Files
wire/internal/goose/parse.go
Ross Light cfc6111ea5 goose: add show command
Lists provider sets in packages given on the command line, including
outputs grouped by what is needed to obtain them.

The goose package now exports the loading phase as an API.

Example output: https://paste.googleplex.com/5509965720584192

Reviewed-by: Tuo Shan <shantuo@google.com>
2018-11-12 14:09:56 -08:00

850 lines
24 KiB
Go

package goose
import (
"fmt"
"go/ast"
"go/build"
"go/token"
"go/types"
"path/filepath"
"strconv"
"strings"
"unicode"
"golang.org/x/tools/go/loader"
)
// A ProviderSet describes a set of providers. The zero value is an empty
// ProviderSet.
type ProviderSet struct {
Providers []*Provider
Bindings []IfaceBinding
Imports []ProviderSetImport
}
// An IfaceBinding declares that a type should be used to satisfy inputs
// of the given interface type.
type IfaceBinding struct {
// Iface is the interface type, which is what can be injected.
Iface types.Type
// Provided is always a type that is assignable to Iface.
Provided types.Type
// Pos is the position where the binding was declared.
Pos token.Pos
}
// A ProviderSetImport adds providers from one provider set into another.
type ProviderSetImport struct {
ProviderSetID
Pos token.Pos
}
// Provider records the signature of a provider. A provider is a
// single Go object, either a function or a named struct type.
type Provider struct {
// ImportPath is the package path that the Go object resides in.
ImportPath string
// Name is the name of the Go object.
Name string
// Pos is the source position of the func keyword or type spec
// defining this provider.
Pos token.Pos
// Args is the list of data dependencies this provider has.
Args []ProviderInput
// IsStruct is true if this provider is a named struct type.
// Otherwise it's a function.
IsStruct bool
// Fields lists the field names to populate. This will map 1:1 with
// elements in Args.
Fields []string
// Out is the type this provider produces.
Out types.Type
// HasCleanup reports whether the provider function returns a cleanup
// function. (Always false for structs.)
HasCleanup bool
// HasErr reports whether the provider function can return an error.
// (Always false for structs.)
HasErr bool
}
// ProviderInput describes an incoming edge in the provider graph.
type ProviderInput struct {
Type types.Type
Optional bool
}
// Load finds all the provider sets in the given packages, as well as
// the provider sets' transitive dependencies.
func Load(bctx *build.Context, wd string, pkgs []string) (*Info, error) {
conf := newLoaderConfig(bctx, wd, false)
for _, p := range pkgs {
conf.Import(p)
}
prog, err := conf.Load()
if err != nil {
return nil, fmt.Errorf("load: %v", err)
}
r := newImportResolver(conf, prog.Fset)
var next []string
initial := make(map[string]struct{})
for _, pkgInfo := range prog.InitialPackages() {
path := pkgInfo.Pkg.Path()
next = append(next, path)
initial[path] = struct{}{}
}
visited := make(map[string]struct{})
info := &Info{
Fset: prog.Fset,
Sets: make(map[ProviderSetID]*ProviderSet),
All: make(map[ProviderSetID]*ProviderSet),
}
for len(next) > 0 {
curr := next[len(next)-1]
next = next[:len(next)-1]
if _, ok := visited[curr]; ok {
continue
}
visited[curr] = struct{}{}
pkgInfo := prog.Package(curr)
sets, err := findProviderSets(findContext{
fset: prog.Fset,
pkg: pkgInfo.Pkg,
typeInfo: &pkgInfo.Info,
r: r,
}, pkgInfo.Files)
if err != nil {
return nil, fmt.Errorf("load: %v", err)
}
path := pkgInfo.Pkg.Path()
for name, set := range sets {
info.All[ProviderSetID{path, name}] = set
for _, imp := range set.Imports {
next = append(next, imp.ImportPath)
}
}
if _, ok := initial[path]; ok {
for name, set := range sets {
info.Sets[ProviderSetID{path, name}] = set
}
}
}
return info, nil
}
// Info holds the result of Load.
type Info struct {
Fset *token.FileSet
// Sets contains all the provider sets in the initial packages.
Sets map[ProviderSetID]*ProviderSet
// All contains all the provider sets transitively depended on by the
// initial packages' provider sets.
All map[ProviderSetID]*ProviderSet
}
// A ProviderSetID identifies a provider set.
type ProviderSetID struct {
ImportPath string
Name string
}
// String returns the ID as ""path/to/pkg".Foo".
func (id ProviderSetID) String() string {
return id.symref().String()
}
func (id ProviderSetID) symref() symref {
return symref{importPath: id.ImportPath, name: id.Name}
}
type findContext struct {
fset *token.FileSet
pkg *types.Package
typeInfo *types.Info
r *importResolver
}
// findProviderSets processes a package and extracts the provider sets declared in it.
func findProviderSets(fctx findContext, files []*ast.File) (map[string]*ProviderSet, error) {
sets := make(map[string]*ProviderSet)
for _, f := range files {
fileScope := fctx.typeInfo.Scopes[f]
if fileScope == nil {
return nil, fmt.Errorf("%s: no scope found for file (likely a bug)", fctx.fset.File(f.Pos()).Name())
}
for _, dg := range parseFile(fctx.fset, f) {
if dg.decl != nil {
if err := processDeclDirectives(fctx, sets, fileScope, dg); err != nil {
return nil, err
}
} else {
for _, d := range dg.dirs {
if err := processUnassociatedDirective(fctx, sets, fileScope, d); err != nil {
return nil, err
}
}
}
}
}
return sets, nil
}
// processUnassociatedDirective handles any directive that was not associated with a top-level declaration.
func processUnassociatedDirective(fctx findContext, sets map[string]*ProviderSet, scope *types.Scope, d directive) error {
switch d.kind {
case "provide", "optional":
return fmt.Errorf("%v: only functions can be marked as providers", fctx.fset.Position(d.pos))
case "use":
// Ignore, picked up by injector flow.
case "bind":
args := d.args()
if len(args) != 3 {
return fmt.Errorf("%v: invalid binding: expected TARGET IFACE TYPE", fctx.fset.Position(d.pos))
}
ifaceRef, err := parseSymbolRef(fctx.r, args[1], scope, fctx.pkg.Path(), d.pos)
if err != nil {
return fmt.Errorf("%v: %v", fctx.fset.Position(d.pos), err)
}
ifaceObj, err := ifaceRef.resolveObject(fctx.pkg)
if err != nil {
return fmt.Errorf("%v: %v", fctx.fset.Position(d.pos), err)
}
ifaceDecl, ok := ifaceObj.(*types.TypeName)
if !ok {
return fmt.Errorf("%v: %v does not name a type", fctx.fset.Position(d.pos), ifaceRef)
}
iface := ifaceDecl.Type()
methodSet, ok := iface.Underlying().(*types.Interface)
if !ok {
return fmt.Errorf("%v: %v does not name an interface type", fctx.fset.Position(d.pos), ifaceRef)
}
providedRef, err := parseSymbolRef(fctx.r, strings.TrimPrefix(args[2], "*"), scope, fctx.pkg.Path(), d.pos)
if err != nil {
return fmt.Errorf("%v: %v", fctx.fset.Position(d.pos), err)
}
providedObj, err := providedRef.resolveObject(fctx.pkg)
if err != nil {
return fmt.Errorf("%v: %v", fctx.fset.Position(d.pos), err)
}
providedDecl, ok := providedObj.(*types.TypeName)
if !ok {
return fmt.Errorf("%v: %v does not name a type", fctx.fset.Position(d.pos), providedRef)
}
provided := providedDecl.Type()
if types.Identical(provided, iface) {
return fmt.Errorf("%v: cannot bind interface to itself", fctx.fset.Position(d.pos))
}
if strings.HasPrefix(args[2], "*") {
provided = types.NewPointer(provided)
}
if !types.Implements(provided, methodSet) {
return fmt.Errorf("%v: %s does not implement %s", fctx.fset.Position(d.pos), types.TypeString(provided, nil), types.TypeString(iface, nil))
}
name := args[0]
if pset := sets[name]; pset != nil {
pset.Bindings = append(pset.Bindings, IfaceBinding{
Iface: iface,
Provided: provided,
})
} else {
sets[name] = &ProviderSet{
Bindings: []IfaceBinding{{
Iface: iface,
Provided: provided,
}},
}
}
case "import":
args := d.args()
if len(args) < 2 {
return fmt.Errorf("%v: invalid import: expected TARGET SETREF", fctx.fset.Position(d.pos))
}
name := args[0]
for _, spec := range args[1:] {
ref, err := parseSymbolRef(fctx.r, spec, scope, fctx.pkg.Path(), d.pos)
if err != nil {
return fmt.Errorf("%v: %v", fctx.fset.Position(d.pos), err)
}
if findImport(fctx.pkg, ref.importPath) == nil {
return fmt.Errorf("%v: provider set %s imports %q which is not in the package's imports", fctx.fset.Position(d.pos), name, ref.importPath)
}
if mod := sets[name]; mod != nil {
found := false
for _, other := range mod.Imports {
if ref == other.symref() {
found = true
break
}
}
if !found {
mod.Imports = append(mod.Imports, ProviderSetImport{
ProviderSetID: ProviderSetID{
ImportPath: ref.importPath,
Name: ref.name,
},
Pos: d.pos,
})
}
} else {
sets[name] = &ProviderSet{
Imports: []ProviderSetImport{{
ProviderSetID: ProviderSetID{
ImportPath: ref.importPath,
Name: ref.name,
},
Pos: d.pos,
}},
}
}
}
default:
return fmt.Errorf("%v: unknown directive %s", fctx.fset.Position(d.pos), d.kind)
}
return nil
}
// processDeclDirectives processes the directives associated with a top-level declaration.
func processDeclDirectives(fctx findContext, sets map[string]*ProviderSet, scope *types.Scope, dg directiveGroup) error {
p, err := dg.single(fctx.fset, "provide")
if err != nil {
return err
}
if !p.isValid() {
for _, d := range dg.dirs {
if d.kind == "optional" {
return fmt.Errorf("%v: cannot use goose:%s directive on non-provider", fctx.fset.Position(d.pos), d.kind)
}
}
return nil
}
var providerSetName string
if args := p.args(); len(args) == 1 {
// TODO(light): validate identifier
providerSetName = args[0]
} else if len(args) > 1 {
return fmt.Errorf("%v: goose:provide takes at most one argument", fctx.fset.Position(p.pos))
}
optionals := make(map[string]token.Pos)
for _, d := range dg.dirs {
if d.kind == "optional" {
for _, arg := range d.args() {
optionals[arg] = d.pos
}
}
}
switch decl := dg.decl.(type) {
case *ast.FuncDecl:
fn := fctx.typeInfo.ObjectOf(decl.Name).(*types.Func)
provider, err := processFuncProvider(fctx, fn, optionals)
if err != nil {
return err
}
if providerSetName == "" {
providerSetName = fn.Name()
}
if mod := sets[providerSetName]; mod != nil {
for _, other := range mod.Providers {
if types.Identical(other.Out, provider.Out) {
return fmt.Errorf("%v: provider set %s has multiple providers for %s (previous declaration at %v)", fctx.fset.Position(fn.Pos()), providerSetName, types.TypeString(provider.Out, nil), fctx.fset.Position(other.Pos))
}
}
mod.Providers = append(mod.Providers, provider)
} else {
sets[providerSetName] = &ProviderSet{
Providers: []*Provider{provider},
}
}
case *ast.GenDecl:
if decl.Tok != token.TYPE {
return fmt.Errorf("%v: only functions and structs can be marked as providers", fctx.fset.Position(p.pos))
}
if len(decl.Specs) != 1 {
// TODO(light): tighten directive extraction to associate with particular specs.
return fmt.Errorf("%v: only functions and structs can be marked as providers", fctx.fset.Position(p.pos))
}
typeName := fctx.typeInfo.ObjectOf(decl.Specs[0].(*ast.TypeSpec).Name).(*types.TypeName)
if _, ok := typeName.Type().(*types.Named).Underlying().(*types.Struct); !ok {
return fmt.Errorf("%v: only functions and structs can be marked as providers", fctx.fset.Position(p.pos))
}
provider, err := processStructProvider(fctx, typeName, optionals)
if err != nil {
return err
}
if providerSetName == "" {
providerSetName = typeName.Name()
}
ptrProvider := new(Provider)
*ptrProvider = *provider
ptrProvider.Out = types.NewPointer(provider.Out)
if mod := sets[providerSetName]; mod != nil {
for _, other := range mod.Providers {
if types.Identical(other.Out, provider.Out) {
return fmt.Errorf("%v: provider set %s has multiple providers for %s (previous declaration at %v)", fctx.fset.Position(typeName.Pos()), providerSetName, types.TypeString(provider.Out, nil), fctx.fset.Position(other.Pos))
}
if types.Identical(other.Out, ptrProvider.Out) {
return fmt.Errorf("%v: provider set %s has multiple providers for %s (previous declaration at %v)", fctx.fset.Position(typeName.Pos()), providerSetName, types.TypeString(ptrProvider.Out, nil), fctx.fset.Position(other.Pos))
}
}
mod.Providers = append(mod.Providers, provider, ptrProvider)
} else {
sets[providerSetName] = &ProviderSet{
Providers: []*Provider{provider, ptrProvider},
}
}
default:
return fmt.Errorf("%v: only functions and structs can be marked as providers", fctx.fset.Position(p.pos))
}
return nil
}
func processFuncProvider(fctx findContext, fn *types.Func, optionalArgs map[string]token.Pos) (*Provider, error) {
sig := fn.Type().(*types.Signature)
optionals := make([]bool, sig.Params().Len())
for arg, dpos := range optionalArgs {
pi := paramIndex(sig.Params(), arg)
if pi == -1 {
return nil, fmt.Errorf("%v: %s is not a parameter of func %s", fctx.fset.Position(dpos), arg, fn.Name())
}
optionals[pi] = true
}
fpos := fn.Pos()
r := sig.Results()
var hasCleanup, hasErr bool
switch r.Len() {
case 1:
hasCleanup, hasErr = false, false
case 2:
switch t := r.At(1).Type(); {
case types.Identical(t, errorType):
hasCleanup, hasErr = false, true
case types.Identical(t, cleanupType):
hasCleanup, hasErr = true, false
default:
return nil, fmt.Errorf("%v: wrong signature for provider %s: second return type must be error or func()", fctx.fset.Position(fpos), fn.Name())
}
case 3:
if t := r.At(1).Type(); !types.Identical(t, cleanupType) {
return nil, fmt.Errorf("%v: wrong signature for provider %s: second return type must be func()", fctx.fset.Position(fpos), fn.Name())
}
if t := r.At(2).Type(); !types.Identical(t, errorType) {
return nil, fmt.Errorf("%v: wrong signature for provider %s: third return type must be error", fctx.fset.Position(fpos), fn.Name())
}
hasCleanup, hasErr = true, true
default:
return nil, fmt.Errorf("%v: wrong signature for provider %s: must have one return value and optional error", fctx.fset.Position(fpos), fn.Name())
}
out := r.At(0).Type()
params := sig.Params()
provider := &Provider{
ImportPath: fctx.pkg.Path(),
Name: fn.Name(),
Pos: fn.Pos(),
Args: make([]ProviderInput, params.Len()),
Out: out,
HasCleanup: hasCleanup,
HasErr: hasErr,
}
for i := 0; i < params.Len(); i++ {
provider.Args[i] = ProviderInput{
Type: params.At(i).Type(),
Optional: optionals[i],
}
for j := 0; j < i; j++ {
if types.Identical(provider.Args[i].Type, provider.Args[j].Type) {
return nil, fmt.Errorf("%v: provider has multiple parameters of type %s", fctx.fset.Position(fpos), types.TypeString(provider.Args[j].Type, nil))
}
}
}
return provider, nil
}
func processStructProvider(fctx findContext, typeName *types.TypeName, optionals map[string]token.Pos) (*Provider, error) {
out := typeName.Type()
st := out.Underlying().(*types.Struct)
for arg, dpos := range optionals {
found := false
for i := 0; i < st.NumFields(); i++ {
if st.Field(i).Name() == arg {
found = true
break
}
}
if !found {
return nil, fmt.Errorf("%v: %s is not a field of struct %s", fctx.fset.Position(dpos), arg, types.TypeString(st, nil))
}
}
pos := typeName.Pos()
provider := &Provider{
ImportPath: fctx.pkg.Path(),
Name: typeName.Name(),
Pos: pos,
Args: make([]ProviderInput, st.NumFields()),
Fields: make([]string, st.NumFields()),
IsStruct: true,
Out: out,
}
for i := 0; i < st.NumFields(); i++ {
f := st.Field(i)
_, optional := optionals[f.Name()]
provider.Args[i] = ProviderInput{
Type: f.Type(),
Optional: optional,
}
provider.Fields[i] = f.Name()
for j := 0; j < i; j++ {
if types.Identical(provider.Args[i].Type, provider.Args[j].Type) {
return nil, fmt.Errorf("%v: provider struct has multiple fields of type %s", fctx.fset.Position(pos), types.TypeString(provider.Args[j].Type, nil))
}
}
}
return provider, nil
}
// providerSetCache is a lazily evaluated index of provider sets.
type providerSetCache struct {
sets map[string]map[string]*ProviderSet
fset *token.FileSet
prog *loader.Program
r *importResolver
}
func newProviderSetCache(prog *loader.Program, r *importResolver) *providerSetCache {
return &providerSetCache{
fset: prog.Fset,
prog: prog,
r: r,
}
}
func (mc *providerSetCache) get(ref symref) (*ProviderSet, error) {
if mods, cached := mc.sets[ref.importPath]; cached {
mod := mods[ref.name]
if mod == nil {
return nil, fmt.Errorf("no such provider set %s in package %q", ref.name, ref.importPath)
}
return mod, nil
}
if mc.sets == nil {
mc.sets = make(map[string]map[string]*ProviderSet)
}
pkg := mc.prog.Package(ref.importPath)
mods, err := findProviderSets(findContext{
fset: mc.fset,
pkg: pkg.Pkg,
typeInfo: &pkg.Info,
r: mc.r,
}, pkg.Files)
if err != nil {
mc.sets[ref.importPath] = nil
return nil, err
}
mc.sets[ref.importPath] = mods
mod := mods[ref.name]
if mod == nil {
return nil, fmt.Errorf("no such provider set %s in package %q", ref.name, ref.importPath)
}
return mod, nil
}
// A symref is a parsed reference to a symbol (either a provider set or a Go object).
type symref struct {
importPath string
name string
}
func parseSymbolRef(r *importResolver, ref string, s *types.Scope, pkg string, pos token.Pos) (symref, error) {
// TODO(light): verify that provider set name is an identifier before returning
i := strings.LastIndexByte(ref, '.')
if i == -1 {
return symref{importPath: pkg, name: ref}, nil
}
imp, name := ref[:i], ref[i+1:]
if strings.HasPrefix(imp, `"`) {
path, err := strconv.Unquote(imp)
if err != nil {
return symref{}, fmt.Errorf("parse symbol reference %q: bad import path", ref)
}
path, err = r.resolve(pos, path)
if err != nil {
return symref{}, fmt.Errorf("parse symbol reference %q: %v", ref, err)
}
return symref{importPath: path, name: name}, nil
}
_, obj := s.LookupParent(imp, pos)
if obj == nil {
return symref{}, fmt.Errorf("parse symbol reference %q: unknown identifier %s", ref, imp)
}
pn, ok := obj.(*types.PkgName)
if !ok {
return symref{}, fmt.Errorf("parse symbol reference %q: %s does not name a package", ref, imp)
}
return symref{importPath: pn.Imported().Path(), name: name}, nil
}
func (ref symref) String() string {
return strconv.Quote(ref.importPath) + "." + ref.name
}
func (ref symref) resolveObject(pkg *types.Package) (types.Object, error) {
imp := findImport(pkg, ref.importPath)
if imp == nil {
return nil, fmt.Errorf("resolve Go reference %v: package not directly imported", ref)
}
obj := imp.Scope().Lookup(ref.name)
if obj == nil {
return nil, fmt.Errorf("resolve Go reference %v: %s not found in package", ref, ref.name)
}
return obj, nil
}
type importResolver struct {
fset *token.FileSet
bctx *build.Context
findPackage func(bctx *build.Context, importPath, fromDir string, mode build.ImportMode) (*build.Package, error)
}
func newImportResolver(c *loader.Config, fset *token.FileSet) *importResolver {
r := &importResolver{
fset: fset,
bctx: c.Build,
findPackage: c.FindPackage,
}
if r.bctx == nil {
r.bctx = &build.Default
}
if r.findPackage == nil {
r.findPackage = (*build.Context).Import
}
return r
}
func (r *importResolver) resolve(pos token.Pos, path string) (string, error) {
dir := filepath.Dir(r.fset.File(pos).Name())
pkg, err := r.findPackage(r.bctx, path, dir, build.FindOnly)
if err != nil {
return "", err
}
return pkg.ImportPath, nil
}
func findImport(pkg *types.Package, path string) *types.Package {
if pkg.Path() == path {
return pkg
}
for _, imp := range pkg.Imports() {
if imp.Path() == path {
return imp
}
}
return nil
}
// A directive is a parsed goose comment.
type directive struct {
pos token.Pos
kind string
line string
}
// A directiveGroup is a set of directives associated with a particular
// declaration.
type directiveGroup struct {
decl ast.Decl
dirs []directive
}
// parseFile extracts the directives from a file, grouped by declaration.
func parseFile(fset *token.FileSet, f *ast.File) []directiveGroup {
cmap := ast.NewCommentMap(fset, f, f.Comments)
// Reserve first group for directives that don't associate with a
// declaration, like import.
groups := make([]directiveGroup, 1, len(f.Decls)+1)
// Walk declarations and add to groups.
for _, decl := range f.Decls {
grp := directiveGroup{decl: decl}
ast.Inspect(decl, func(node ast.Node) bool {
if g := cmap[node]; len(g) > 0 {
for _, cg := range g {
start := len(grp.dirs)
grp.dirs = extractDirectives(grp.dirs, cg)
// Move directives that don't associate into the unassociated group.
n := 0
for i := start; i < len(grp.dirs); i++ {
if k := grp.dirs[i].kind; k == "provide" || k == "optional" || k == "use" {
grp.dirs[start+n] = grp.dirs[i]
n++
} else {
groups[0].dirs = append(groups[0].dirs, grp.dirs[i])
}
}
grp.dirs = grp.dirs[:start+n]
}
delete(cmap, node)
}
return true
})
if len(grp.dirs) > 0 {
groups = append(groups, grp)
}
}
// Place remaining directives into the unassociated group.
unassoc := &groups[0]
for _, g := range cmap {
for _, cg := range g {
unassoc.dirs = extractDirectives(unassoc.dirs, cg)
}
}
if len(unassoc.dirs) == 0 {
return groups[1:]
}
return groups
}
func extractDirectives(d []directive, cg *ast.CommentGroup) []directive {
const prefix = "goose:"
text := cg.Text()
for len(text) > 0 {
text = strings.TrimLeft(text, " \t\r\n")
if !strings.HasPrefix(text, prefix) {
break
}
line := text[len(prefix):]
// Text() is always newline terminated.
i := strings.IndexByte(line, '\n')
line, text = line[:i], line[i+1:]
if i := strings.IndexByte(line, ' '); i != -1 {
d = append(d, directive{
kind: line[:i],
line: strings.TrimSpace(line[i+1:]),
pos: cg.Pos(), // TODO(light): more precise position
})
} else {
d = append(d, directive{
kind: line,
pos: cg.Pos(), // TODO(light): more precise position
})
}
}
return d
}
// single finds at most one directive that matches the given kind.
func (dg directiveGroup) single(fset *token.FileSet, kind string) (directive, error) {
var found directive
ok := false
for _, d := range dg.dirs {
if d.kind != kind {
continue
}
if ok {
switch decl := dg.decl.(type) {
case *ast.FuncDecl:
return directive{}, fmt.Errorf("%v: multiple %s directives for %s", fset.Position(d.pos), kind, decl.Name.Name)
case *ast.GenDecl:
if decl.Tok == token.TYPE && len(decl.Specs) == 1 {
name := decl.Specs[0].(*ast.TypeSpec).Name.Name
return directive{}, fmt.Errorf("%v: multiple %s directives for %s", fset.Position(d.pos), kind, name)
}
return directive{}, fmt.Errorf("%v: multiple %s directives", fset.Position(d.pos), kind)
default:
return directive{}, fmt.Errorf("%v: multiple %s directives", fset.Position(d.pos), kind)
}
}
found, ok = d, true
}
return found, nil
}
func (d directive) isValid() bool {
return d.kind != ""
}
// args splits the directive line into tokens.
func (d directive) args() []string {
var args []string
start := -1
state := 0 // 0 = boundary, 1 = in token, 2 = in quote, 3 = quote backslash
for i, r := range d.line {
switch state {
case 0:
// Argument boundary
switch {
case r == '"':
start = i
state = 2
case !unicode.IsSpace(r):
start = i
state = 1
}
case 1:
// In token
switch {
case unicode.IsSpace(r):
args = append(args, d.line[start:i])
start = -1
state = 0
case r == '"':
state = 2
}
case 2:
// In quotes
switch {
case r == '"':
state = 1
case r == '\\':
state = 3
}
case 3:
// Quote backslash. Consumes one character and jumps back into "in quote" state.
state = 2
default:
panic("unreachable")
}
}
if start != -1 {
args = append(args, d.line[start:])
}
return args
}
// isInjectFile reports whether a given file is an injection template.
func isInjectFile(f *ast.File) bool {
// TODO(light): better determination
for _, cg := range f.Comments {
text := cg.Text()
if strings.HasPrefix(text, "+build") && strings.Contains(text, "gooseinject") {
return true
}
}
return false
}
// paramIndex returns the index of the parameter with the given name, or
// -1 if no such parameter exists.
func paramIndex(params *types.Tuple, name string) int {
for i := 0; i < params.Len(); i++ {
if params.At(i).Name() == name {
return i
}
}
return -1
}