-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
imports.go
238 lines (187 loc) · 6.04 KB
/
imports.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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
package nit
import (
"go/ast"
"go/token"
"strings"
"github.com/pkg/errors"
)
type (
// ImportsSection represents one of the 3 valid `imports` sections.
ImportsSection uint8
// ImportsSectionMachine represents the `imports` code organization state
// machine.
ImportsSectionMachine struct {
current ImportsTransition
previous ImportsTransition
}
// ImportsTransition represents one of the 3 valid `imports` sections, it
// defines the State Machine transition rules for that concrete section,
// the order to be expected is: "Standard" -> "External" -> "Local".
ImportsTransition interface {
External() (ImportsTransition, error)
Local() (ImportsTransition, error)
Standard() (ImportsTransition, error)
}
// ImportsValidator defines the type including the rules used for validating
// the `imports` section as a whole.
ImportsValidator struct {
localPath string
fsm *ImportsSectionMachine
}
externalImportsTransition struct{}
localImportsTransition struct{}
standardImportsTransition struct{}
)
const (
// ImportsSectionStd represents the Standard Library Packages `imports` section.
ImportsSectionStd ImportsSection = iota
// ImportsSectionExternal represents the External Packages `imports` section.
ImportsSectionExternal
// ImportsSectionLocal represents the local Packages `imports` section.
ImportsSectionLocal
)
// NewImportsSection returns the value representing the corresponding Imports
// section.
func NewImportsSection(path, localPathPrefix string) ImportsSection {
if !strings.Contains(path, ".") {
return ImportsSectionStd
}
if strings.HasPrefix(strings.Replace(path, "\"", "", -1), localPathPrefix) {
return ImportsSectionLocal
}
return ImportsSectionExternal
}
// NewImportsSectionMachine returns a new ImportsSectionMachine with the
// initial state as `start`.
func NewImportsSectionMachine(start ImportsSection) (*ImportsSectionMachine, error) {
// FIXME: implement tests
c, err := NewImportsTransition(start)
if err != nil {
return nil, err
}
return &ImportsSectionMachine{current: c}, nil
}
// NewImportsTransition returns a new transition corresponding to the received
// value.
func NewImportsTransition(s ImportsSection) (ImportsTransition, error) {
switch s {
case ImportsSectionStd:
return standardImportsTransition{}, nil
case ImportsSectionExternal:
return externalImportsTransition{}, nil
case ImportsSectionLocal:
return localImportsTransition{}, nil
}
return nil, errors.New("invalid imports value")
}
// NewImportsValidator returns a new instalce of ImporstValidator with the
// local path prefix set.
func NewImportsValidator(localPath string) ImportsValidator {
return ImportsValidator{localPath: localPath}
}
//-
// Current returns the current state.
func (s *ImportsSectionMachine) Current() ImportsTransition {
return s.current
}
// Previous returns the previous state.
func (s *ImportsSectionMachine) Previous() ImportsTransition {
return s.previous
}
// Transition updates the internal state.
func (s *ImportsSectionMachine) Transition(next ImportsSection) error {
var (
res ImportsTransition
err error
)
switch next {
case ImportsSectionStd:
res, err = s.current.Standard()
case ImportsSectionExternal:
res, err = s.current.External()
case ImportsSectionLocal:
res, err = s.current.Local()
default:
err = errors.Errorf("invalid imports value: %d", next)
}
if err != nil {
return err
}
s.previous = s.current
s.current = res
return nil
}
//-
// Validate makes sure the implemented `imports` declaration satisfies the
// following rules:
// * Group declaration is parenthesized
// * Packages are separated by a breaking line like this:
// * First standard packages,
// * Next external packages, and
// * Finally local packages
func (i *ImportsValidator) Validate(v *ast.GenDecl, fset *token.FileSet) error {
if !v.Lparen.IsValid() {
return errors.Wrap(errors.New("expected parenthesized declaration"), fset.PositionFor(v.Pos(), false).String())
}
lastLine := fset.PositionFor(v.Pos(), false).Line
for _, t := range v.Specs {
errPrefix := fset.PositionFor(t.Pos(), false).String()
s, ok := t.(*ast.ImportSpec)
if !ok {
return errors.Wrap(errors.Errorf("invalid token %+v", t), errPrefix)
}
section := NewImportsSection(s.Path.Value, i.localPath)
if i.fsm == nil {
fsm, err := NewImportsSectionMachine(section)
if err != nil {
return errors.Wrap(errors.Errorf("invalid imports found: %s", err), errPrefix)
}
i.fsm = fsm
}
if err := i.fsm.Transition(section); err != nil {
return errors.Wrap(err, errPrefix)
}
newLine := fset.PositionFor(t.Pos(), false).Line
if i.fsm.Current() == i.fsm.Previous() {
if lastLine+1 != newLine {
return errors.Wrap(errors.New("extra line break in section"), errPrefix)
}
} else {
if lastLine+1 == newLine {
return errors.Wrap(errors.New("missing line break in section"), errPrefix)
}
}
lastLine = newLine
}
return nil
}
//-
func (externalImportsTransition) External() (ImportsTransition, error) {
return externalImportsTransition{}, nil
}
func (externalImportsTransition) Local() (ImportsTransition, error) {
return localImportsTransition{}, nil
}
func (externalImportsTransition) Standard() (ImportsTransition, error) {
return nil, errors.New("standard imports is invalid, next one must be external or local")
}
//-
func (localImportsTransition) External() (ImportsTransition, error) {
return nil, errors.New("external imports is invalid, next one must be local")
}
func (localImportsTransition) Local() (ImportsTransition, error) {
return localImportsTransition{}, nil
}
func (localImportsTransition) Standard() (ImportsTransition, error) {
return nil, errors.New("standard imports is invalid, next one must be local")
}
//-
func (standardImportsTransition) External() (ImportsTransition, error) {
return externalImportsTransition{}, nil
}
func (standardImportsTransition) Local() (ImportsTransition, error) {
return localImportsTransition{}, nil
}
func (standardImportsTransition) Standard() (ImportsTransition, error) {
return standardImportsTransition{}, nil
}