// FormatCode runs "goimports -w" on the source file. func (f *SourceFile) FormatCode() error { // Parse file into AST fset := token.NewFileSet() file, err := parser.ParseFile(fset, f.Abs(), nil, parser.ParseComments) if err != nil { content, _ := ioutil.ReadFile(f.Abs()) var buf bytes.Buffer scanner.PrintError(&buf, err) return fmt.Errorf("%s\n========\nContent:\n%s", buf.String(), content) } // Clean unused imports imports := astutil.Imports(fset, file) for _, group := range imports { for _, imp := range group { path := strings.Trim(imp.Path.Value, `"`) if !astutil.UsesImport(file, path) { if imp.Name != nil { astutil.DeleteNamedImport(fset, file, imp.Name.Name, path) } else { astutil.DeleteImport(fset, file, path) } } } } ast.SortImports(fset, file) // Open file to be written w, err := os.OpenFile(f.Abs(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm) if err != nil { return err } defer w.Close() // Write formatted code without unused imports return format.Node(w, fset, file) }
// FormatCode runs "goimports -w" on the source file. func (f *SourceFile) FormatCode() error { if NoFormat { return nil } // Parse file into AST fset := token.NewFileSet() file, err := parser.ParseFile(fset, f.Abs(), nil, parser.ParseComments) if err != nil { return err } // Clean unused imports imports := astutil.Imports(fset, file) for _, group := range imports { for _, imp := range group { path := strings.Trim(imp.Path.Value, `"`) if !astutil.UsesImport(file, path) { astutil.DeleteImport(fset, file, path) } } } ast.SortImports(fset, file) // Open file to be written w, err := os.OpenFile(f.Abs(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm) if err != nil { return err } defer w.Close() // Write formatted code without unused imports return format.Node(w, fset, file) }
// importPaths returns a list of import paths for the given go source file func importPaths(src string) ([]string, error) { fset := token.NewFileSet() f, err := parser.ParseFile(fset, src, nil, 0) if err != nil { return nil, err } imps := astutil.Imports(fset, f) paths := make([]string, len(imps[0])) for i, imp := range imps[0] { if path := imp.Path; path != nil { paths[i], _ = strconv.Unquote(path.Value) } } return paths, nil }
// Clean writes the clean source to io.Writer. The source can be a io.Reader, // string or []bytes func Clean(w io.Writer, src interface{}) error { fset := token.NewFileSet() file, err := parser.ParseFile(fset, "clean.go", src, parser.ParseComments) if err != nil { return err } // Clean unused imports imports := astutil.Imports(fset, file) for _, group := range imports { for _, imp := range group { path := strings.Trim(imp.Path.Value, `"`) if !astutil.UsesImport(file, path) { astutil.DeleteImport(fset, file, path) } } } ast.SortImports(fset, file) // Write formatted code without unused imports return format.Node(w, fset, file) }
func sortImports2(fset *token.FileSet, f *ast.File) (fset2 *token.FileSet, f2 *ast.File) { sortImports(fset, f) imps := astutil.Imports(fset, f) var spacesBefore []string // import paths we need spaces before for _, impSection := range imps { // Within each block of contiguous imports, see if any // import lines are in different group numbers. If so, // we'll need to put a space between them so it's // compatible with gofmt. lastGroup := -1 for _, importSpec := range impSection { importPath, _ := strconv.Unquote(importSpec.Path.Value) groupNum := importGroup(importPath) if groupNum != lastGroup && lastGroup != -1 { spacesBefore = append(spacesBefore, importPath) } lastGroup = groupNum } } // So gross. Print out the entire AST, add a space by modifying the bytes, and reparse the whole thing. // Because I don't see an API that'd let me modify (insert a newline) the FileSet directly. // We really need a go/ast v2, which is as friendly to parsing/printing/formatting as current, but // also friendly towards direct AST modification... To avoid all these horrible hacks. out := []byte(gist5639599.SprintAst(fset, f)) if len(spacesBefore) > 0 { out = addImportSpaces(bytes.NewReader(out), spacesBefore) } fset2 = token.NewFileSet() var err error f2, err = parser.ParseFile(fset2, "", out, parser.ParseComments) if err != nil { panic(err) } return }
// ProcessFileAST processes the files using golang's AST parser func ProcessFileAST(filePath string, from string, to string) { //Colors to be used on the console red := ansi.ColorCode("red+bh") white := ansi.ColorCode("white+bh") yellow := ansi.ColorCode("yellow+bh") blackOnWhite := ansi.ColorCode("black+b:white+h") //Reset the color reset := ansi.ColorCode("reset") fmt.Println(blackOnWhite+"Processing file", filePath, "in SAFE MODE", reset) // New FileSet to parse the go file to fSet := token.NewFileSet() // Parse the file file, err := parser.ParseFile(fSet, filePath, nil, 0) if err != nil { fmt.Println(err) } // Get the list of imports from the ast imports := astutil.Imports(fSet, file) // Keep track of number of changes numChanges := 0 // Iterate through the imports array for _, mPackage := range imports { for _, mImport := range mPackage { // Since astutil returns the path string with quotes, remove those importString := strings.TrimSuffix(strings.TrimPrefix(mImport.Path.Value, "\""), "\"") // If the path matches the oldpath, replace it with the new one if strings.Contains(importString, from) { //If it needs to be replaced, increase numChanges so we can write the file later numChanges++ // Join the path of the import package with the remainder from the old one after removing the old import package replacePackage := strings.Replace(importString, from, to, -1) fmt.Println(red + "Updating import " + reset + white + importString + reset + red + " to " + reset + white + replacePackage + reset) // Remove the old import and replace it with the replacement astutil.DeleteImport(fSet, file, importString) astutil.AddImport(fSet, file, replacePackage) } } } // If the number of changes are more than 0, write file if numChanges > 0 { // Print the new AST tree to a new output buffer var outputBuffer bytes.Buffer printer.Fprint(&outputBuffer, fSet, file) ioutil.WriteFile(filePath, outputBuffer.Bytes(), os.ModePerm) fmt.Println(yellow+ "File", filePath, "saved after", numChanges, "changes", reset, "\n\n") } else { fmt.Println(yellow+ "No changes to write on this file.", reset, "\n\n") } }
// Process formats and adjusts imports for the provided file. // If opt is nil the defaults are used. // // Note that filename's directory influences which imports can be chosen, // so it is important that filename be accurate. // To process data ``as if'' it were in filename, pass the data as a non-nil src. func Process(filename string, src []byte, opt *Options) ([]byte, error) { if opt == nil { opt = &Options{Comments: true, TabIndent: true, TabWidth: 8} } fileSet := token.NewFileSet() file, adjust, err := parse(fileSet, filename, src, opt) if err != nil { return nil, err } _, err = fixImports(fileSet, file, filename) if err != nil { return nil, err } sortImports(fileSet, file) imps := astutil.Imports(fileSet, file) var spacesBefore []string // import paths we need spaces before for _, impSection := range imps { // Within each block of contiguous imports, see if any // import lines are in different group numbers. If so, // we'll need to put a space between them so it's // compatible with gofmt. lastGroup := -1 for _, importSpec := range impSection { importPath, _ := strconv.Unquote(importSpec.Path.Value) groupNum := importGroup(importPath) if groupNum != lastGroup && lastGroup != -1 { spacesBefore = append(spacesBefore, importPath) } lastGroup = groupNum } } printerMode := printer.UseSpaces if opt.TabIndent { printerMode |= printer.TabIndent } printConfig := &printer.Config{Mode: printerMode, Tabwidth: opt.TabWidth} var buf bytes.Buffer err = printConfig.Fprint(&buf, fileSet, file) if err != nil { return nil, err } out := buf.Bytes() if adjust != nil { out = adjust(src, out) } if len(spacesBefore) > 0 { out = addImportSpaces(bytes.NewReader(out), spacesBefore) } out, err = format.Source(out) if err != nil { return nil, err } return out, nil }
func (d *DirectiveList) Save() error { fset := token.NewFileSet() f, err := parser.ParseFile(fset, d.file, nil, 0) if err != nil { return ErrInvalidDirectiveFile } imps := astutil.Imports(fset, f) for _, imp := range imps[0] { name := "" if imp.Name != nil && imp.Name.String() != "." { name = imp.Name.String() } path, _ := strconv.Unquote(imp.Path.Value) if name != "" { astutil.DeleteNamedImport(fset, f, name, path) } else { astutil.DeleteImport(fset, f, path) } } astutil.AddImport(fset, f, "github.com/mholt/caddy/caddy/https") astutil.AddImport(fset, f, "github.com/mholt/caddy/caddy/parse") astutil.AddImport(fset, f, "github.com/mholt/caddy/caddy/setup") astutil.AddImport(fset, f, "github.com/mholt/caddy/middleware") importName := "" for _, dir := range d.list { if dir.Removed { continue } if dir.Core == false && len(dir.ImportPath) != 0 { importName = strings.Replace(dir.Name, "-", "_", -1) astutil.AddImport(fset, f, fmt.Sprintf("{{import-%s}}", importName)) } } var buf bytes.Buffer err = printer.Fprint(&buf, fset, f) if err != nil { return err } out := buf.String() f, err = parser.ParseFile(token.NewFileSet(), "", out, 0) node, ok := f.Scope.Lookup("directiveOrder").Decl.(ast.Node) if !ok { return ErrInvalidDirectiveFile } begin := out[0 : node.Pos()-1] end := out[node.End():len(out)] dirOrder := "" for _, dir := range d.list { if dir.Removed { continue } comment := "" if dir.Active == false { comment = "//@caddyext " } importName = strings.Replace(dir.Name, "-", "_", -1) begin = strings.Replace(begin, fmt.Sprintf(`"{{import-%s}}"`, importName), fmt.Sprintf(`%s%s "%s"`, comment, importName, dir.ImportPath), -1) if dir.Core == true { dirOrder = dirOrder + fmt.Sprintf(` %s{"%s", %s},`+"\n", comment, dir.Name, dir.Setup) } else { dirOrder = dirOrder + fmt.Sprintf(` %s{"%s", %s.Setup},`+"\n", comment, dir.Name, importName) } } out = begin + dirOrderStart + dirOrder + dirOrderEnd + end return ioutil.WriteFile(d.file, []byte(out), os.FileMode(0660)) }
func (d *DirectiveList) LoadList() error { data, err := ioutil.ReadFile(d.file) if err != nil { return err } fset := token.NewFileSet() extMatches := regCommentLine.FindAll(data, -1) data = regComment.ReplaceAll(data, []byte("")) f, err := parser.ParseFile(token.NewFileSet(), "", data, 0) impPaths := astutil.Imports(fset, f) node, ok := f.Scope.Lookup("directiveOrder").Decl.(ast.Node) if !ok { return ErrInvalidDirectiveFile } c := node.(*ast.ValueSpec).Values[0].(*ast.CompositeLit) for _, m := range c.Elts { var setup *ast.SelectorExpr var directive *ast.BasicLit token := m.(*ast.CompositeLit).Elts active := true if v, ok := token[0].(*ast.BasicLit); ok { directive = v } else { return ErrImportInvalidDirectiveFormat } if v, ok := m.(*ast.CompositeLit).Elts[1].(*ast.SelectorExpr); ok { setup = v } else if _, ok := m.(*ast.CompositeLit).Elts[1].(*ast.FuncLit); ok { active = false } else { return ErrImportInvalidDirectiveFormat } name, err := strconv.Unquote(directive.Value) if err != nil { return ErrImportInvalidDirectiveFormat } dir := &Directive{ Name: name, Active: active, Core: true, Removed: false, } if active { dir.Setup = fmt.Sprintf("%s.%s", setup.X, setup.Sel) } else { dir.Name = strings.TrimLeft(dir.Name, "!") } for _, imp := range impPaths[0] { if imp.Name.String() == dir.Name { dir.Core = false dir.ImportPath, _ = strconv.Unquote(imp.Path.Value) break } } for _, commented := range extMatches { if strings.Contains(string(commented), `{"`+dir.Name) { dir.Active = false break } } d.list = append(d.list, dir) } return nil }