|
| 1 | +// Copyright (c) Microsoft Corporation. |
| 2 | +// Licensed under the MIT license. |
| 3 | + |
| 4 | +package sqlcmdlinter |
| 5 | + |
| 6 | +import ( |
| 7 | + "go/ast" |
| 8 | + "go/token" |
| 9 | + "strings" |
| 10 | + |
| 11 | + "golang.org/x/tools/go/analysis" |
| 12 | + "golang.org/x/tools/go/analysis/passes/inspect" |
| 13 | + "golang.org/x/tools/go/ast/inspector" |
| 14 | +) |
| 15 | + |
| 16 | +var ImportsAnalyzer = &analysis.Analyzer{ |
| 17 | + Name: "importslint", |
| 18 | + Doc: "Require most external packages be imported only by internal packages", |
| 19 | + Requires: []*analysis.Analyzer{inspect.Analyzer}, |
| 20 | + Run: runImports, |
| 21 | +} |
| 22 | + |
| 23 | +var AllowedImports = map[string][]string{ |
| 24 | + `"github.com/alecthomas/kong`: {`cmd/sqlcmd`, `pkg/sqlcmd`}, |
| 25 | + `"github.com/golang-sql/sqlexp`: {`pkg/sqlcmd`}, |
| 26 | + `"github.com/google/uuid`: {}, |
| 27 | + `"github.com/peterh/liner`: {`pkg/console`}, |
| 28 | + `"github.com/microsoft/go-mssqldb`: {}, |
| 29 | + `"github.com/microsoft/go-sqlcmd`: {}, |
| 30 | + `"github.com/spf13/cobra`: {`cmd/sqlcmd`, `cmd/modern`}, |
| 31 | + `"github.com/spf13/viper`: {`cmd/sqlcmd`, `cmd/modern`}, |
| 32 | + `"github.com/stretchr/testify`: {}, |
| 33 | +} |
| 34 | + |
| 35 | +func runImports(pass *analysis.Pass) (interface{}, error) { |
| 36 | + inspectorInstance := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) |
| 37 | + nodeFilter := []ast.Node{(*ast.File)(nil)} |
| 38 | + inspectorInstance.Preorder(nodeFilter, func(n ast.Node) { |
| 39 | + |
| 40 | + f := n.(*ast.File) |
| 41 | + fileName := pass.Fset.Position(f.Package).Filename |
| 42 | + isInternal := strings.Contains(fileName, `internal\`) || strings.Contains(fileName, `internal/`) |
| 43 | + for _, s := range f.Imports { |
| 44 | + if s.Path.Kind == token.STRING { |
| 45 | + pkg := s.Path.Value |
| 46 | + if isInternal { |
| 47 | + if !isValidInternalImport(pkg) { |
| 48 | + pass.Reportf(s.Pos(), "Internal packages should not import %s", pkg) |
| 49 | + } |
| 50 | + } else if !isValidExternalImport(pkg, fileName) { |
| 51 | + pass.Reportf(s.Pos(), "Non-internal packages should not import %s", pkg) |
| 52 | + } |
| 53 | + } |
| 54 | + } |
| 55 | + }) |
| 56 | + |
| 57 | + return nil, nil |
| 58 | +} |
| 59 | + |
| 60 | +func isValidInternalImport(pkg string) bool { |
| 61 | + return !strings.HasPrefix(pkg, `"github.com/microsoft/go-sqlcmd/pkg`) && !strings.HasPrefix(pkg, `"github.com/microsoft/go-sqlcmd/cmd`) |
| 62 | +} |
| 63 | + |
| 64 | +func isValidExternalImport(pkg string, filename string) bool { |
| 65 | + if strings.HasPrefix(pkg, `"github.com`) { |
| 66 | + for key, paths := range AllowedImports { |
| 67 | + if strings.HasPrefix(pkg, key) { |
| 68 | + if len(paths) == 0 { |
| 69 | + // any package can import it |
| 70 | + return true |
| 71 | + } |
| 72 | + for _, p := range paths { |
| 73 | + // canonicalize path to Linux separator for comparison |
| 74 | + path := strings.ReplaceAll(filename, `\`, `/`) |
| 75 | + if strings.Contains(path, p) { |
| 76 | + return true |
| 77 | + } |
| 78 | + } |
| 79 | + } |
| 80 | + } |
| 81 | + return false |
| 82 | + } |
| 83 | + return true |
| 84 | +} |
0 commit comments