diff --git a/.gitignore b/.gitignore index c225901..af016df 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,4 @@ _testmain.go *.exe *.test *.prof +*.DS_Store \ No newline at end of file diff --git a/cli/xgotext/parser/dir/golang.go b/cli/xgotext/parser/dir/golang.go index f82e415..6e7c2d8 100644 --- a/cli/xgotext/parser/dir/golang.go +++ b/cli/xgotext/parser/dir/golang.go @@ -1,12 +1,9 @@ package dir import ( - "fmt" "go/ast" "go/token" - "go/types" "log" - "path/filepath" "strconv" "golang.org/x/tools/go/packages" @@ -14,31 +11,6 @@ import ( "github.com/leonelquinteros/gotext/cli/xgotext/parser" ) -// GetterDef describes a getter -type GetterDef struct { - Id int - Plural int - Context int - Domain int -} - -// maxArgIndex returns the largest argument index -func (d *GetterDef) maxArgIndex() int { - return max(d.Id, d.Plural, d.Context, d.Domain) -} - -// list of supported getter -var gotextGetter = map[string]GetterDef{ - "Get": {0, -1, -1, -1}, - "GetN": {0, 1, -1, -1}, - "GetD": {1, -1, -1, 0}, - "GetND": {1, 2, -1, 0}, - "GetC": {0, -1, 1, -1}, - "GetNC": {0, 1, 3, -1}, - "GetDC": {1, -1, 2, 0}, - "GetNDC": {1, 2, 4, 0}, -} - // register go parser func init() { AddParser(goParser) @@ -72,37 +44,32 @@ func goParser(dirPath, basePath string, data *parser.DomainMap) error { // handle each file for _, node := range pkgs[0].Syntax { file := GoFile{ - pkgConf: &conf, - filePath: fileSet.Position(node.Package).Filename, - basePath: basePath, - data: data, - fileSet: fileSet, - - importedPackages: map[string]*packages.Package{ - pkgs[0].Name: pkgs[0], + parser.GoFile{ + PkgConf: &conf, + FilePath: fileSet.Position(node.Package).Filename, + BasePath: basePath, + Data: data, + FileSet: fileSet, + + ImportedPackages: map[string]*packages.Package{ + pkgs[0].Name: pkgs[0], + }, }, } - ast.Inspect(node, file.inspectFile) + ast.Inspect(node, file.InspectFile) } return nil } // GoFile handles the parsing of one go file type GoFile struct { - filePath string - basePath string - data *parser.DomainMap - - fileSet *token.FileSet - pkgConf *packages.Config - - importedPackages map[string]*packages.Package + parser.GoFile } // getPackage loads module by name -func (g *GoFile) getPackage(name string) (*packages.Package, error) { - pkgs, err := packages.Load(g.pkgConf, name) +func (g *GoFile) GetPackage(name string) (*packages.Package, error) { + pkgs, err := packages.Load(g.PkgConf, name) if err != nil { return nil, err } @@ -112,36 +79,26 @@ func (g *GoFile) getPackage(name string) (*packages.Package, error) { return pkgs[0], nil } -// getType from ident object -func (g *GoFile) getType(ident *ast.Ident) types.Object { - for _, pkg := range g.importedPackages { - if obj, ok := pkg.TypesInfo.Uses[ident]; ok { - return obj - } - } - return nil -} - -func (g *GoFile) inspectFile(n ast.Node) bool { +func (g *GoFile) InspectFile(n ast.Node) bool { switch x := n.(type) { // get names of imported packages case *ast.ImportSpec: packageName, _ := strconv.Unquote(x.Path.Value) - pkg, err := g.getPackage(packageName) + pkg, err := g.GetPackage(packageName) if err != nil { log.Printf("failed to load package %s: %s", packageName, err) } else { if x.Name == nil { - g.importedPackages[pkg.Name] = pkg + g.ImportedPackages[pkg.Name] = pkg } else { - g.importedPackages[x.Name.Name] = pkg + g.ImportedPackages[x.Name.Name] = pkg } } // check each function call case *ast.CallExpr: - g.inspectCallExpr(x) + g.InspectCallExpr(x) default: print() @@ -149,121 +106,3 @@ func (g *GoFile) inspectFile(n ast.Node) bool { return true } - -// checkType for gotext object -func (g *GoFile) checkType(rawType types.Type) bool { - switch t := rawType.(type) { - case *types.Pointer: - return g.checkType(t.Elem()) - - case *types.Named: - if t.Obj().Pkg() == nil || t.Obj().Pkg().Path() != "github.com/leonelquinteros/gotext" { - return false - } - - case *types.Alias: - return g.checkType(t.Rhs()) - - default: - return false - } - return true -} - -func (g *GoFile) inspectCallExpr(n *ast.CallExpr) { - // must be a selector expression otherwise it is a local function call - expr, ok := n.Fun.(*ast.SelectorExpr) - if !ok { - return - } - - switch e := expr.X.(type) { - // direct call - case *ast.Ident: - // object is a package if the Obj is not set - if e.Obj == nil { - pkg, ok := g.importedPackages[e.Name] - if !ok || pkg.PkgPath != "github.com/leonelquinteros/gotext" { - return - } - - } else { - // validate type of object - t := g.getType(e) - if t == nil || !g.checkType(t.Type()) { - return - } - } - - // call to attribute - case *ast.SelectorExpr: - // validate type of object - t := g.getType(e.Sel) - if t == nil || !g.checkType(t.Type()) { - return - } - - default: - return - } - - // convert args - args := make([]*ast.BasicLit, len(n.Args)) - for idx, arg := range n.Args { - args[idx], _ = arg.(*ast.BasicLit) - } - - // get position - path, _ := filepath.Rel(g.basePath, g.filePath) - position := fmt.Sprintf("%s:%d", path, g.fileSet.Position(n.Lparen).Line) - - // handle getters - if def, ok := gotextGetter[expr.Sel.String()]; ok { - g.parseGetter(def, args, position) - return - } -} - -func (g *GoFile) parseGetter(def GetterDef, args []*ast.BasicLit, pos string) { - // check if enough arguments are given - if len(args) < def.maxArgIndex() { - return - } - - // get domain - var domain string - if def.Domain != -1 { - domain, _ = strconv.Unquote(args[def.Domain].Value) - } - - // only handle function calls with strings as ID - if args[def.Id] == nil || args[def.Id].Kind != token.STRING { - log.Printf("ERR: Unsupported call at %s (ID not a string)", pos) - return - } - - msgID, _ := strconv.Unquote(args[def.Id].Value) - trans := parser.Translation{ - MsgId: msgID, - SourceLocations: []string{pos}, - } - if def.Plural > 0 { - // plural ID must be a string - if args[def.Plural] == nil || args[def.Plural].Kind != token.STRING { - log.Printf("ERR: Unsupported call at %s (Plural not a string)", pos) - return - } - msgIDPlural, _ := strconv.Unquote(args[def.Plural].Value) - trans.MsgIdPlural = msgIDPlural - } - if def.Context > 0 { - // Context must be a string - if args[def.Context] == nil || args[def.Context].Kind != token.STRING { - log.Printf("ERR: Unsupported call at %s (Context not a string)", pos) - return - } - trans.Context = args[def.Context].Value - } - - g.data.AddTranslation(domain, &trans) -} diff --git a/cli/xgotext/parser/gofile.go b/cli/xgotext/parser/gofile.go new file mode 100644 index 0000000..477fbee --- /dev/null +++ b/cli/xgotext/parser/gofile.go @@ -0,0 +1,181 @@ +package parser + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "log" + "path/filepath" + "strconv" + + "golang.org/x/tools/go/packages" +) + +// GetterDef describes a getter +type GetterDef struct { + Id int + Plural int + Context int + Domain int +} + +// maxArgIndex returns the largest argument index +func (d *GetterDef) MaxArgIndex() int { + return max(d.Id, d.Plural, d.Context, d.Domain) +} + +// list of supported getter +var gotextGetter = map[string]GetterDef{ + "Get": {0, -1, -1, -1}, + "GetN": {0, 1, -1, -1}, + "GetD": {1, -1, -1, 0}, + "GetND": {1, 2, -1, 0}, + "GetC": {0, -1, 1, -1}, + "GetNC": {0, 1, 3, -1}, + "GetDC": {1, -1, 2, 0}, + "GetNDC": {1, 2, 4, 0}, +} + +// GoFile handles the parsing of one go file +type GoFile struct { + FilePath string + BasePath string + Data *DomainMap + + FileSet *token.FileSet + PkgConf *packages.Config + + ImportedPackages map[string]*packages.Package +} + +// GetType from ident object +func (g *GoFile) GetType(ident *ast.Ident) types.Object { + for _, pkg := range g.ImportedPackages { + if pkg.Types == nil { + continue + } + if obj, ok := pkg.TypesInfo.Uses[ident]; ok { + return obj + } + } + return nil +} + +// checkType for gotext object +func (g *GoFile) CheckType(rawType types.Type) bool { + switch t := rawType.(type) { + case *types.Pointer: + return g.CheckType(t.Elem()) + + case *types.Named: + if t.Obj().Pkg() == nil || t.Obj().Pkg().Path() != "github.com/leonelquinteros/gotext" { + return false + } + + case *types.Alias: + return g.CheckType(t.Rhs()) + + default: + return false + } + return true +} + +func (g *GoFile) InspectCallExpr(n *ast.CallExpr) { + // must be a selector expression otherwise it is a local function call + expr, ok := n.Fun.(*ast.SelectorExpr) + if !ok { + return + } + + switch e := expr.X.(type) { + // direct call + case *ast.Ident: + // object is a package if the Obj is not set + if e.Obj == nil { + pkg, ok := g.ImportedPackages[e.Name] + if !ok || pkg.PkgPath != "github.com/leonelquinteros/gotext" { + return + } + + } else { + // validate type of object + t := g.GetType(e) + if t == nil || !g.CheckType(t.Type()) { + return + } + } + + // call to attribute + case *ast.SelectorExpr: + // validate type of object + t := g.GetType(e.Sel) + if t == nil || !g.CheckType(t.Type()) { + return + } + + default: + return + } + + // convert args + args := make([]*ast.BasicLit, len(n.Args)) + for idx, arg := range n.Args { + args[idx], _ = arg.(*ast.BasicLit) + } + + // get position + path, _ := filepath.Rel(g.BasePath, g.FilePath) + position := fmt.Sprintf("%s:%d", path, g.FileSet.Position(n.Lparen).Line) + + // handle getters + if def, ok := gotextGetter[expr.Sel.String()]; ok { + g.ParseGetter(def, args, position) + return + } +} + +func (g *GoFile) ParseGetter(def GetterDef, args []*ast.BasicLit, pos string) { + // check if enough arguments are given + if len(args) < def.MaxArgIndex() { + return + } + + // get domain + var domain string + if def.Domain != -1 { + domain, _ = strconv.Unquote(args[def.Domain].Value) + } + + // only handle function calls with strings as ID + if args[def.Id] == nil || args[def.Id].Kind != token.STRING { + log.Printf("ERR: Unsupported call at %s (ID not a string)", pos) + return + } + + msgID, _ := strconv.Unquote(args[def.Id].Value) + trans := Translation{ + MsgId: msgID, + SourceLocations: []string{pos}, + } + if def.Plural > 0 { + // plural ID must be a string + if args[def.Plural] == nil || args[def.Plural].Kind != token.STRING { + log.Printf("ERR: Unsupported call at %s (Plural not a string)", pos) + return + } + msgIDPlural, _ := strconv.Unquote(args[def.Plural].Value) + trans.MsgIdPlural = msgIDPlural + } + if def.Context > 0 { + // Context must be a string + if args[def.Context] == nil || args[def.Context].Kind != token.STRING { + log.Printf("ERR: Unsupported call at %s (Context not a string)", pos) + return + } + trans.Context = args[def.Context].Value + } + + g.Data.AddTranslation(domain, &trans) +} diff --git a/cli/xgotext/parser/pkg-tree/golang.go b/cli/xgotext/parser/pkg-tree/golang.go index 54afe45..1a1d258 100644 --- a/cli/xgotext/parser/pkg-tree/golang.go +++ b/cli/xgotext/parser/pkg-tree/golang.go @@ -4,54 +4,15 @@ import ( "fmt" "go/ast" "go/token" - "go/types" "log" "os" - "path/filepath" "strconv" - "strings" "golang.org/x/tools/go/packages" "github.com/leonelquinteros/gotext/cli/xgotext/parser" ) -const gotextPkgPath = "github.com/leonelquinteros/gotext" - -type GetterDef struct { - Id int - Plural int - Context int - Domain int -} - -// MaxArgIndex returns the largest argument index -func (d *GetterDef) maxArgIndex() int { - m := d.Id - if d.Plural > m { - m = d.Plural - } - if d.Context > m { - m = d.Context - } - if d.Domain > m { - m = d.Domain - } - return m -} - -// list of supported getter -var gotextGetter = map[string]GetterDef{ - "Get": {0, -1, -1, -1}, - "GetN": {0, 1, -1, -1}, - "GetD": {1, -1, -1, 0}, - "GetND": {1, 2, -1, 0}, - "GetC": {0, -1, 1, -1}, - "GetNC": {0, 1, 3, -1}, - "GetDC": {1, -1, 2, 0}, - "GetNDC": {1, 2, 4, 0}, -} - // ParsePkgTree parse go package tree func ParsePkgTree(pkgPath string, data *parser.DomainMap, verbose bool) error { basePath, err := os.Getwd() @@ -73,17 +34,19 @@ func pkgParser(dirPath, basePath string, data *parser.DomainMap, verbose bool) e } for _, node := range pkg.Syntax { file := GoFile{ - filePath: pkg.Fset.Position(node.Package).Filename, - basePath: basePath, - data: data, - fileSet: pkg.Fset, - - importedPackages: map[string]*packages.Package{ - pkg.Name: pkg, + parser.GoFile{ + FilePath: pkg.Fset.Position(node.Package).Filename, + BasePath: basePath, + Data: data, + FileSet: pkg.Fset, + + ImportedPackages: map[string]*packages.Package{ + pkg.Name: pkg, + }, }, } - ast.Inspect(node, file.inspectFile) + ast.Inspect(node, file.InspectFile) } } @@ -113,14 +76,6 @@ func loadPackage(name string) (*packages.Package, error) { return pkgs[0], nil } -func getPkgPath(pkg *packages.Package) string { - if len(pkg.GoFiles) == 0 { - return pkg.PkgPath - } - pkgPath, _ := filepath.Split(pkg.GoFiles[0]) - return strings.TrimRight(pkgPath, "/") -} - func filterPkgs(pkg *packages.Package) []*packages.Package { result := filterPkgsRec(pkg) return result @@ -130,7 +85,7 @@ func filterPkgsRec(pkg *packages.Package) []*packages.Package { result := make([]*packages.Package, 0, 100) pkgCache[pkg.ID] = pkg for _, importedPkg := range pkg.Imports { - if importedPkg.ID == gotextPkgPath { + if importedPkg.ID == "github.com/leonelquinteros/gotext" { result = append(result, pkg) } if _, ok := pkgCache[importedPkg.ID]; ok { @@ -143,18 +98,11 @@ func filterPkgsRec(pkg *packages.Package) []*packages.Package { // GoFile handles the parsing of one go file type GoFile struct { - filePath string - basePath string - data *parser.DomainMap - - fileSet *token.FileSet - pkgConf *packages.Config - - importedPackages map[string]*packages.Package + parser.GoFile } -// getPackage loads module by name -func (g *GoFile) getPackage(name string) (*packages.Package, error) { +// GetPackage loads module by name +func (g *GoFile) GetPackage(name string) (*packages.Package, error) { pkg, ok := pkgCache[name] if !ok { return nil, fmt.Errorf("not found in cache") @@ -162,39 +110,26 @@ func (g *GoFile) getPackage(name string) (*packages.Package, error) { return pkg, nil } -// getType from ident object -func (g *GoFile) getType(ident *ast.Ident) types.Object { - for _, pkg := range g.importedPackages { - if pkg.Types == nil { - continue - } - if obj, ok := pkg.TypesInfo.Uses[ident]; ok { - return obj - } - } - return nil -} - -func (g *GoFile) inspectFile(n ast.Node) bool { +func (g *GoFile) InspectFile(n ast.Node) bool { switch x := n.(type) { // get names of imported packages case *ast.ImportSpec: packageName, _ := strconv.Unquote(x.Path.Value) - pkg, err := g.getPackage(packageName) + pkg, err := g.GetPackage(packageName) if err != nil { log.Printf("failed to load package %s: %s", packageName, err) } else { if x.Name == nil { - g.importedPackages[pkg.Name] = pkg + g.ImportedPackages[pkg.Name] = pkg } else { - g.importedPackages[x.Name.Name] = pkg + g.ImportedPackages[x.Name.Name] = pkg } } // check each function call case *ast.CallExpr: - g.inspectCallExpr(x) + g.InspectCallExpr(x) default: print() @@ -202,115 +137,3 @@ func (g *GoFile) inspectFile(n ast.Node) bool { return true } - -// checkType for gotext object -func (g *GoFile) checkType(rawType types.Type) bool { - switch t := rawType.(type) { - case *types.Pointer: - return g.checkType(t.Elem()) - - case *types.Named: - if t.Obj().Pkg() == nil || t.Obj().Pkg().Path() != gotextPkgPath { - return false - } - - case *types.Alias: - return g.checkType(t.Rhs()) - - default: - return false - } - return true -} - -func (g *GoFile) inspectCallExpr(n *ast.CallExpr) { - // must be a selector expression otherwise it is a local function call - expr, ok := n.Fun.(*ast.SelectorExpr) - if !ok { - return - } - - switch e := expr.X.(type) { - // direct call - case *ast.Ident: - // object is a package if the Obj is not set - if e.Obj != nil { - // validate type of object - t := g.getType(e) - if t == nil || !g.checkType(t.Type()) { - return - } - } - - // call to attribute - case *ast.SelectorExpr: - // validate type of object - t := g.getType(e.Sel) - if t == nil || !g.checkType(t.Type()) { - return - } - - default: - return - } - - // convert args - args := make([]*ast.BasicLit, len(n.Args)) - for idx, arg := range n.Args { - args[idx], _ = arg.(*ast.BasicLit) - } - - // get position - path, _ := filepath.Rel(g.basePath, g.filePath) - position := fmt.Sprintf("%s:%d", path, g.fileSet.Position(n.Lparen).Line) - - // handle getters - if def, ok := gotextGetter[expr.Sel.String()]; ok { - g.parseGetter(def, args, position) - return - } -} - -func (g *GoFile) parseGetter(def GetterDef, args []*ast.BasicLit, pos string) { - // check if enough arguments are given - if len(args) < def.maxArgIndex() { - return - } - - // get domain - var domain string - if def.Domain != -1 { - domain, _ = strconv.Unquote(args[def.Domain].Value) - } - - // only handle function calls with strings as ID - if args[def.Id] == nil || args[def.Id].Kind != token.STRING { - log.Printf("ERR: Unsupported call at %s (ID not a string)", pos) - return - } - - msgID, _ := strconv.Unquote(args[def.Id].Value) - trans := parser.Translation{ - MsgId: msgID, - SourceLocations: []string{pos}, - } - if def.Plural > 0 { - // plural ID must be a string - if args[def.Plural] == nil || args[def.Plural].Kind != token.STRING { - log.Printf("ERR: Unsupported call at %s (Plural not a string)", pos) - return - } - msgIDPlural, _ := strconv.Unquote(args[def.Plural].Value) - trans.MsgIdPlural = msgIDPlural - } - if def.Context > 0 { - // Context must be a string - if args[def.Context] == nil || args[def.Context].Kind != token.STRING { - log.Printf("ERR: Unsupported call at %s (Context not a string)", pos) - return - } - trans.Context = args[def.Context].Value - } - - g.data.AddTranslation(domain, &trans) -}