// Copyright 2013 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.
// Package astutil contains common utilities for working with the Go AST.
package astutil // import "golang.org/x/tools/go/ast/astutil"import ()// AddImport adds the import path to the file f, if absent.func ( *token.FileSet, *ast.File, string) ( bool) {returnAddNamedImport(, , "", )}// AddNamedImport adds the import with the given name and path to the file f, if absent.// If name is not empty, it is used to rename the import.//// For example, calling//// AddNamedImport(fset, f, "pathpkg", "path")//// adds//// import pathpkg "path"func ( *token.FileSet, *ast.File, , string) ( bool) {ifimports(, , ) {returnfalse } := &ast.ImportSpec{Path: &ast.BasicLit{Kind: token.STRING,Value: strconv.Quote(), }, }if != "" { .Name = &ast.Ident{Name: } }// Find an import decl to add to. // The goal is to find an existing import // whose import path has the longest shared // prefix with path.var ( = -1// length of longest shared prefix = -1// index in f.Decls of the file's final import decl *ast.GenDecl// import decl containing the best match = -1// spec index in impDecl containing the best match = isThirdParty() )for , := range .Decls { , := .(*ast.GenDecl)if && .Tok == token.IMPORT { = // Do not add to import "C", to avoid disrupting the // association with its doc comment, breaking cgo.ifdeclImports(, "C") {continue }// Match an empty import decl if that's all that is available.iflen(.Specs) == 0 && == -1 { = }// Compute longest shared prefix with imports in this group and find best // matched import spec. // 1. Always prefer import spec with longest shared prefix. // 2. While match length is 0, // - for stdlib package: prefer first import spec. // - for third party package: prefer first third party import spec. // We cannot use last import spec as best match for third party package // because grouped imports are usually placed last by goimports -local // flag. // See issue #19190. := falsefor , := range .Specs { := .(*ast.ImportSpec) := importPath() := matchLen(, )if > || ( == 0 && ! && ) { = = = } = || isThirdParty() } } }// If no import decl found, add one after the last import.if == nil { = &ast.GenDecl{Tok: token.IMPORT, }if >= 0 { .TokPos = .Decls[].End() } else {// There are no existing imports. // Our new import, preceded by a blank line, goes after the package declaration // and after the comment, if any, that starts on the same line as the // package declaration. .TokPos = .Package := .File(.Package) := .Line(.Package)for , := range .Comments {if .Line(.Pos()) > {break }// +2 for a blank line .TokPos = .End() + 2 } } .Decls = append(.Decls, nil)copy(.Decls[+2:], .Decls[+1:]) .Decls[+1] = }// Insert new import at insertAt. := 0if >= 0 {// insert after the found import = + 1 } .Specs = append(.Specs, nil)copy(.Specs[+1:], .Specs[:]) .Specs[] = := .Pos()if > 0 {// If there is a comment after an existing import, preserve the comment // position by adding the new import after the comment.if , := .Specs[-1].(*ast.ImportSpec); && .Comment != nil { = .Comment.End() } else {// Assign same position as the previous import, // so that the sorter sees it as being in the same block. = .Specs[-1].Pos() } }if .Name != nil { .Name.NamePos = }updateBasicLitPos(.Path, ) .EndPos = // Clean up parens. impDecl contains at least one spec.iflen(.Specs) == 1 {// Remove unneeded parens. .Lparen = token.NoPos } elseif !.Lparen.IsValid() {// impDecl needs parens added. .Lparen = .Specs[0].Pos() } .Imports = append(.Imports, )iflen(.Decls) <= 1 {returntrue }// Merge all the import declarations into the first one.var *ast.GenDeclfor := 0; < len(.Decls); ++ { := .Decls[] , := .(*ast.GenDecl)if ! || .Tok != token.IMPORT || declImports(, "C") {continue }if == nil { = continue// Don't touch the first one. }// We now know there is more than one package in this import // declaration. Ensure that it ends up parenthesized. .Lparen = .Pos()// Move the imports of the other import declaration to the first one.for , := range .Specs {updateBasicLitPos(.(*ast.ImportSpec).Path, .Pos()) .Specs = append(.Specs, ) } .Decls = slices.Delete(.Decls, , +1) -- }returntrue}func isThirdParty( string) bool {// Third party package import path usually contains "." (".com", ".org", ...) // This logic is taken from golang.org/x/tools/imports package.returnstrings.Contains(, ".")}// DeleteImport deletes the import path from the file f, if present.// If there are duplicate import declarations, all matching ones are deleted.func ( *token.FileSet, *ast.File, string) ( bool) {returnDeleteNamedImport(, , "", )}// DeleteNamedImport deletes the import with the given name and path from the file f, if present.// If there are duplicate import declarations, all matching ones are deleted.func ( *token.FileSet, *ast.File, , string) ( bool) {var ( = make(map[*ast.ImportSpec]bool) = make(map[*ast.CommentGroup]bool) )// Find the import nodes that import path, if any.for := 0; < len(.Decls); ++ { , := .Decls[].(*ast.GenDecl)if ! || .Tok != token.IMPORT {continue }for := 0; < len(.Specs); ++ { := .Specs[].(*ast.ImportSpec)ifimportName() != || importPath() != {continue }// We found an import spec that imports path. // Delete it. [] = true = true .Specs = slices.Delete(.Specs, , +1)// If this was the last import spec in this decl, // delete the decl, too.iflen(.Specs) == 0 { .Decls = slices.Delete(.Decls, , +1) --break } elseiflen(.Specs) == 1 {if .Doc != nil { [.Doc] = true }if .Comment != nil { [.Comment] = true }for , := range .Comments {// Found comment on the same line as the import spec.if .End() < .Pos() && .Position(.End()).Line == .Position(.Pos()).Line { [] = truebreak } } := .Specs[0].(*ast.ImportSpec)// Move the documentation right after the import decl.if .Doc != nil {for .Position(.TokPos).Line+1 < .Position(.Doc.Pos()).Line { .File(.TokPos).MergeLine(.Position(.TokPos).Line) } }for , := range .Comments {if .End() < .Pos() && .Position(.End()).Line == .Position(.Pos()).Line {for .Position(.TokPos).Line+1 < .Position(.Pos()).Line { .File(.TokPos).MergeLine(.Position(.TokPos).Line) }break } } }if > 0 { := .Specs[-1].(*ast.ImportSpec) := .PositionFor(.Path.ValuePos, false).Line := .PositionFor(.Path.ValuePos, false).Line// We deleted an entry but now there may be // a blank line-sized hole where the import was.if - > 1 || !.Rparen.IsValid() {// There was a blank line immediately preceding the deleted import, // so there's no need to close the hole. The right parenthesis is // invalid after AddImport to an import statement without parenthesis. // Do nothing. } elseif != .File(.Rparen).LineCount() {// There was no blank line. Close the hole. .File(.Rparen).MergeLine() } } -- } }// Delete imports from f.Imports. := len(.Imports) .Imports = slices.DeleteFunc(.Imports, func( *ast.ImportSpec) bool { , := []return })iflen(.Imports)+len() != {// This can happen when the AST is invalid (i.e. imports differ between f.Decls and f.Imports).panic(fmt.Sprintf("deleted specs from Decls but not Imports: %v", )) }// Delete comments from f.Comments. .Comments = slices.DeleteFunc(.Comments, func( *ast.CommentGroup) bool { , := []return })return}// RewriteImport rewrites any import of path oldPath to path newPath.func ( *token.FileSet, *ast.File, , string) ( bool) {for , := range .Imports {ifimportPath() == { = true// record old End, because the default is to compute // it using the length of imp.Path.Value. .EndPos = .End() .Path.Value = strconv.Quote() } }return}// UsesImport reports whether a given import is used.// The provided File must have been parsed with syntactic object resolution// (not using go/parser.SkipObjectResolution).func ( *ast.File, string) ( bool) {if .Scope == nil {panic("file f was not parsed with syntactic object resolution") } := importSpec(, )if == nil {return } := .Name.String()switch {case"<nil>":// If the package name is not explicitly specified, // make an educated guess. This is not guaranteed to be correct. := strings.LastIndex(, "/")if == -1 { = } else { = [+1:] }case"_", ".":// Not sure if this import is used - err on the side of caution.returntrue }ast.Walk(visitFn(func( ast.Node) { , := .(*ast.SelectorExpr)if && isTopName(.X, ) { = true } }), )return}type visitFn func(node ast.Node)func ( visitFn) ( ast.Node) ast.Visitor { ()return}// imports reports whether f has an import with the specified name and path.func imports( *ast.File, , string) bool {for , := range .Imports {ifimportName() == && importPath() == {returntrue } }returnfalse}// importSpec returns the import spec if f imports path,// or nil otherwise.func importSpec( *ast.File, string) *ast.ImportSpec {for , := range .Imports {ifimportPath() == {return } }returnnil}// importName returns the name of s,// or "" if the import is not named.func importName( *ast.ImportSpec) string {if .Name == nil {return"" }return .Name.Name}// importPath returns the unquoted import path of s,// or "" if the path is not properly quoted.func importPath( *ast.ImportSpec) string { , := strconv.Unquote(.Path.Value)if != nil {return"" }return}// declImports reports whether gen contains an import of path.func declImports( *ast.GenDecl, string) bool {if .Tok != token.IMPORT {returnfalse }for , := range .Specs { := .(*ast.ImportSpec)ifimportPath() == {returntrue } }returnfalse}// matchLen returns the length of the longest path segment prefix shared by x and y.func matchLen(, string) int { := 0for := 0; < len() && < len() && [] == []; ++ {if [] == '/' { ++ } }return}// isTopName returns true if n is a top-level unresolved identifier with the given name.func isTopName( ast.Expr, string) bool { , := .(*ast.Ident)return && .Name == && .Obj == nil}// Imports returns the file imports grouped by paragraph.func ( *token.FileSet, *ast.File) [][]*ast.ImportSpec {var [][]*ast.ImportSpecfor , := range .Decls { , := .(*ast.GenDecl)if ! || .Tok != token.IMPORT {break } := []*ast.ImportSpec{}varintfor , := range .Specs { := .(*ast.ImportSpec) := .Path.ValuePos := .Position().Lineif > 0 && > 0 && - > 1 { = append(, ) = []*ast.ImportSpec{} } = append(, ) = } = append(, ) }return}// updateBasicLitPos updates lit.Pos,// ensuring that lit.End (if set) is displaced by the same amount.// (See https://go.dev/issue/76395.)func updateBasicLitPos( *ast.BasicLit, token.Pos) { := .End() - .Pos() .ValuePos = // TODO(adonovan): after go1.26, simplify to: // lit.ValueEnd = pos + len := reflect.ValueOf().Elem().FieldByName("ValueEnd")if .IsValid() && .Int() != 0 { .SetInt(int64( + )) }}
The pages are generated with Goldsv0.8.7. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.