jsonenums/parser/parser.go

148 lines
4.5 KiB
Go

// Copyright 2017 Google Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to writing, software distributed
// under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
// Package parser parses Go code and keeps track of all the types defined
// and provides access to all the constants defined for an int type.
package parser
import (
"fmt"
"go/ast"
"go/build"
"go/constant"
"go/token"
"go/types"
"log"
"strings"
"golang.org/x/tools/go/loader"
)
// A Package contains all the information related to a parsed package.
type Package struct {
Name string
files []*ast.File
defs map[*ast.Ident]types.Object
}
// ParsePackage parses the package in the given directory and returns it.
func ParsePackage(directory string) (*Package, error) {
p, err := build.ImportDir(directory, build.FindOnly)
if err != nil {
return nil, fmt.Errorf("provided directory (%s) may not under GOPATH (%s): %v",
directory, build.Default.GOPATH, err)
}
conf := loader.Config{TypeChecker: types.Config{FakeImportC: true}}
conf.Import(p.ImportPath)
program, err := conf.Load()
if err != nil {
return nil, fmt.Errorf("couldn't load package: %v", err)
}
pkgInfo := program.Package(p.ImportPath)
return &Package{
Name: pkgInfo.Pkg.Name(),
files: pkgInfo.Files,
defs: pkgInfo.Defs,
}, nil
}
// generate produces the String method for the named type.
func (pkg *Package) ValuesOfType(typeName string) ([]string, error) {
var values, inspectErrs []string
for _, file := range pkg.files {
ast.Inspect(file, func(node ast.Node) bool {
decl, ok := node.(*ast.GenDecl)
if !ok || decl.Tok != token.CONST {
// We only care about const declarations.
return true
}
if vs, err := pkg.valuesOfTypeIn(typeName, decl); err != nil {
inspectErrs = append(inspectErrs, err.Error())
} else {
values = append(values, vs...)
}
return false
})
}
if len(inspectErrs) > 0 {
return nil, fmt.Errorf("inspecting code:\n\t%v", strings.Join(inspectErrs, "\n\t"))
}
if len(values) == 0 {
return nil, fmt.Errorf("no values defined for type %s", typeName)
}
return values, nil
}
func (pkg *Package) valuesOfTypeIn(typeName string, decl *ast.GenDecl) ([]string, error) {
var values []string
// The name of the type of the constants we are declaring.
// Can change if this is a multi-element declaration.
typ := ""
// Loop over the elements of the declaration. Each element is a ValueSpec:
// a list of names possibly followed by a type, possibly followed by values.
// If the type and value are both missing, we carry down the type (and value,
// but the "go/types" package takes care of that).
for _, spec := range decl.Specs {
vspec := spec.(*ast.ValueSpec) // Guaranteed to succeed as this is CONST.
if vspec.Type == nil && len(vspec.Values) > 0 {
// "X = 1". With no type but a value, the constant is untyped.
// Skip this vspec and reset the remembered type.
typ = ""
continue
}
if vspec.Type != nil {
// "X T". We have a type. Remember it.
ident, ok := vspec.Type.(*ast.Ident)
if !ok {
continue
}
typ = ident.Name
}
if typ != typeName {
// This is not the type we're looking for.
continue
}
// We now have a list of names (from one line of source code) all being
// declared with the desired type.
// Grab their names and actual values and store them in f.values.
for _, name := range vspec.Names {
if name.Name == "_" {
continue
}
// This dance lets the type checker find the values for us. It's a
// bit tricky: look up the object declared by the name, find its
// types.Const, and extract its value.
obj, ok := pkg.defs[name]
if !ok {
return nil, fmt.Errorf("no value for constant %s", name)
}
info := obj.Type().Underlying().(*types.Basic).Info()
if info&types.IsInteger == 0 {
return nil, fmt.Errorf("can't handle non-integer constant type %s", typ)
}
value := obj.(*types.Const).Val() // Guaranteed to succeed as this is CONST.
if value.Kind() != constant.Int {
log.Fatalf("can't happen: constant is not an integer %s", name)
}
values = append(values, name.Name)
}
}
return values, nil
}