-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathmain.go
More file actions
176 lines (162 loc) · 5.17 KB
/
main.go
File metadata and controls
176 lines (162 loc) · 5.17 KB
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
package main
import (
"context"
"errors"
"fmt"
"log"
"strings"
"buf.build/go/bufplugin/check"
"buf.build/go/bufplugin/descriptor"
"github.com/aep-dev/api-linter/lint"
"github.com/aep-dev/api-linter/rules"
"github.com/aep-dev/api-linter/internal/desc"
"google.golang.org/protobuf/reflect/protoreflect"
)
const (
aepCategoryID = "AEP"
aepCoreCategoryID = "AEP_CORE"
)
type fileDescriptorsContextKey struct{}
func main() {
spec, err := newSpec()
if err != nil {
log.Fatalln(err)
}
// AEP rules cannot be run in parallel as there is thread-unsafe code in
// this repository that causes concurrent read and write access to a map.
check.Main(spec, check.MainWithParallelism(1))
}
func newSpec() (*check.Spec, error) {
ruleRegistry := lint.NewRuleRegistry()
if err := rules.Add(ruleRegistry); err != nil {
return nil, err
}
ruleSpecs := make([]*check.RuleSpec, 0, len(ruleRegistry))
for _, protoRule := range ruleRegistry {
ruleSpec, err := newRuleSpec(protoRule)
if err != nil {
return nil, err
}
ruleSpecs = append(ruleSpecs, ruleSpec)
}
return &check.Spec{
Rules: ruleSpecs,
Categories: []*check.CategorySpec{
{
ID: aepCategoryID,
Purpose: "Checks all API Enhancement proposals as specified at https://aep.dev.",
},
{
ID: aepCoreCategoryID,
Purpose: "Checks all core API Enhancement proposals as specified at https://aep.dev.",
},
},
Before: before,
}, nil
}
func newRuleSpec(protoRule lint.ProtoRule) (*check.RuleSpec, error) {
ruleName := protoRule.GetName()
if !ruleName.IsValid() {
return nil, fmt.Errorf("lint.RuleName is invalid: %q", ruleName)
}
split := strings.Split(string(ruleName), "::")
if len(split) != 3 {
return nil, fmt.Errorf("unknown lint.RuleName format, expected three parts split by '::' : %q", ruleName)
}
categoryIDs := []string{aepCategoryID}
switch extraCategoryID := split[0]; extraCategoryID {
case "core":
categoryIDs = append(categoryIDs, aepCoreCategoryID)
default:
return nil, fmt.Errorf("unknown lint.RuleName format: unknown category %q : %q", extraCategoryID, ruleName)
}
// The allowed characters for RuleName are a-z, 0-9, -.
// The separator :: is also allowed.
// We do a translation of these into valid check.Rule IDs.
ruleID := "AEP_" + strings.Join(split[1:3], "_")
ruleID = strings.ReplaceAll(ruleID, "-", "_")
ruleID = strings.ToUpper(ruleID)
return &check.RuleSpec{
ID: ruleID,
CategoryIDs: categoryIDs,
Default: true,
Purpose: fmt.Sprintf("Checks AEP rule %s.", ruleName),
Type: check.RuleTypeLint,
Handler: newRuleHandler(protoRule),
}, nil
}
func newRuleHandler(protoRule lint.ProtoRule) check.RuleHandler {
return check.RuleHandlerFunc(
func(ctx context.Context, responseWriter check.ResponseWriter, request check.Request) error {
fileDescriptors, _ := ctx.Value(fileDescriptorsContextKey{}).([]*desc.FileDescriptor)
for _, fileDescriptor := range fileDescriptors {
for _, problem := range protoRule.Lint(fileDescriptor) {
if err := addProblem(responseWriter, problem); err != nil {
return err
}
}
}
return nil
},
)
}
func addProblem(responseWriter check.ResponseWriter, problem lint.Problem) error {
addAnnotationOptions := []check.AddAnnotationOption{
check.WithMessage(problem.Message),
}
descriptor := problem.Descriptor
if descriptor == nil {
// This should never happen.
return errors.New("got nil problem.Descriptor")
}
fileDescriptor := descriptor.GetFile()
if fileDescriptor == nil {
// If we do not have a FileDescriptor, we cannot report a location.
responseWriter.AddAnnotation(addAnnotationOptions...)
return nil
}
// If a location is available from the problem, we use that directly.
if location := problem.Location; location != nil {
addAnnotationOptions = append(
addAnnotationOptions,
check.WithFileNameAndSourcePath(
fileDescriptor.GetName(),
protoreflect.SourcePath(location.GetPath()),
),
)
} else {
// Otherwise we check the source info for the descriptor from the problem.
if location := descriptor.GetSourceInfo(); location != nil {
addAnnotationOptions = append(
addAnnotationOptions,
check.WithFileNameAndSourcePath(
fileDescriptor.GetName(),
protoreflect.SourcePath(location.GetPath()),
),
)
}
}
responseWriter.AddAnnotation(addAnnotationOptions...)
return nil
}
func before(ctx context.Context, request check.Request) (context.Context, check.Request, error) {
fileDescriptors, err := nonImportFileDescriptorsForFileDescriptors(request.FileDescriptors())
if err != nil {
return nil, nil, err
}
ctx = context.WithValue(ctx, fileDescriptorsContextKey{}, fileDescriptors)
return ctx, request, nil
}
func nonImportFileDescriptorsForFileDescriptors(fileDescriptors []descriptor.FileDescriptor) ([]*desc.FileDescriptor, error) {
if len(fileDescriptors) == 0 {
return nil, nil
}
reflectFileDescriptors := make([]protoreflect.FileDescriptor, 0, len(fileDescriptors))
for _, fileDescriptor := range fileDescriptors {
if fileDescriptor.IsImport() {
continue
}
reflectFileDescriptors = append(reflectFileDescriptors, fileDescriptor.ProtoreflectFileDescriptor())
}
return desc.WrapFiles(reflectFileDescriptors)
}