internal/imports: refactor to split finding and applying fixes
A pass is responsible for fixing the imports of a given file. It now finds the necessary changes to make without applying the result to the ast, which may be desirable to give a user more control about what changes will be applied to their program. This change splits the process of finding the fixes from making the modifications to the ast to allow this functionality to be easily possible. Change-Id: Ibf8ca247c35539f91de4be90c634f0db9a939d07 Reviewed-on: https://go-review.googlesource.com/c/tools/+/184197 Reviewed-by: Heschi Kreinick <heschi@google.com> Run-TryBot: Suzy Mueller <suzmue@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
parent
38ae2c8f64
commit
9bea2ecb95
|
@ -67,6 +67,19 @@ func importGroup(env *ProcessEnv, importPath string) int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type importFixType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
addImport importFixType = iota
|
||||||
|
deleteImport
|
||||||
|
setImportName
|
||||||
|
)
|
||||||
|
|
||||||
|
type importFix struct {
|
||||||
|
info importInfo
|
||||||
|
fixType importFixType
|
||||||
|
}
|
||||||
|
|
||||||
// An importInfo represents a single import statement.
|
// An importInfo represents a single import statement.
|
||||||
type importInfo struct {
|
type importInfo struct {
|
||||||
importPath string // import path, e.g. "crypto/rand".
|
importPath string // import path, e.g. "crypto/rand".
|
||||||
|
@ -290,7 +303,7 @@ func (p *pass) importIdentifier(imp *importInfo) string {
|
||||||
// load reads in everything necessary to run a pass, and reports whether the
|
// load reads in everything necessary to run a pass, and reports whether the
|
||||||
// file already has all the imports it needs. It fills in p.missingRefs with the
|
// file already has all the imports it needs. It fills in p.missingRefs with the
|
||||||
// file's missing symbols, if any, or removes unused imports if not.
|
// file's missing symbols, if any, or removes unused imports if not.
|
||||||
func (p *pass) load() bool {
|
func (p *pass) load() ([]*importFix, bool) {
|
||||||
p.knownPackages = map[string]*packageInfo{}
|
p.knownPackages = map[string]*packageInfo{}
|
||||||
p.missingRefs = references{}
|
p.missingRefs = references{}
|
||||||
p.existingImports = map[string]*importInfo{}
|
p.existingImports = map[string]*importInfo{}
|
||||||
|
@ -320,7 +333,7 @@ func (p *pass) load() bool {
|
||||||
if p.env.Debug {
|
if p.env.Debug {
|
||||||
p.env.Logf("loading package names: %v", err)
|
p.env.Logf("loading package names: %v", err)
|
||||||
}
|
}
|
||||||
return false
|
return nil, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, imp := range imports {
|
for _, imp := range imports {
|
||||||
|
@ -339,16 +352,16 @@ func (p *pass) load() bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(p.missingRefs) != 0 {
|
if len(p.missingRefs) != 0 {
|
||||||
return false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.fix()
|
return p.fix()
|
||||||
}
|
}
|
||||||
|
|
||||||
// fix attempts to satisfy missing imports using p.candidates. If it finds
|
// fix attempts to satisfy missing imports using p.candidates. If it finds
|
||||||
// everything, or if p.lastTry is true, it adds the imports it found,
|
// everything, or if p.lastTry is true, it updates fixes to add the imports it found,
|
||||||
// removes anything unused, and returns true.
|
// delete anything unused, and update import names, and returns true.
|
||||||
func (p *pass) fix() bool {
|
func (p *pass) fix() ([]*importFix, bool) {
|
||||||
// Find missing imports.
|
// Find missing imports.
|
||||||
var selected []*importInfo
|
var selected []*importInfo
|
||||||
for left, rights := range p.missingRefs {
|
for left, rights := range p.missingRefs {
|
||||||
|
@ -358,10 +371,11 @@ func (p *pass) fix() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !p.lastTry && len(selected) != len(p.missingRefs) {
|
if !p.lastTry && len(selected) != len(p.missingRefs) {
|
||||||
return false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Found everything, or giving up. Add the new imports and remove any unused.
|
// Found everything, or giving up. Add the new imports and remove any unused.
|
||||||
|
var fixes []*importFix
|
||||||
for _, imp := range p.existingImports {
|
for _, imp := range p.existingImports {
|
||||||
// We deliberately ignore globals here, because we can't be sure
|
// We deliberately ignore globals here, because we can't be sure
|
||||||
// they're in the same package. People do things like put multiple
|
// they're in the same package. People do things like put multiple
|
||||||
|
@ -369,27 +383,77 @@ func (p *pass) fix() bool {
|
||||||
// remove imports if they happen to have the same name as a var in
|
// remove imports if they happen to have the same name as a var in
|
||||||
// a different package.
|
// a different package.
|
||||||
if _, ok := p.allRefs[p.importIdentifier(imp)]; !ok {
|
if _, ok := p.allRefs[p.importIdentifier(imp)]; !ok {
|
||||||
astutil.DeleteNamedImport(p.fset, p.f, imp.name, imp.importPath)
|
fixes = append(fixes, &importFix{
|
||||||
|
info: *imp,
|
||||||
|
fixType: deleteImport,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// An existing import may need to update its import name to be correct.
|
||||||
|
if name := p.importSpecName(imp); name != imp.name {
|
||||||
|
fixes = append(fixes, &importFix{
|
||||||
|
info: importInfo{
|
||||||
|
name: name,
|
||||||
|
importPath: imp.importPath,
|
||||||
|
},
|
||||||
|
fixType: setImportName,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, imp := range selected {
|
for _, imp := range selected {
|
||||||
astutil.AddNamedImport(p.fset, p.f, imp.name, imp.importPath)
|
fixes = append(fixes, &importFix{
|
||||||
|
info: importInfo{
|
||||||
|
name: p.importSpecName(imp),
|
||||||
|
importPath: imp.importPath,
|
||||||
|
},
|
||||||
|
fixType: addImport,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.loadRealPackageNames {
|
return fixes, true
|
||||||
for _, imp := range p.f.Imports {
|
}
|
||||||
if imp.Name != nil {
|
|
||||||
continue
|
// importSpecName gets the import name of imp in the import spec.
|
||||||
}
|
//
|
||||||
path := strings.Trim(imp.Path.Value, `""`)
|
// When the import identifier matches the assumed import name, the import name does
|
||||||
ident := p.importIdentifier(&importInfo{importPath: path})
|
// not appear in the import spec.
|
||||||
if ident != importPathToAssumedName(path) {
|
func (p *pass) importSpecName(imp *importInfo) string {
|
||||||
imp.Name = &ast.Ident{Name: ident, NamePos: imp.Pos()}
|
// If we did not load the real package names, or the name is already set,
|
||||||
|
// we just return the existing name.
|
||||||
|
if !p.loadRealPackageNames || imp.name != "" {
|
||||||
|
return imp.name
|
||||||
|
}
|
||||||
|
|
||||||
|
ident := p.importIdentifier(imp)
|
||||||
|
if ident == importPathToAssumedName(imp.importPath) {
|
||||||
|
return "" // ident not needed since the assumed and real names are the same.
|
||||||
|
}
|
||||||
|
return ident
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply will perform the fixes on f in order.
|
||||||
|
func apply(fset *token.FileSet, f *ast.File, fixes []*importFix) bool {
|
||||||
|
for _, fix := range fixes {
|
||||||
|
switch fix.fixType {
|
||||||
|
case deleteImport:
|
||||||
|
astutil.DeleteNamedImport(fset, f, fix.info.name, fix.info.importPath)
|
||||||
|
case addImport:
|
||||||
|
astutil.AddNamedImport(fset, f, fix.info.name, fix.info.importPath)
|
||||||
|
case setImportName:
|
||||||
|
// Find the matching import path and change the name.
|
||||||
|
for _, spec := range f.Imports {
|
||||||
|
path := strings.Trim(spec.Path.Value, `""`)
|
||||||
|
if path == fix.info.importPath {
|
||||||
|
spec.Name = &ast.Ident{
|
||||||
|
Name: fix.info.name,
|
||||||
|
NamePos: spec.Pos(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -442,10 +506,21 @@ func (p *pass) addCandidate(imp *importInfo, pkg *packageInfo) {
|
||||||
var fixImports = fixImportsDefault
|
var fixImports = fixImportsDefault
|
||||||
|
|
||||||
func fixImportsDefault(fset *token.FileSet, f *ast.File, filename string, env *ProcessEnv) error {
|
func fixImportsDefault(fset *token.FileSet, f *ast.File, filename string, env *ProcessEnv) error {
|
||||||
abs, err := filepath.Abs(filename)
|
fixes, err := getFixes(fset, f, filename, env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
apply(fset, f, fixes)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// getFixes gets the getFixes that need to be made to f in order to fix the imports.
|
||||||
|
// It does not modify the ast.
|
||||||
|
func getFixes(fset *token.FileSet, f *ast.File, filename string, env *ProcessEnv) ([]*importFix, error) {
|
||||||
|
abs, err := filepath.Abs(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
srcDir := filepath.Dir(abs)
|
srcDir := filepath.Dir(abs)
|
||||||
if env.Debug {
|
if env.Debug {
|
||||||
env.Logf("fixImports(filename=%q), abs=%q, srcDir=%q ...", filename, abs, srcDir)
|
env.Logf("fixImports(filename=%q), abs=%q, srcDir=%q ...", filename, abs, srcDir)
|
||||||
|
@ -456,8 +531,8 @@ func fixImportsDefault(fset *token.FileSet, f *ast.File, filename string, env *P
|
||||||
// complete. We can't add any imports yet, because we don't know
|
// complete. We can't add any imports yet, because we don't know
|
||||||
// if missing references are actually package vars.
|
// if missing references are actually package vars.
|
||||||
p := &pass{fset: fset, f: f, srcDir: srcDir}
|
p := &pass{fset: fset, f: f, srcDir: srcDir}
|
||||||
if p.load() {
|
if fixes, done := p.load(); done {
|
||||||
return nil
|
return fixes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
otherFiles := parseOtherFiles(fset, srcDir, filename)
|
otherFiles := parseOtherFiles(fset, srcDir, filename)
|
||||||
|
@ -465,15 +540,15 @@ func fixImportsDefault(fset *token.FileSet, f *ast.File, filename string, env *P
|
||||||
// Second pass: add information from other files in the same package,
|
// Second pass: add information from other files in the same package,
|
||||||
// like their package vars and imports.
|
// like their package vars and imports.
|
||||||
p.otherFiles = otherFiles
|
p.otherFiles = otherFiles
|
||||||
if p.load() {
|
if fixes, done := p.load(); done {
|
||||||
return nil
|
return fixes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now we can try adding imports from the stdlib.
|
// Now we can try adding imports from the stdlib.
|
||||||
p.assumeSiblingImportsValid()
|
p.assumeSiblingImportsValid()
|
||||||
addStdlibCandidates(p, p.missingRefs)
|
addStdlibCandidates(p, p.missingRefs)
|
||||||
if p.fix() {
|
if fixes, done := p.fix(); done {
|
||||||
return nil
|
return fixes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Third pass: get real package names where we had previously used
|
// Third pass: get real package names where we had previously used
|
||||||
|
@ -482,25 +557,25 @@ func fixImportsDefault(fset *token.FileSet, f *ast.File, filename string, env *P
|
||||||
p = &pass{fset: fset, f: f, srcDir: srcDir, env: env}
|
p = &pass{fset: fset, f: f, srcDir: srcDir, env: env}
|
||||||
p.loadRealPackageNames = true
|
p.loadRealPackageNames = true
|
||||||
p.otherFiles = otherFiles
|
p.otherFiles = otherFiles
|
||||||
if p.load() {
|
if fixes, done := p.load(); done {
|
||||||
return nil
|
return fixes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
addStdlibCandidates(p, p.missingRefs)
|
addStdlibCandidates(p, p.missingRefs)
|
||||||
p.assumeSiblingImportsValid()
|
p.assumeSiblingImportsValid()
|
||||||
if p.fix() {
|
if fixes, done := p.fix(); done {
|
||||||
return nil
|
return fixes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Go look for candidates in $GOPATH, etc. We don't necessarily load
|
// Go look for candidates in $GOPATH, etc. We don't necessarily load
|
||||||
// the real exports of sibling imports, so keep assuming their contents.
|
// the real exports of sibling imports, so keep assuming their contents.
|
||||||
if err := addExternalCandidates(p, p.missingRefs, filename); err != nil {
|
if err := addExternalCandidates(p, p.missingRefs, filename); err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
p.lastTry = true
|
p.lastTry = true
|
||||||
p.fix()
|
fixes, _ := p.fix()
|
||||||
return nil
|
return fixes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProcessEnv contains environment variables and settings that affect the use of
|
// ProcessEnv contains environment variables and settings that affect the use of
|
||||||
|
|
Loading…
Reference in New Issue