forked from bufbuild/buf
-
Notifications
You must be signed in to change notification settings - Fork 0
/
util.go
288 lines (268 loc) · 8.43 KB
/
util.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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
// Copyright 2020-2023 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package buflintcheck
import (
"strings"
"github.com/bufbuild/buf/private/bufpkg/bufanalysis"
"github.com/bufbuild/buf/private/bufpkg/bufcheck/internal"
"github.com/bufbuild/buf/private/pkg/protosource"
"github.com/bufbuild/buf/private/pkg/stringutil"
)
// addFunc adds a FileAnnotation.
//
// Both the Descriptor and Locations can be nil.
type addFunc func(protosource.Descriptor, protosource.Location, []protosource.Location, string, ...interface{})
func fieldToLowerSnakeCase(s string) string {
// Try running this on googleapis and watch
// We allow both effectively by not passing the option
//return stringutil.ToLowerSnakeCase(s, stringutil.SnakeCaseWithNewWordOnDigits())
return stringutil.ToLowerSnakeCase(s)
}
func fieldToUpperSnakeCase(s string) string {
// Try running this on googleapis and watch
// We allow both effectively by not passing the option
//return stringutil.ToUpperSnakeCase(s, stringutil.SnakeCaseWithNewWordOnDigits())
return stringutil.ToUpperSnakeCase(s)
}
// validLeadingComment returns true if comment has at least one line that isn't empty
// and doesn't start with CommentIgnorePrefix.
func validLeadingComment(comment string) bool {
for _, line := range strings.Split(comment, "\n") {
line = strings.TrimSpace(line)
if line != "" && !strings.HasPrefix(line, CommentIgnorePrefix) {
return true
}
}
return false
}
// Returns the usedPackageList if there is an import cycle.
//
// Note this stops on the first import cycle detected, it doesn't attempt to get all of them - not perfect.
func getImportCycleIfExists(
// Should never be ""
pkg string,
packageToDirectlyImportedPackageToFileImports map[string]map[string][]protosource.FileImport,
usedPackageMap map[string]struct{},
usedPackageList []string,
) []string {
// Append before checking so that the returned import cycle is actually a cycle
usedPackageList = append(usedPackageList, pkg)
if _, ok := usedPackageMap[pkg]; ok {
// We have an import cycle, but if the first package in the list does not
// equal the last, do not return as an import cycle unless the first
// element equals the last - we do DFS from each package so this will
// be picked up separately
if usedPackageList[0] == usedPackageList[len(usedPackageList)-1] {
return usedPackageList
}
return nil
}
usedPackageMap[pkg] = struct{}{}
// Will never equal pkg
for directlyImportedPackage := range packageToDirectlyImportedPackageToFileImports[pkg] {
// Can equal "" per the function signature of PackageToDirectlyImportedPackageToFileImports
if directlyImportedPackage == "" {
continue
}
if importCycle := getImportCycleIfExists(
directlyImportedPackage,
packageToDirectlyImportedPackageToFileImports,
usedPackageMap,
usedPackageList,
); len(importCycle) != 0 {
return importCycle
}
}
delete(usedPackageMap, pkg)
return nil
}
func newFilesCheckFunc(
f func(addFunc, []protosource.File) error,
) func(string, internal.IgnoreFunc, []protosource.File) ([]bufanalysis.FileAnnotation, error) {
return func(id string, ignoreFunc internal.IgnoreFunc, files []protosource.File) ([]bufanalysis.FileAnnotation, error) {
helper := internal.NewHelper(id, ignoreFunc)
if err := f(helper.AddFileAnnotationWithExtraIgnoreLocationsf, files); err != nil {
return nil, err
}
return helper.FileAnnotations(), nil
}
}
func newPackageToFilesCheckFunc(
f func(add addFunc, pkg string, files []protosource.File) error,
) func(string, internal.IgnoreFunc, []protosource.File) ([]bufanalysis.FileAnnotation, error) {
return newFilesCheckFunc(
func(add addFunc, files []protosource.File) error {
packageToFiles, err := protosource.PackageToFiles(files...)
if err != nil {
return err
}
for pkg, files := range packageToFiles {
if err := f(add, pkg, files); err != nil {
return err
}
}
return nil
},
)
}
func newDirToFilesCheckFunc(
f func(add addFunc, dirPath string, files []protosource.File) error,
) func(string, internal.IgnoreFunc, []protosource.File) ([]bufanalysis.FileAnnotation, error) {
return newFilesCheckFunc(
func(add addFunc, files []protosource.File) error {
dirPathToFiles, err := protosource.DirPathToFiles(files...)
if err != nil {
return err
}
for dirPath, files := range dirPathToFiles {
if err := f(add, dirPath, files); err != nil {
return err
}
}
return nil
},
)
}
func newFileCheckFunc(
f func(addFunc, protosource.File) error,
) func(string, internal.IgnoreFunc, []protosource.File) ([]bufanalysis.FileAnnotation, error) {
return newFilesCheckFunc(
func(add addFunc, files []protosource.File) error {
for _, file := range files {
if err := f(add, file); err != nil {
return err
}
}
return nil
},
)
}
func newFileImportCheckFunc(
f func(addFunc, protosource.FileImport) error,
) func(string, internal.IgnoreFunc, []protosource.File) ([]bufanalysis.FileAnnotation, error) {
return newFileCheckFunc(
func(add addFunc, file protosource.File) error {
for _, fileImport := range file.FileImports() {
if err := f(add, fileImport); err != nil {
return err
}
}
return nil
},
)
}
func newEnumCheckFunc(
f func(addFunc, protosource.Enum) error,
) func(string, internal.IgnoreFunc, []protosource.File) ([]bufanalysis.FileAnnotation, error) {
return newFileCheckFunc(
func(add addFunc, file protosource.File) error {
return protosource.ForEachEnum(
func(enum protosource.Enum) error {
return f(add, enum)
},
file,
)
},
)
}
func newEnumValueCheckFunc(
f func(addFunc, protosource.EnumValue) error,
) func(string, internal.IgnoreFunc, []protosource.File) ([]bufanalysis.FileAnnotation, error) {
return newEnumCheckFunc(
func(add addFunc, enum protosource.Enum) error {
for _, enumValue := range enum.Values() {
if err := f(add, enumValue); err != nil {
return err
}
}
return nil
},
)
}
func newMessageCheckFunc(
f func(addFunc, protosource.Message) error,
) func(string, internal.IgnoreFunc, []protosource.File) ([]bufanalysis.FileAnnotation, error) {
return newFileCheckFunc(
func(add addFunc, file protosource.File) error {
return protosource.ForEachMessage(
func(message protosource.Message) error {
return f(add, message)
},
file,
)
},
)
}
func newFieldCheckFunc(
f func(addFunc, protosource.Field) error,
) func(string, internal.IgnoreFunc, []protosource.File) ([]bufanalysis.FileAnnotation, error) {
return newMessageCheckFunc(
func(add addFunc, message protosource.Message) error {
for _, field := range message.Fields() {
if err := f(add, field); err != nil {
return err
}
}
// TODO: is this right?
for _, field := range message.Extensions() {
if err := f(add, field); err != nil {
return err
}
}
return nil
},
)
}
func newOneofCheckFunc(
f func(addFunc, protosource.Oneof) error,
) func(string, internal.IgnoreFunc, []protosource.File) ([]bufanalysis.FileAnnotation, error) {
return newMessageCheckFunc(
func(add addFunc, message protosource.Message) error {
for _, oneof := range message.Oneofs() {
if err := f(add, oneof); err != nil {
return err
}
}
return nil
},
)
}
func newServiceCheckFunc(
f func(addFunc, protosource.Service) error,
) func(string, internal.IgnoreFunc, []protosource.File) ([]bufanalysis.FileAnnotation, error) {
return newFileCheckFunc(
func(add addFunc, file protosource.File) error {
for _, service := range file.Services() {
if err := f(add, service); err != nil {
return err
}
}
return nil
},
)
}
func newMethodCheckFunc(
f func(addFunc, protosource.Method) error,
) func(string, internal.IgnoreFunc, []protosource.File) ([]bufanalysis.FileAnnotation, error) {
return newServiceCheckFunc(
func(add addFunc, service protosource.Service) error {
for _, method := range service.Methods() {
if err := f(add, method); err != nil {
return err
}
}
return nil
},
)
}