diff --git a/endtoend_test.go b/endtoend_test.go index 3397c06..ff060d7 100644 --- a/endtoend_test.go +++ b/endtoend_test.go @@ -111,8 +111,15 @@ func copy(to, from string) error { // run runs a single command and returns an error if it does not succeed. // os/exec should have this function, to be honest. func run(name string, arg ...string) error { + return runInDir(".", name, arg...) +} + +// runInDir runs a single command in directory dir and returns an error if +// it does not succeed. +func runInDir(dir, name string, arg ...string) error { cmd := exec.Command(name, arg...) + cmd.Dir = dir cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd.Run() -} +} \ No newline at end of file diff --git a/go.mod b/go.mod index c586c74..670d4a2 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module github.com/alvaroloes/enumer go 1.12 -require github.com/pascaldekloe/name v0.0.0-20180628100202-0fd16699aae1 +require ( + github.com/pascaldekloe/name v0.0.0-20180628100202-0fd16699aae1 + golang.org/x/tools v0.0.0-20190524210228-3d17549cdc6b +) diff --git a/go.sum b/go.sum index e21b462..0d37513 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,9 @@ github.com/pascaldekloe/name v0.0.0-20180628100202-0fd16699aae1 h1:/I3lTljEEDNYLho3/FUB7iD/oc2cEFgVmbHzV+O0PtU= github.com/pascaldekloe/name v0.0.0-20180628100202-0fd16699aae1/go.mod h1:eD5JxqMiuNYyFNmyY9rkJ/slN8y59oEu4Ei7F8OoKWQ= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190524210228-3d17549cdc6b h1:iEAPfYPbYbxG/2lNN4cMOHkmgKNsCuUwkxlDCK46UlU= +golang.org/x/tools v0.0.0-20190524210228-3d17549cdc6b/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= diff --git a/golden_test.go b/golden_test.go index 0a81aad..8144fc6 100644 --- a/golden_test.go +++ b/golden_test.go @@ -10,6 +10,9 @@ package main import ( + "io/ioutil" + "os" + "path/filepath" "strings" "testing" ) @@ -1050,7 +1053,24 @@ func runGoldenTest(t *testing.T, test Golden, generateJSON, generateYAML, genera var g Generator input := "package test\n" + test.input file := test.name + ".go" - g.parsePackage(".", []string{file}, input) + + dir, err := ioutil.TempDir("", "stringer") + if err != nil { + t.Error(err) + } + defer func() { + err = os.RemoveAll(dir) + if err != nil { + t.Error(err) + } + }() + + absFile := filepath.Join(dir, file) + err = ioutil.WriteFile(absFile, []byte(input), 0644) + if err != nil { + t.Error(err) + } + g.parsePackage([]string{absFile}) // Extract the name and type of the constant from the first line. tokens := strings.SplitN(test.input, " ", 3) if len(tokens) != 3 { diff --git a/stringer.go b/stringer.go index 593e071..17c670b 100644 --- a/stringer.go +++ b/stringer.go @@ -15,13 +15,12 @@ import ( "flag" "fmt" "go/ast" - "go/build" exact "go/constant" "go/format" "go/importer" - "go/parser" "go/token" "go/types" + "golang.org/x/tools/go/packages" "io/ioutil" "log" "os" @@ -97,12 +96,12 @@ func main() { if len(args) == 1 && isDirectory(args[0]) { dir = args[0] - g.parsePackageDir(args[0]) } else { dir = filepath.Dir(args[0]) - g.parsePackageFiles(args) } + g.parsePackage(args) + // Print the header and package clause. g.Printf("// Code generated by \"enumer %s\"; DO NOT EDIT.\n", strings.Join(os.Args[1:], " ")) g.Printf("\n") @@ -194,70 +193,105 @@ type Package struct { typesPkg *types.Package } -// parsePackageDir parses the package residing in the directory. -func (g *Generator) parsePackageDir(directory string) { - pkg, err := build.Default.ImportDir(directory, 0) +//// parsePackageDir parses the package residing in the directory. +//func (g *Generator) parsePackageDir(directory string) { +// pkg, err := build.Default.ImportDir(directory, 0) +// if err != nil { +// log.Fatalf("cannot process directory %s: %s", directory, err) +// } +// var names []string +// names = append(names, pkg.GoFiles...) +// names = append(names, pkg.CgoFiles...) +// // TODO: Need to think about constants in test files. Maybe write type_string_test.go +// // in a separate pass? For later. +// // names = append(names, pkg.TestGoFiles...) // These are also in the "foo" package. +// names = append(names, pkg.SFiles...) +// names = prefixDirectory(directory, names) +// g.parsePackage(directory, names, nil) +//} +// +//// parsePackageFiles parses the package occupying the named files. +//func (g *Generator) parsePackageFiles(names []string) { +// g.parsePackage(".", names, nil) +//} +// +//// prefixDirectory places the directory name on the beginning of each name in the list. +//func prefixDirectory(directory string, names []string) []string { +// if directory == "." { +// return names +// } +// ret := make([]string, len(names)) +// for i, name := range names { +// ret[i] = filepath.Join(directory, name) +// } +// return ret +//} + +//// parsePackage analyzes the single package constructed from the named files. +//// If text is non-nil, it is a string to be used instead of the content of the file, +//// to be used for testing. parsePackage exits if there is an error. +//func (g *Generator) parsePackage(directory string, names []string, text interface{}) { +// var files []*File +// var astFiles []*ast.File +// g.pkg = new(Package) +// fs := token.NewFileSet() +// for _, name := range names { +// if !strings.HasSuffix(name, ".go") { +// continue +// } +// parsedFile, err := parser.ParseFile(fs, name, text, 0) +// if err != nil { +// log.Fatalf("parsing package: %s: %s", name, err) +// } +// astFiles = append(astFiles, parsedFile) +// files = append(files, &File{ +// file: parsedFile, +// pkg: g.pkg, +// }) +// } +// if len(astFiles) == 0 { +// log.Fatalf("%s: no buildable Go files", directory) +// } +// g.pkg.name = astFiles[0].Name.Name +// g.pkg.files = files +// g.pkg.dir = directory +// // Type check the package. +// g.pkg.check(fs, astFiles) +//} + +// parsePackage analyzes the single package constructed from the patterns and tags. +// parsePackage exits if there is an error. +func (g *Generator) parsePackage(patterns []string) { + cfg := &packages.Config{ + Mode: packages.LoadSyntax, + // TODO: Need to think about constants in test files. Maybe write type_string_test.go + // in a separate pass? For later. + Tests: false, + } + pkgs, err := packages.Load(cfg, patterns...) if err != nil { - log.Fatalf("cannot process directory %s: %s", directory, err) + log.Fatal(err) } - var names []string - names = append(names, pkg.GoFiles...) - names = append(names, pkg.CgoFiles...) - // TODO: Need to think about constants in test files. Maybe write type_string_test.go - // in a separate pass? For later. - // names = append(names, pkg.TestGoFiles...) // These are also in the "foo" package. - names = append(names, pkg.SFiles...) - names = prefixDirectory(directory, names) - g.parsePackage(directory, names, nil) + if len(pkgs) != 1 { + log.Fatalf("error: %d packages found", len(pkgs)) + } + g.addPackage(pkgs[0]) } -// parsePackageFiles parses the package occupying the named files. -func (g *Generator) parsePackageFiles(names []string) { - g.parsePackage(".", names, nil) -} - -// prefixDirectory places the directory name on the beginning of each name in the list. -func prefixDirectory(directory string, names []string) []string { - if directory == "." { - return names +// addPackage adds a type checked Package and its syntax files to the generator. +func (g *Generator) addPackage(pkg *packages.Package) { + g.pkg = &Package{ + name: pkg.Name, + defs: pkg.TypesInfo.Defs, + files: make([]*File, len(pkg.Syntax)), } - ret := make([]string, len(names)) - for i, name := range names { - ret[i] = filepath.Join(directory, name) - } - return ret -} -// parsePackage analyzes the single package constructed from the named files. -// If text is non-nil, it is a string to be used instead of the content of the file, -// to be used for testing. parsePackage exits if there is an error. -func (g *Generator) parsePackage(directory string, names []string, text interface{}) { - var files []*File - var astFiles []*ast.File - g.pkg = new(Package) - fs := token.NewFileSet() - for _, name := range names { - if !strings.HasSuffix(name, ".go") { - continue - } - parsedFile, err := parser.ParseFile(fs, name, text, 0) - if err != nil { - log.Fatalf("parsing package: %s: %s", name, err) - } - astFiles = append(astFiles, parsedFile) - files = append(files, &File{ - file: parsedFile, + for i, file := range pkg.Syntax { + g.pkg.files[i] = &File{ + file: file, pkg: g.pkg, - }) + } } - if len(astFiles) == 0 { - log.Fatalf("%s: no buildable Go files", directory) - } - g.pkg.name = astFiles[0].Name.Name - g.pkg.files = files - g.pkg.dir = directory - // Type check the package. - g.pkg.check(fs, astFiles) } // check type-checks the package. The package must be OK to proceed.