func fiximports()

in cmd/fiximports/main.go [141:353]


func fiximports(packages ...string) bool {
	// importedBy is the transpose of the package import graph.
	importedBy := make(map[string]map[*build.Package]bool)

	// addEdge adds an edge to the import graph.
	addEdge := func(from *build.Package, to string) {
		if to == "C" || to == "unsafe" {
			return // fake
		}
		pkgs := importedBy[to]
		if pkgs == nil {
			pkgs = make(map[*build.Package]bool)
			importedBy[to] = pkgs
		}
		pkgs[from] = true
	}

	// List metadata for all packages in the workspace.
	pkgs, err := list("...")
	if err != nil {
		fmt.Fprintf(stderr, "importfix: %v\n", err)
		return false
	}

	// packageName maps each package's path to its name.
	packageName := make(map[string]string)
	for _, p := range pkgs {
		packageName[p.ImportPath] = p.Package.Name
	}

	// canonical maps each non-canonical package path to
	// its canonical path and name.
	// A present nil value indicates that the canonical package
	// is unknown: hosted on a bad domain with no redirect.
	canonical := make(map[string]canonicalName)
	domains := strings.Split(*badDomains, ",")

	type replaceItem struct {
		old, new    string
		matchPrefix bool
	}
	var replace []replaceItem
	for _, pair := range strings.Split(*replaceFlag, ",") {
		if pair == "" {
			continue
		}
		words := strings.Split(pair, "=")
		if len(words) != 2 {
			fmt.Fprintf(stderr, "importfix: -replace: %q is not of the form \"canonical=noncanonical\".\n", pair)
			return false
		}
		replace = append(replace, replaceItem{
			old: strings.TrimSuffix(words[0], "..."),
			new: strings.TrimSuffix(words[1], "..."),
			matchPrefix: strings.HasSuffix(words[0], "...") &&
				strings.HasSuffix(words[1], "..."),
		})
	}

	// Find non-canonical packages and populate importedBy graph.
	for _, p := range pkgs {
		if p.Error != nil {
			msg := p.Error.Err
			if strings.Contains(msg, "code in directory") &&
				strings.Contains(msg, "expects import") {
				// don't show the very errors we're trying to fix
			} else {
				fmt.Fprintln(stderr, p.Error)
			}
		}

		for _, imp := range p.Imports {
			addEdge(&p.Package, imp)
		}
		for _, imp := range p.TestImports {
			addEdge(&p.Package, imp)
		}
		for _, imp := range p.XTestImports {
			addEdge(&p.Package, imp)
		}

		// Does package have an explicit import comment?
		if p.ImportComment != "" {
			if p.ImportComment != p.ImportPath {
				canonical[p.ImportPath] = canonicalName{
					path: p.Package.ImportComment,
					name: p.Package.Name,
				}
			}
		} else {
			// Is package matched by a -replace item?
			var newPath string
			for _, item := range replace {
				if item.matchPrefix {
					if strings.HasPrefix(p.ImportPath, item.old) {
						newPath = item.new + p.ImportPath[len(item.old):]
						break
					}
				} else if p.ImportPath == item.old {
					newPath = item.new
					break
				}
			}
			if newPath != "" {
				newName := packageName[newPath]
				if newName == "" {
					newName = filepath.Base(newPath) // a guess
				}
				canonical[p.ImportPath] = canonicalName{
					path: newPath,
					name: newName,
				}
				continue
			}

			// Is package matched by a -baddomains item?
			for _, domain := range domains {
				slash := strings.Index(p.ImportPath, "/")
				if slash < 0 {
					continue // no slash: standard package
				}
				if p.ImportPath[:slash] == domain {
					// Package comes from bad domain and has no import comment.
					// Report an error each time this package is imported.
					canonical[p.ImportPath] = canonicalName{}

					// TODO(adonovan): should we make an HTTP request to
					// see if there's an HTTP redirect, a "go-import" meta tag,
					// or an import comment in the latest revision?
					// It would duplicate a lot of logic from "go get".
				}
				break
			}
		}
	}

	// Find all clients (direct importers) of canonical packages.
	// These are the packages that need fixing up.
	clients := make(map[*build.Package]bool)
	for path := range canonical {
		for client := range importedBy[path] {
			clients[client] = true
		}
	}

	// Restrict rewrites to the set of packages specified by the user.
	if len(packages) == 1 && (packages[0] == "all" || packages[0] == "...") {
		// no restriction
	} else {
		pkgs, err := list(packages...)
		if err != nil {
			fmt.Fprintf(stderr, "importfix: %v\n", err)
			return false
		}
		seen := make(map[string]bool)
		for _, p := range pkgs {
			seen[p.ImportPath] = true
		}
		for client := range clients {
			if !seen[client.ImportPath] {
				delete(clients, client)
			}
		}
	}

	// Rewrite selected client packages.
	ok := true
	for client := range clients {
		if !rewritePackage(client, canonical) {
			ok = false

			// There were errors.
			// Show direct and indirect imports of client.
			seen := make(map[string]bool)
			var direct, indirect []string
			for p := range importedBy[client.ImportPath] {
				direct = append(direct, p.ImportPath)
				seen[p.ImportPath] = true
			}

			var visit func(path string)
			visit = func(path string) {
				for q := range importedBy[path] {
					qpath := q.ImportPath
					if !seen[qpath] {
						seen[qpath] = true
						indirect = append(indirect, qpath)
						visit(qpath)
					}
				}
			}

			if direct != nil {
				fmt.Fprintf(stderr, "\timported directly by:\n")
				sort.Strings(direct)
				for _, path := range direct {
					fmt.Fprintf(stderr, "\t\t%s\n", path)
					visit(path)
				}

				if indirect != nil {
					fmt.Fprintf(stderr, "\timported indirectly by:\n")
					sort.Strings(indirect)
					for _, path := range indirect {
						fmt.Fprintf(stderr, "\t\t%s\n", path)
					}
				}
			}
		}
	}

	return ok
}