// 离线列出模块内非测试 Go 文件的导入。 // 只使用标准库,跳过 _test.go 和带 go.mod 的子目录,避免 go list 去下载模块图。 package main import ( "bufio" "fmt" "go/parser" "go/token" "os" "path/filepath" "strings" ) func main() { for _, root := range os.Args[1:] { mod := modulePath(filepath.Join(root, "go.mod")) if mod == "" { fmt.Fprintf(os.Stderr, "lint-imports: %s has no module path\n", root) os.Exit(1) } err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error { if err != nil || !d.IsDir() { return nil } name := d.Name() if name == "vendor" || (name != "." && strings.HasPrefix(name, ".")) { return filepath.SkipDir } if path != root { if _, statErr := os.Stat(filepath.Join(path, "go.mod")); statErr == nil { return filepath.SkipDir } } return printImports(root, mod, path) }) if err != nil { fmt.Fprintf(os.Stderr, "lint-imports: %v\n", err) os.Exit(1) } } } func modulePath(goMod string) string { f, err := os.Open(goMod) if err != nil { return "" } defer f.Close() sc := bufio.NewScanner(f) for sc.Scan() { fields := strings.Fields(sc.Text()) if len(fields) >= 2 && fields[0] == "module" { return fields[1] } } return "" } func printImports(root, mod, dir string) error { rel, err := filepath.Rel(root, dir) if err != nil { return err } importer := mod if rel != "." { importer = mod + "/" + filepath.ToSlash(rel) } entries, err := os.ReadDir(dir) if err != nil { return err } fset := token.NewFileSet() seen := map[string]struct{}{} var imports []string for _, entry := range entries { name := entry.Name() if entry.IsDir() || !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") { continue } file, err := parser.ParseFile(fset, filepath.Join(dir, name), nil, parser.ImportsOnly) if err != nil { continue } for _, imp := range file.Imports { path := strings.Trim(imp.Path.Value, `"`) if _, ok := seen[path]; ok { continue } seen[path] = struct{}{} imports = append(imports, path) } } if len(imports) == 0 { return nil } fmt.Printf("%s %s\n", importer, strings.Join(imports, " ")) return nil }