Skip to content

Commit 5d01549

Browse files
committed
wip: support for attribute and value constraints
Signed-off-by: Bryce Palmer <[email protected]>
1 parent 747e58e commit 5d01549

File tree

5 files changed

+149
-23
lines changed

5 files changed

+149
-23
lines changed

pkg/analysis/forbiddenmarkers/analyzer.go

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,46 @@ import (
3131
const name = "forbiddenmarkers"
3232

3333
type analyzer struct {
34-
forbiddenMarkers []string
34+
forbiddenMarkers []config.ForbiddenMarker
35+
}
36+
37+
// ForbiddenMarkersOptions is a function that configures the
38+
// forbiddenmarkers analysis.Analyzer
39+
type ForbiddenMarkersOption func(a *analysis.Analyzer)
40+
41+
// WithName sets the name of the forbiddenmarkers analysis.Analyzer
42+
func WithName(name string) ForbiddenMarkersOption {
43+
return func(a *analysis.Analyzer) {
44+
a.Name = name
45+
}
46+
}
47+
48+
// WithDoc sets the doc string of the forbiddenmarkers analysis.Analyzer
49+
func WithDoc(doc string) ForbiddenMarkersOption {
50+
return func(a *analysis.Analyzer) {
51+
a.Doc = doc
52+
}
3553
}
3654

3755
// NewAnalyzer creates a new analysis.Analyzer for the forbiddenmarkers
3856
// linter based on the provided config.ForbiddenMarkersConfig.
39-
func NewAnalyzer(cfg config.ForbiddenMarkersConfig) *analysis.Analyzer {
57+
func NewAnalyzer(cfg config.ForbiddenMarkersConfig, opts ...ForbiddenMarkersOption) *analysis.Analyzer {
4058
a := &analyzer{
4159
forbiddenMarkers: cfg.Markers,
4260
}
4361

44-
return &analysis.Analyzer{
62+
analyzer := &analysis.Analyzer{
4563
Name: name,
4664
Doc: "Check that no forbidden markers are present on types and fields.",
4765
Run: a.run,
4866
Requires: []*analysis.Analyzer{inspector.Analyzer},
4967
}
68+
69+
for _, opt := range opts {
70+
opt(analyzer)
71+
}
72+
73+
return analyzer
5074
}
5175

5276
func (a *analyzer) run(pass *analysis.Pass) (any, error) {
@@ -66,7 +90,7 @@ func (a *analyzer) run(pass *analysis.Pass) (any, error) {
6690
return nil, nil //nolint:nilnil
6791
}
6892

69-
func checkField(pass *analysis.Pass, field *ast.Field, markersAccess markers.Markers, forbiddenMarkers []string) {
93+
func checkField(pass *analysis.Pass, field *ast.Field, markersAccess markers.Markers, forbiddenMarkers []config.ForbiddenMarker) {
7094
if field == nil || len(field.Names) == 0 {
7195
return
7296
}
@@ -75,7 +99,7 @@ func checkField(pass *analysis.Pass, field *ast.Field, markersAccess markers.Mar
7599
check(markers, forbiddenMarkers, reportField(pass, field))
76100
}
77101

78-
func checkType(pass *analysis.Pass, typeSpec *ast.TypeSpec, markersAccess markers.Markers, forbiddenMarkers []string) {
102+
func checkType(pass *analysis.Pass, typeSpec *ast.TypeSpec, markersAccess markers.Markers, forbiddenMarkers []config.ForbiddenMarker) {
79103
if typeSpec == nil {
80104
return
81105
}
@@ -84,15 +108,52 @@ func checkType(pass *analysis.Pass, typeSpec *ast.TypeSpec, markersAccess marker
84108
check(markers, forbiddenMarkers, reportType(pass, typeSpec))
85109
}
86110

87-
func check(markerSet markers.MarkerSet, forbiddenMarkers []string, reportFunc func(marker markers.Marker)) {
111+
func check(markerSet markers.MarkerSet, forbiddenMarkers []config.ForbiddenMarker, reportFunc func(marker markers.Marker)) {
88112
for _, marker := range forbiddenMarkers {
89-
marks := markerSet.Get(marker)
113+
marks := markerSet.Get(marker.Identifier)
90114
for _, mark := range marks {
91-
reportFunc(mark)
115+
if markerMatchesAttributeRules(mark, marker.Attributes...) {
116+
reportFunc(mark)
117+
}
92118
}
93119
}
94120
}
95121

122+
// TODO: this should probably return some representation of the marker that is failing the
123+
// attribute rules so that it can be returned to users helpfully.
124+
func markerMatchesAttributeRules(marker markers.Marker, attrRules ...config.ForbiddenMarkerAttribute) bool {
125+
matchesAll := true
126+
for _, attrRule := range attrRules {
127+
if val, ok := marker.Expressions[attrRule.Attribute]; ok {
128+
// if no values are specified, that means the existence match is enough
129+
// and we can continue to the next rule
130+
if len(attrRule.Values) == 0 {
131+
continue
132+
}
133+
134+
// if the value doesn't match one of the forbidden ones, this marker is not forbidden
135+
matchesOneValue := false
136+
for _, value := range attrRule.Values {
137+
if val == value {
138+
matchesOneValue = true
139+
break
140+
}
141+
}
142+
143+
if !matchesOneValue {
144+
matchesAll = false
145+
break
146+
}
147+
}
148+
// if the marker doesn't contain the attribute for a specified rule it fails the AND
149+
// operation.
150+
matchesAll = false
151+
break
152+
}
153+
154+
return matchesAll
155+
}
156+
96157
func reportField(pass *analysis.Pass, field *ast.Field) func(marker markers.Marker) {
97158
return func(marker markers.Marker) {
98159
pass.Report(analysis.Diagnostic{

pkg/analysis/forbiddenmarkers/analyzer_test.go

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,63 @@ import (
2626
func TestWithConfiguration(t *testing.T) {
2727
testdata := analysistest.TestData()
2828
analysistest.RunWithSuggestedFixes(t, testdata, forbiddenmarkers.NewAnalyzer(config.ForbiddenMarkersConfig{
29-
Markers: []string{
30-
"forbidden",
29+
Markers: []config.ForbiddenMarker{
30+
{
31+
Identifier: "custom:forbidden",
32+
},
33+
{
34+
Identifier: "custom:AttrNoValues",
35+
Attributes: []config.ForbiddenMarkerAttribute{
36+
{
37+
Attribute: "fruit",
38+
},
39+
},
40+
},
41+
{
42+
Identifier: "custom:AttrValues",
43+
Attributes: []config.ForbiddenMarkerAttribute{
44+
{
45+
Attribute: "fruit",
46+
Values: []string{
47+
"apple",
48+
"orange",
49+
"banana",
50+
},
51+
},
52+
},
53+
},
54+
{
55+
Identifier: "custom:AttrsNoValues",
56+
Attributes: []config.ForbiddenMarkerAttribute{
57+
{
58+
Attribute: "fruit",
59+
},
60+
{
61+
Attribute: "color",
62+
},
63+
},
64+
},
65+
{
66+
Identifier: "custom:AttrsValues",
67+
Attributes: []config.ForbiddenMarkerAttribute{
68+
{
69+
Attribute: "fruit",
70+
Values: []string{
71+
"apple",
72+
"orange",
73+
"banana",
74+
},
75+
},
76+
{
77+
Attribute: "color",
78+
Values: []string{
79+
"red",
80+
"blue",
81+
"green",
82+
},
83+
},
84+
},
85+
},
3186
},
3287
}), "a/...")
3388
}

pkg/analysis/forbiddenmarkers/testdata/src/a/a.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
package a
22

3-
// +forbidden
4-
type ForbiddenMarkerType string // want `type ForbiddenMarkerType has forbidden marker "forbidden"`
3+
// +custom:forbidden
4+
type ForbiddenMarkerType string // want `type ForbiddenMarkerType has forbidden marker "custom:forbidden"`
5+
6+
// +custom:AttrNoValues:fruit=apple
7+
type ForbiddenMarkerWithAttrType string // want `type ForbiddenMarkerWithAttrType has forbidden marker "forbidden:AttrNoValues"`
58

69
// +allowed
710
type AllowedMarkerType string
811

12+
// +custom:AttrNoValues:color=blue
13+
type AllowedMarkerWithAttrType string
14+
915
type Test struct {
10-
// +forbidden
11-
ForbiddenMarkerField string `json:"forbiddenMarkerField"`// want `field ForbiddenMarkerField has forbidden marker "forbidden"`
16+
// +custom:forbidden
17+
ForbiddenMarkerField string `json:"forbiddenMarkerField"`// want `field ForbiddenMarkerField has forbidden marker "custom:forbidden"`
1218

1319
ForbiddenMarkerFieldTypeAlias ForbiddenMarkerType `json:"forbiddenMarkerFieldTypeAlias"` // want `field ForbiddenMarkerFieldTypeAlias has forbidden marker "forbidden"`
1420

@@ -17,4 +23,3 @@ type Test struct {
1723

1824
AllowedMarkerFieldTypeAlias AllowedMarkerType `json:"AllowedMarkerFieldTypeAlias"`
1925
}
20-

pkg/analysis/forbiddenmarkers/testdata/src/a/a.go.golden

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package a
22

3-
type ForbiddenMarkerType string // want `type ForbiddenMarkerType has forbidden marker "forbidden"`
3+
type ForbiddenMarkerType string // want `type ForbiddenMarkerType has forbidden marker "custom:forbidden"`
4+
5+
type ForbiddenMarkerWithAttrType string // want `type ForbiddenMarkerWithAttrType has forbidden marker "forbidden:AttrNoValues"`
46

57
// +allowed
68
type AllowedMarkerType string
79

10+
// +custom:AttrNoValues:color=blue
11+
type AllowedMarkerWithAttrType string
12+
813
type Test struct {
9-
ForbiddenMarkerField string `json:"forbiddenMarkerField"`// want `field ForbiddenMarkerField has forbidden marker "forbidden"`
14+
ForbiddenMarkerField string `json:"forbiddenMarkerField"`// want `field ForbiddenMarkerField has forbidden marker "custom:forbidden"`
1015

1116
ForbiddenMarkerFieldTypeAlias ForbiddenMarkerType `json:"forbiddenMarkerFieldTypeAlias"` // want `field ForbiddenMarkerFieldTypeAlias has forbidden marker "forbidden"`
1217

pkg/analysis/nonullable/analyzer.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,16 @@ import (
2222
)
2323

2424
const name = "nonullable"
25+
const doc = "Check that nullable marker is not present on any types or fields."
2526

2627
func newAnalyzer() *analysis.Analyzer {
2728
analyzer := forbiddenmarkers.NewAnalyzer(config.ForbiddenMarkersConfig{
28-
Markers: []string{
29-
"nullable",
29+
Markers: []config.ForbiddenMarker{
30+
{
31+
Identifier: "nullable",
32+
},
3033
},
31-
})
32-
33-
analyzer.Name = name
34-
analyzer.Doc = "Check that nullable marker is not present on any types or fields."
34+
}, forbiddenmarkers.WithName(name), forbiddenmarkers.WithDoc(doc))
3535

3636
return analyzer
3737
}

0 commit comments

Comments
 (0)