woodpecker/vendor/github.com/quasilyte/go-ruleguard/ruleguard/parser.go
Lukas c28f7cb29f
Add golangci-lint (#502)
Initial part of #435
2021-11-14 21:01:54 +01:00

988 lines
26 KiB
Go

package ruleguard
import (
"bytes"
"errors"
"fmt"
"go/ast"
"go/parser"
"go/token"
"go/types"
"io"
"io/ioutil"
"path"
"regexp"
"strconv"
"github.com/quasilyte/go-ruleguard/internal/gogrep"
"github.com/quasilyte/go-ruleguard/nodetag"
"github.com/quasilyte/go-ruleguard/ruleguard/goutil"
"github.com/quasilyte/go-ruleguard/ruleguard/quasigo"
"github.com/quasilyte/go-ruleguard/ruleguard/typematch"
)
// TODO(quasilyte): use source code byte slicing instead of SprintNode?
type parseError struct{ error }
// ImportError is returned when a ruleguard file references a package that cannot be imported.
type ImportError struct {
msg string
err error
}
func (e *ImportError) Error() string { return e.msg }
func (e *ImportError) Unwrap() error { return e.err }
type rulesParser struct {
state *engineState
ctx *ParseContext
prefix string // For imported packages, a prefix that is added to a rule group name
importedPkg string // Package path; only for imported packages
filename string
group string
res *goRuleSet
pkg *types.Package
types *types.Info
importer *goImporter
itab *typematch.ImportsTab
imported []*goRuleSet
dslPkgname string // The local name of the "ruleguard/dsl" package (usually its just "dsl")
}
type rulesParserConfig struct {
state *engineState
ctx *ParseContext
importer *goImporter
prefix string
importedPkg string
itab *typematch.ImportsTab
}
func newRulesParser(config rulesParserConfig) *rulesParser {
return &rulesParser{
state: config.state,
ctx: config.ctx,
importer: config.importer,
prefix: config.prefix,
importedPkg: config.importedPkg,
itab: config.itab,
}
}
func (p *rulesParser) ParseFile(filename string, r io.Reader) (*goRuleSet, error) {
p.dslPkgname = "dsl"
p.filename = filename
p.res = &goRuleSet{
universal: &scopedGoRuleSet{},
groups: make(map[string]token.Position),
}
parserFlags := parser.Mode(0)
f, err := parser.ParseFile(p.ctx.Fset, filename, r, parserFlags)
if err != nil {
return nil, fmt.Errorf("parse file error: %w", err)
}
for _, imp := range f.Imports {
importPath, err := strconv.Unquote(imp.Path.Value)
if err != nil {
return nil, p.errorf(imp, fmt.Errorf("unquote %s import path: %w", imp.Path.Value, err))
}
if importPath == "github.com/quasilyte/go-ruleguard/dsl" {
if imp.Name != nil {
p.dslPkgname = imp.Name.Name
}
}
}
if f.Name.Name != "gorules" {
return nil, fmt.Errorf("expected a gorules package name, found %s", f.Name.Name)
}
typechecker := types.Config{Importer: p.importer}
p.types = &types.Info{
Types: map[ast.Expr]types.TypeAndValue{},
Uses: map[*ast.Ident]types.Object{},
Defs: map[*ast.Ident]types.Object{},
}
pkg, err := typechecker.Check("gorules", p.ctx.Fset, []*ast.File{f}, p.types)
if err != nil {
return nil, fmt.Errorf("typechecker error: %w", err)
}
p.pkg = pkg
var matcherFuncs []*ast.FuncDecl
var userFuncs []*ast.FuncDecl
for _, decl := range f.Decls {
decl, ok := decl.(*ast.FuncDecl)
if !ok {
continue
}
if decl.Name.String() == "init" {
if err := p.parseInitFunc(decl); err != nil {
return nil, err
}
continue
}
if p.isMatcherFunc(decl) {
matcherFuncs = append(matcherFuncs, decl)
} else {
userFuncs = append(userFuncs, decl)
}
}
for _, decl := range userFuncs {
if err := p.parseUserFunc(decl); err != nil {
return nil, err
}
}
for _, decl := range matcherFuncs {
if err := p.parseRuleGroup(decl); err != nil {
return nil, err
}
}
if len(p.imported) != 0 {
toMerge := []*goRuleSet{p.res}
toMerge = append(toMerge, p.imported...)
merged, err := mergeRuleSets(toMerge)
if err != nil {
return nil, err
}
p.res = merged
}
return p.res, nil
}
func (p *rulesParser) parseUserFunc(f *ast.FuncDecl) error {
ctx := &quasigo.CompileContext{
Env: p.state.env,
Types: p.types,
Fset: p.ctx.Fset,
}
compiled, err := quasigo.Compile(ctx, f)
if err != nil {
return err
}
if p.ctx.DebugFilter == f.Name.String() {
p.ctx.DebugPrint(quasigo.Disasm(p.state.env, compiled))
}
ctx.Env.AddFunc(p.pkg.Path(), f.Name.String(), compiled)
return nil
}
func (p *rulesParser) parseInitFunc(f *ast.FuncDecl) error {
type bundleImport struct {
node ast.Node
prefix string
pkgPath string
}
var imported []bundleImport
for _, stmt := range f.Body.List {
exprStmt, ok := stmt.(*ast.ExprStmt)
if !ok {
return p.errorf(stmt, errors.New("unsupported statement"))
}
call, ok := exprStmt.X.(*ast.CallExpr)
if !ok {
return p.errorf(stmt, errors.New("unsupported expr"))
}
fn, ok := call.Fun.(*ast.SelectorExpr)
if !ok {
return p.errorf(stmt, errors.New("unsupported call"))
}
pkg, ok := fn.X.(*ast.Ident)
if !ok || pkg.Name != p.dslPkgname {
return p.errorf(stmt, errors.New("unsupported call"))
}
switch fn.Sel.Name {
case "ImportRules":
if p.importedPkg != "" {
return p.errorf(call, errors.New("imports from imported packages are not supported yet"))
}
prefix := p.parseStringArg(call.Args[0])
bundleSelector, ok := call.Args[1].(*ast.SelectorExpr)
if !ok {
return p.errorf(call.Args[1], errors.New("expected a `pkgname.Bundle` argument"))
}
bundleObj := p.types.ObjectOf(bundleSelector.Sel)
imported = append(imported, bundleImport{
node: stmt,
prefix: prefix,
pkgPath: bundleObj.Pkg().Path(),
})
default:
return p.errorf(stmt, fmt.Errorf("unsupported %s call", fn.Sel.Name))
}
}
for _, imp := range imported {
files, err := findBundleFiles(imp.pkgPath)
if err != nil {
return p.errorf(imp.node, fmt.Errorf("import lookup error: %w", err))
}
for _, filename := range files {
rset, err := p.importRules(imp.prefix, imp.pkgPath, filename)
if err != nil {
return p.errorf(imp.node, fmt.Errorf("import parsing error: %w", err))
}
p.imported = append(p.imported, rset)
}
}
return nil
}
func (p *rulesParser) importRules(prefix, pkgPath, filename string) (*goRuleSet, error) {
data, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
config := rulesParserConfig{
state: p.state,
ctx: p.ctx,
importer: p.importer,
prefix: prefix,
importedPkg: pkgPath,
itab: p.itab,
}
rset, err := newRulesParser(config).ParseFile(filename, bytes.NewReader(data))
if err != nil {
return nil, fmt.Errorf("%s: %w", p.importedPkg, err)
}
return rset, nil
}
func (p *rulesParser) isMatcherFunc(f *ast.FuncDecl) bool {
typ := p.types.ObjectOf(f.Name).Type().(*types.Signature)
return typ.Results().Len() == 0 &&
typ.Params().Len() == 1 &&
typ.Params().At(0).Type().String() == "github.com/quasilyte/go-ruleguard/dsl.Matcher"
}
func (p *rulesParser) parseRuleGroup(f *ast.FuncDecl) (err error) {
defer func() {
if err != nil {
return
}
rv := recover()
if rv == nil {
return
}
if parseErr, ok := rv.(parseError); ok {
err = parseErr.error
return
}
panic(rv) // not our panic
}()
if f.Name.String() == "_" {
return p.errorf(f.Name, errors.New("`_` is not a valid rule group function name"))
}
if f.Body == nil {
return p.errorf(f, errors.New("unexpected empty function body"))
}
params := f.Type.Params.List
matcher := params[0].Names[0].Name
p.group = f.Name.Name
if p.prefix != "" {
p.group = p.prefix + "/" + f.Name.Name
}
if p.ctx.GroupFilter != nil && !p.ctx.GroupFilter(p.group) {
return nil // Skip this group
}
if _, ok := p.res.groups[p.group]; ok {
panic(fmt.Sprintf("duplicated function %s after the typecheck", p.group)) // Should never happen
}
p.res.groups[p.group] = token.Position{
Filename: p.filename,
Line: p.ctx.Fset.Position(f.Name.Pos()).Line,
}
p.itab.EnterScope()
defer p.itab.LeaveScope()
for _, stmt := range f.Body.List {
if _, ok := stmt.(*ast.DeclStmt); ok {
continue
}
stmtExpr, ok := stmt.(*ast.ExprStmt)
if !ok {
return p.errorf(stmt, fmt.Errorf("expected a %s method call, found %s", matcher, goutil.SprintNode(p.ctx.Fset, stmt)))
}
call, ok := stmtExpr.X.(*ast.CallExpr)
if !ok {
return p.errorf(stmt, fmt.Errorf("expected a %s method call, found %s", matcher, goutil.SprintNode(p.ctx.Fset, stmt)))
}
if err := p.parseCall(matcher, call); err != nil {
return err
}
}
return nil
}
func (p *rulesParser) parseCall(matcher string, call *ast.CallExpr) error {
f := call.Fun.(*ast.SelectorExpr)
x, ok := f.X.(*ast.Ident)
if ok && x.Name == matcher {
return p.parseStmt(f.Sel, call.Args)
}
return p.parseRule(matcher, call)
}
func (p *rulesParser) parseStmt(fn *ast.Ident, args []ast.Expr) error {
switch fn.Name {
case "Import":
pkgPath, ok := p.toStringValue(args[0])
if !ok {
return p.errorf(args[0], errors.New("expected a string literal argument"))
}
pkgName := path.Base(pkgPath)
p.itab.Load(pkgName, pkgPath)
return nil
default:
return p.errorf(fn, fmt.Errorf("unexpected %s method", fn.Name))
}
}
func (p *rulesParser) parseRule(matcher string, call *ast.CallExpr) error {
origCall := call
var (
matchArgs *[]ast.Expr
matchCommentArgs *[]ast.Expr
whereArgs *[]ast.Expr
suggestArgs *[]ast.Expr
reportArgs *[]ast.Expr
atArgs *[]ast.Expr
)
for {
chain, ok := call.Fun.(*ast.SelectorExpr)
if !ok {
break
}
switch chain.Sel.Name {
case "Match":
if matchArgs != nil {
return p.errorf(chain.Sel, errors.New("Match() can't be repeated"))
}
if matchCommentArgs != nil {
return p.errorf(chain.Sel, errors.New("Match() and MatchComment() can't be combined"))
}
matchArgs = &call.Args
case "MatchComment":
if matchCommentArgs != nil {
return p.errorf(chain.Sel, errors.New("MatchComment() can't be repeated"))
}
if matchArgs != nil {
return p.errorf(chain.Sel, errors.New("Match() and MatchComment() can't be combined"))
}
matchCommentArgs = &call.Args
case "Where":
if whereArgs != nil {
return p.errorf(chain.Sel, errors.New("Where() can't be repeated"))
}
whereArgs = &call.Args
case "Suggest":
if suggestArgs != nil {
return p.errorf(chain.Sel, errors.New("Suggest() can't be repeated"))
}
suggestArgs = &call.Args
case "Report":
if reportArgs != nil {
return p.errorf(chain.Sel, errors.New("Report() can't be repeated"))
}
reportArgs = &call.Args
case "At":
if atArgs != nil {
return p.errorf(chain.Sel, errors.New("At() can't be repeated"))
}
atArgs = &call.Args
default:
return p.errorf(chain.Sel, fmt.Errorf("unexpected %s method", chain.Sel.Name))
}
call, ok = chain.X.(*ast.CallExpr)
if !ok {
break
}
}
proto := goRule{
filename: p.filename,
line: p.ctx.Fset.Position(origCall.Pos()).Line,
group: p.group,
}
// AST patterns for Match() or regexp patterns for MatchComment().
var alternatives []string
if matchArgs == nil && matchCommentArgs == nil {
return p.errorf(origCall, errors.New("missing Match() or MatchComment() call"))
}
if matchArgs != nil {
for _, arg := range *matchArgs {
alternatives = append(alternatives, p.parseStringArg(arg))
}
} else {
for _, arg := range *matchCommentArgs {
alternatives = append(alternatives, p.parseStringArg(arg))
}
}
if whereArgs != nil {
proto.filter = p.parseFilter((*whereArgs)[0])
}
if suggestArgs != nil {
proto.suggestion = p.parseStringArg((*suggestArgs)[0])
}
if reportArgs == nil {
if suggestArgs == nil {
return p.errorf(origCall, errors.New("missing Report() or Suggest() call"))
}
proto.msg = "suggestion: " + proto.suggestion
} else {
proto.msg = p.parseStringArg((*reportArgs)[0])
}
if atArgs != nil {
index, ok := (*atArgs)[0].(*ast.IndexExpr)
if !ok {
return p.errorf((*atArgs)[0], fmt.Errorf("expected %s[`varname`] expression", matcher))
}
arg, ok := p.toStringValue(index.Index)
if !ok {
return p.errorf(index.Index, errors.New("expected a string literal index"))
}
proto.location = arg
}
if matchArgs != nil {
return p.loadGogrepRules(proto, *matchArgs, alternatives)
}
return p.loadCommentRules(proto, *matchCommentArgs, alternatives)
}
func (p *rulesParser) loadCommentRules(proto goRule, matchArgs []ast.Expr, alternatives []string) error {
dst := p.res.universal
for i, alt := range alternatives {
pat, err := regexp.Compile(alt)
if err != nil {
return p.errorf(matchArgs[i], fmt.Errorf("parse match comment pattern: %w", err))
}
rule := goCommentRule{
base: proto,
pat: pat,
captureGroups: regexpHasCaptureGroups(alt),
}
dst.commentRules = append(dst.commentRules, rule)
}
return nil
}
func (p *rulesParser) loadGogrepRules(proto goRule, matchArgs []ast.Expr, alternatives []string) error {
dst := p.res.universal
for i, alt := range alternatives {
rule := proto
pat, err := gogrep.Compile(p.ctx.Fset, alt, false)
if err != nil {
return p.errorf(matchArgs[i], fmt.Errorf("parse match pattern: %w", err))
}
rule.pat = pat
var dstTags []nodetag.Value
switch tag := pat.NodeTag(); tag {
case nodetag.Unknown:
return p.errorf(matchArgs[i], fmt.Errorf("can't infer a tag of %s", alt))
case nodetag.Node:
// TODO: add to every bucket?
return p.errorf(matchArgs[i], fmt.Errorf("%s is too general", alt))
case nodetag.StmtList:
dstTags = []nodetag.Value{
nodetag.BlockStmt,
nodetag.CaseClause,
nodetag.CommClause,
}
case nodetag.ExprList:
dstTags = []nodetag.Value{
nodetag.CallExpr,
nodetag.CompositeLit,
nodetag.ReturnStmt,
}
default:
dstTags = []nodetag.Value{tag}
}
for _, tag := range dstTags {
dst.rulesByTag[tag] = append(dst.rulesByTag[tag], rule)
}
dst.categorizedNum++
}
return nil
}
func (p *rulesParser) parseFilter(root ast.Expr) matchFilter {
return p.parseFilterExpr(root)
}
func (p *rulesParser) errorf(n ast.Node, err error) parseError {
loc := p.ctx.Fset.Position(n.Pos())
return parseError{fmt.Errorf("%s:%d: %w", loc.Filename, loc.Line, err)}
}
func (p *rulesParser) parseStringArg(e ast.Expr) string {
s, ok := p.toStringValue(e)
if !ok {
panic(p.errorf(e, errors.New("expected a string literal argument")))
}
return s
}
func (p *rulesParser) parseRegexpArg(e ast.Expr) *regexp.Regexp {
patternString, ok := p.toStringValue(e)
if !ok {
panic(p.errorf(e, errors.New("expected a regexp pattern argument")))
}
re, err := regexp.Compile(patternString)
if err != nil {
panic(p.errorf(e, err))
}
return re
}
func (p *rulesParser) parseTypeStringArg(e ast.Expr) types.Type {
typeString, ok := p.toStringValue(e)
if !ok {
panic(p.errorf(e, errors.New("expected a type string argument")))
}
typ, err := typeFromString(typeString)
if err != nil {
panic(p.errorf(e, fmt.Errorf("parse type expr: %w", err)))
}
if typ == nil {
panic(p.errorf(e, fmt.Errorf("can't convert %s into a type constraint yet", typeString)))
}
return typ
}
func (p *rulesParser) parseFilterExpr(e ast.Expr) matchFilter {
result := matchFilter{src: goutil.SprintNode(p.ctx.Fset, e)}
switch e := e.(type) {
case *ast.ParenExpr:
return p.parseFilterExpr(e.X)
case *ast.UnaryExpr:
x := p.parseFilterExpr(e.X)
if e.Op == token.NOT {
result.fn = makeNotFilter(result.src, x)
return result
}
panic(p.errorf(e, fmt.Errorf("unsupported unary op: %s", result.src)))
case *ast.BinaryExpr:
switch e.Op {
case token.LAND:
result.fn = makeAndFilter(p.parseFilterExpr(e.X), p.parseFilterExpr(e.Y))
return result
case token.LOR:
result.fn = makeOrFilter(p.parseFilterExpr(e.X), p.parseFilterExpr(e.Y))
return result
case token.GEQ, token.LEQ, token.LSS, token.GTR, token.EQL, token.NEQ:
operand := p.toFilterOperand(e.X)
rhs := p.toFilterOperand(e.Y)
rhsValue := p.types.Types[e.Y].Value
if operand.path == "Type.Size" && rhsValue != nil {
result.fn = makeTypeSizeConstFilter(result.src, operand.varName, e.Op, rhsValue)
return result
}
if operand.path == "Value.Int" && rhsValue != nil {
result.fn = makeValueIntConstFilter(result.src, operand.varName, e.Op, rhsValue)
return result
}
if operand.path == "Value.Int" && rhs.path == "Value.Int" && rhs.varName != "" {
result.fn = makeValueIntFilter(result.src, operand.varName, e.Op, rhs.varName)
return result
}
if operand.path == "Text" && rhsValue != nil {
result.fn = makeTextConstFilter(result.src, operand.varName, e.Op, rhsValue)
return result
}
if operand.path == "Text" && rhs.path == "Text" && rhs.varName != "" {
result.fn = makeTextFilter(result.src, operand.varName, e.Op, rhs.varName)
return result
}
}
panic(p.errorf(e, fmt.Errorf("unsupported binary op: %s", result.src)))
}
operand := p.toFilterOperand(e)
args := operand.args
switch operand.path {
default:
panic(p.errorf(e, fmt.Errorf("unsupported expr: %s", result.src)))
case "File.Imports":
pkgPath := p.parseStringArg(args[0])
result.fn = makeFileImportsFilter(result.src, pkgPath)
case "File.PkgPath.Matches":
re := p.parseRegexpArg(args[0])
result.fn = makeFilePkgPathMatchesFilter(result.src, re)
case "File.Name.Matches":
re := p.parseRegexpArg(args[0])
result.fn = makeFileNameMatchesFilter(result.src, re)
case "Pure":
result.fn = makePureFilter(result.src, operand.varName)
case "Const":
result.fn = makeConstFilter(result.src, operand.varName)
case "Addressable":
result.fn = makeAddressableFilter(result.src, operand.varName)
case "Filter":
expr, fn := goutil.ResolveFunc(p.types, args[0])
if expr != nil {
panic(p.errorf(expr, errors.New("expected a simple function name, found expression")))
}
sig := fn.Type().(*types.Signature)
userFn := p.state.env.GetFunc(fn.Pkg().Path(), fn.Name())
if userFn == nil {
panic(p.errorf(args[0], fmt.Errorf("can't find a compiled version of %s", sig.String())))
}
result.fn = makeCustomVarFilter(result.src, operand.varName, userFn)
case "Type.Is", "Type.Underlying.Is":
// TODO(quasilyte): add FQN support?
typeString, ok := p.toStringValue(args[0])
if !ok {
panic(p.errorf(args[0], errors.New("expected a string literal argument")))
}
ctx := typematch.Context{Itab: p.itab}
pat, err := typematch.Parse(&ctx, typeString)
if err != nil {
panic(p.errorf(args[0], fmt.Errorf("parse type expr: %w", err)))
}
underlying := operand.path == "Type.Underlying.Is"
result.fn = makeTypeIsFilter(result.src, operand.varName, underlying, pat)
case "Type.ConvertibleTo":
dstType := p.parseTypeStringArg(args[0])
result.fn = makeTypeConvertibleToFilter(result.src, operand.varName, dstType)
case "Type.AssignableTo":
dstType := p.parseTypeStringArg(args[0])
result.fn = makeTypeAssignableToFilter(result.src, operand.varName, dstType)
case "Type.Implements":
iface := p.toInterfaceValue(args[0])
result.fn = makeTypeImplementsFilter(result.src, operand.varName, iface)
case "Text.Matches":
re := p.parseRegexpArg(args[0])
result.fn = makeTextMatchesFilter(result.src, operand.varName, re)
case "Node.Is":
typeString, ok := p.toStringValue(args[0])
if !ok {
panic(p.errorf(args[0], errors.New("expected a string literal argument")))
}
tag := nodetag.FromString(typeString)
if tag == nodetag.Unknown {
panic(p.errorf(args[0], fmt.Errorf("%s is not a valid go/ast type name", typeString)))
}
result.fn = makeNodeIsFilter(result.src, operand.varName, tag)
}
if result.fn == nil {
panic("bug: nil func for the filter") // Should never happen
}
return result
}
func (p *rulesParser) toInterfaceValue(x ast.Node) *types.Interface {
typeString, ok := p.toStringValue(x)
if !ok {
panic(p.errorf(x, errors.New("expected a string literal argument")))
}
typ, err := p.state.FindType(p.importer, p.pkg, typeString)
if err == nil {
iface, ok := typ.Underlying().(*types.Interface)
if !ok {
panic(p.errorf(x, fmt.Errorf("%s is not an interface type", typeString)))
}
return iface
}
n, err := parser.ParseExpr(typeString)
if err != nil {
panic(p.errorf(x, fmt.Errorf("parse type expr: %w", err)))
}
qn, ok := n.(*ast.SelectorExpr)
if !ok {
panic(p.errorf(x, fmt.Errorf("can't resolve %s type; try a fully-qualified name", typeString)))
}
pkgName, ok := qn.X.(*ast.Ident)
if !ok {
panic(p.errorf(qn.X, errors.New("invalid package name")))
}
pkgPath, ok := p.itab.Lookup(pkgName.Name)
if !ok {
panic(p.errorf(qn.X, fmt.Errorf("package %s is not imported", pkgName.Name)))
}
pkg, err := p.importer.Import(pkgPath)
if err != nil {
panic(p.errorf(n, &ImportError{msg: fmt.Sprintf("can't load %s", pkgPath), err: err}))
}
obj := pkg.Scope().Lookup(qn.Sel.Name)
if obj == nil {
panic(p.errorf(n, fmt.Errorf("%s is not found in %s", qn.Sel.Name, pkgPath)))
}
iface, ok := obj.Type().Underlying().(*types.Interface)
if !ok {
panic(p.errorf(n, fmt.Errorf("%s is not an interface type", qn.Sel.Name)))
}
return iface
}
func (p *rulesParser) toStringValue(x ast.Node) (string, bool) {
switch x := x.(type) {
case *ast.BasicLit:
if x.Kind != token.STRING {
return "", false
}
s, err := strconv.Unquote(x.Value)
if err != nil {
return "", false
}
return s, true
case ast.Expr:
typ, ok := p.types.Types[x]
if !ok || typ.Type.String() != "string" {
return "", false
}
str := typ.Value.ExactString()
str = str[1 : len(str)-1] // remove quotes
return str, true
}
return "", false
}
func (p *rulesParser) toFilterOperand(e ast.Expr) filterOperand {
var o filterOperand
if call, ok := e.(*ast.CallExpr); ok {
o.args = call.Args
e = call.Fun
}
var path string
for {
if call, ok := e.(*ast.CallExpr); ok {
e = call.Fun
continue
}
selector, ok := e.(*ast.SelectorExpr)
if !ok {
break
}
if path == "" {
path = selector.Sel.Name
} else {
path = selector.Sel.Name + "." + path
}
e = selector.X
}
o.path = path
indexing, ok := e.(*ast.IndexExpr)
if !ok {
return o
}
mapIdent, ok := indexing.X.(*ast.Ident)
if !ok {
return o
}
o.mapName = mapIdent.Name
indexString, _ := p.toStringValue(indexing.Index)
o.varName = indexString
return o
}
type filterOperand struct {
mapName string
varName string
path string
args []ast.Expr
}
var stdlibPackages = map[string]string{
"adler32": "hash/adler32",
"aes": "crypto/aes",
"ascii85": "encoding/ascii85",
"asn1": "encoding/asn1",
"ast": "go/ast",
"atomic": "sync/atomic",
"base32": "encoding/base32",
"base64": "encoding/base64",
"big": "math/big",
"binary": "encoding/binary",
"bits": "math/bits",
"bufio": "bufio",
"build": "go/build",
"bytes": "bytes",
"bzip2": "compress/bzip2",
"cgi": "net/http/cgi",
"cgo": "runtime/cgo",
"cipher": "crypto/cipher",
"cmplx": "math/cmplx",
"color": "image/color",
"constant": "go/constant",
"context": "context",
"cookiejar": "net/http/cookiejar",
"crc32": "hash/crc32",
"crc64": "hash/crc64",
"crypto": "crypto",
"csv": "encoding/csv",
"debug": "runtime/debug",
"des": "crypto/des",
"doc": "go/doc",
"draw": "image/draw",
"driver": "database/sql/driver",
"dsa": "crypto/dsa",
"dwarf": "debug/dwarf",
"ecdsa": "crypto/ecdsa",
"ed25519": "crypto/ed25519",
"elf": "debug/elf",
"elliptic": "crypto/elliptic",
"encoding": "encoding",
"errors": "errors",
"exec": "os/exec",
"expvar": "expvar",
"fcgi": "net/http/fcgi",
"filepath": "path/filepath",
"flag": "flag",
"flate": "compress/flate",
"fmt": "fmt",
"fnv": "hash/fnv",
"format": "go/format",
"gif": "image/gif",
"gob": "encoding/gob",
"gosym": "debug/gosym",
"gzip": "compress/gzip",
"hash": "hash",
"heap": "container/heap",
"hex": "encoding/hex",
"hmac": "crypto/hmac",
"html": "html",
"http": "net/http",
"httptest": "net/http/httptest",
"httptrace": "net/http/httptrace",
"httputil": "net/http/httputil",
"image": "image",
"importer": "go/importer",
"io": "io",
"iotest": "testing/iotest",
"ioutil": "io/ioutil",
"jpeg": "image/jpeg",
"json": "encoding/json",
"jsonrpc": "net/rpc/jsonrpc",
"list": "container/list",
"log": "log",
"lzw": "compress/lzw",
"macho": "debug/macho",
"mail": "net/mail",
"math": "math",
"md5": "crypto/md5",
"mime": "mime",
"multipart": "mime/multipart",
"net": "net",
"os": "os",
"palette": "image/color/palette",
"parse": "text/template/parse",
"parser": "go/parser",
"path": "path",
"pe": "debug/pe",
"pem": "encoding/pem",
"pkix": "crypto/x509/pkix",
"plan9obj": "debug/plan9obj",
"plugin": "plugin",
"png": "image/png",
"pprof": "runtime/pprof",
"printer": "go/printer",
"quick": "testing/quick",
"quotedprintable": "mime/quotedprintable",
"race": "runtime/race",
"rand": "math/rand",
"rc4": "crypto/rc4",
"reflect": "reflect",
"regexp": "regexp",
"ring": "container/ring",
"rpc": "net/rpc",
"rsa": "crypto/rsa",
"runtime": "runtime",
"scanner": "text/scanner",
"sha1": "crypto/sha1",
"sha256": "crypto/sha256",
"sha512": "crypto/sha512",
"signal": "os/signal",
"smtp": "net/smtp",
"sort": "sort",
"sql": "database/sql",
"strconv": "strconv",
"strings": "strings",
"subtle": "crypto/subtle",
"suffixarray": "index/suffixarray",
"sync": "sync",
"syntax": "regexp/syntax",
"syscall": "syscall",
"syslog": "log/syslog",
"tabwriter": "text/tabwriter",
"tar": "archive/tar",
"template": "text/template",
"testing": "testing",
"textproto": "net/textproto",
"time": "time",
"tls": "crypto/tls",
"token": "go/token",
"trace": "runtime/trace",
"types": "go/types",
"unicode": "unicode",
"unsafe": "unsafe",
"url": "net/url",
"user": "os/user",
"utf16": "unicode/utf16",
"utf8": "unicode/utf8",
"x509": "crypto/x509",
"xml": "encoding/xml",
"zip": "archive/zip",
"zlib": "compress/zlib",
}