/
analyzer.go
160 lines (139 loc) · 4.43 KB
/
analyzer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package analyzer
import (
"bytes"
"fmt"
"go/token"
"strings"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"github.com/daixiang0/gci/pkg/config"
"github.com/daixiang0/gci/pkg/gci"
"github.com/daixiang0/gci/pkg/io"
"github.com/daixiang0/gci/pkg/log"
)
const (
NoInlineCommentsFlag = "noInlineComments"
NoPrefixCommentsFlag = "noPrefixComments"
SkipGeneratedFlag = "skipGenerated"
SectionsFlag = "Sections"
SectionSeparatorsFlag = "SectionSeparators"
SectionDelimiter = ","
)
var (
noInlineComments bool
noPrefixComments bool
skipGenerated bool
sectionsStr string
sectionSeparatorsStr string
)
func init() {
Analyzer.Flags.BoolVar(&noInlineComments, NoInlineCommentsFlag, false, "If comments in the same line as the input should be present")
Analyzer.Flags.BoolVar(&noPrefixComments, NoPrefixCommentsFlag, false, "If comments above an input should be present")
Analyzer.Flags.BoolVar(&skipGenerated, SkipGeneratedFlag, false, "Skip generated files")
Analyzer.Flags.StringVar(§ionsStr, SectionsFlag, "", "Specify the Sections format that should be used to check the file formatting")
Analyzer.Flags.StringVar(§ionSeparatorsStr, SectionSeparatorsFlag, "", "Specify the Sections that are inserted as Separators between Sections")
log.InitLogger()
defer log.L().Sync()
}
var Analyzer = &analysis.Analyzer{
Name: "gci",
Doc: "A tool that control golang package import order and make it always deterministic.",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: runAnalysis,
}
func runAnalysis(pass *analysis.Pass) (interface{}, error) {
var fileReferences []*token.File
// extract file references for all files in the analyzer pass
for _, pkgFile := range pass.Files {
fileForPos := pass.Fset.File(pkgFile.Package)
if fileForPos != nil {
fileReferences = append(fileReferences, fileForPos)
}
}
expectedNumFiles := len(pass.Files)
foundNumFiles := len(fileReferences)
if expectedNumFiles != foundNumFiles {
return nil, InvalidNumberOfFilesInAnalysis{expectedNumFiles, foundNumFiles}
}
// read configuration options
gciCfg, err := parseGciConfiguration()
if err != nil {
return nil, err
}
for _, file := range fileReferences {
filePath := file.Name()
unmodifiedFile, formattedFile, err := gci.LoadFormatGoFile(io.File{FilePath: filePath}, *gciCfg)
if err != nil {
return nil, err
}
// search for a difference
fileRunes := bytes.Runes(unmodifiedFile)
formattedRunes := bytes.Runes(formattedFile)
diffIdx := compareRunes(fileRunes, formattedRunes)
switch diffIdx {
case -1:
// no difference
default:
pass.Reportf(file.Pos(diffIdx), "fix by `%s %s`", generateCmdLine(*gciCfg), filePath)
}
}
return nil, nil
}
func compareRunes(a, b []rune) (differencePos int) {
// check shorter rune slice first to prevent invalid array access
shorterRune := a
if len(b) < len(a) {
shorterRune = b
}
// check for differences up to where the length is identical
for idx := 0; idx < len(shorterRune); idx++ {
if a[idx] != b[idx] {
return idx
}
}
// check that we have compared two equally long rune arrays
if len(a) != len(b) {
return len(shorterRune) + 1
}
return -1
}
func parseGciConfiguration() (*config.Config, error) {
fmtCfg := config.BoolConfig{
NoInlineComments: noInlineComments,
NoPrefixComments: noPrefixComments,
Debug: false,
SkipGenerated: skipGenerated,
}
var sectionStrings []string
if sectionsStr != "" {
sectionStrings = strings.Split(sectionsStr, SectionDelimiter)
}
var sectionSeparatorStrings []string
if sectionSeparatorsStr != "" {
sectionSeparatorStrings = strings.Split(sectionSeparatorsStr, SectionDelimiter)
fmt.Println(sectionSeparatorsStr)
}
return config.YamlConfig{Cfg: fmtCfg, SectionStrings: sectionStrings, SectionSeparatorStrings: sectionSeparatorStrings}.Parse()
}
func generateCmdLine(cfg config.Config) string {
result := "gci write"
if cfg.BoolConfig.NoInlineComments {
result += " --NoInlineComments "
}
if cfg.BoolConfig.NoPrefixComments {
result += " --NoPrefixComments "
}
if cfg.BoolConfig.SkipGenerated {
result += " --skip-generated "
}
if cfg.BoolConfig.CustomOrder {
result += " --custom-order "
}
for _, s := range cfg.Sections.String() {
result += fmt.Sprintf(" --Section \"%s\" ", s)
}
for _, s := range cfg.SectionSeparators.String() {
result += fmt.Sprintf(" --SectionSeparator %s ", s)
}
return result
}