Skip to content

Commit 00a5c0f

Browse files
authored
Merge pull request #79 from sivchari/add-astfield-util
add FieldName API
2 parents 3e7c663 + c17423e commit 00a5c0f

File tree

12 files changed

+153
-33
lines changed

12 files changed

+153
-33
lines changed

pkg/analysis/commentstart/analyzer.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/extractjsontags"
2828
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/inspector"
2929
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/markers"
30+
"sigs.k8s.io/kube-api-linter/pkg/analysis/utils"
3031
)
3132

3233
const name = "commentstart"
@@ -58,10 +59,8 @@ func checkField(pass *analysis.Pass, field *ast.Field, tagInfo extractjsontags.F
5859
return
5960
}
6061

61-
var fieldName string
62-
if len(field.Names) > 0 {
63-
fieldName = field.Names[0].Name
64-
} else {
62+
fieldName := utils.FieldName(field)
63+
if fieldName == "" {
6564
fieldName = types.ExprString(field.Type)
6665
}
6766

pkg/analysis/helpers/inspector/analyzer_test.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/extractjsontags"
2626
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/inspector"
2727
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/markers"
28+
"sigs.k8s.io/kube-api-linter/pkg/analysis/utils"
2829
)
2930

3031
func TestInspector(t *testing.T) {
@@ -49,14 +50,7 @@ func run(pass *analysis.Pass) (any, error) {
4950
}
5051

5152
inspect.InspectFields(func(field *ast.Field, stack []ast.Node, jsonTagInfo extractjsontags.FieldTagInfo, markersAccess markers.Markers) {
52-
var fieldName string
53-
if len(field.Names) > 0 {
54-
fieldName = field.Names[0].Name
55-
} else if ident, ok := field.Type.(*ast.Ident); ok {
56-
fieldName = ident.Name
57-
}
58-
59-
pass.Reportf(field.Pos(), "field: %v", fieldName)
53+
pass.Reportf(field.Pos(), "field: %v", utils.FieldName(field))
6054

6155
if jsonTagInfo.Name != "" {
6256
pass.Reportf(field.Pos(), "json tag: %v", jsonTagInfo.Name)

pkg/analysis/jsontags/analyzer.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/extractjsontags"
2525
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/inspector"
2626
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/markers"
27+
"sigs.k8s.io/kube-api-linter/pkg/analysis/utils"
2728
"sigs.k8s.io/kube-api-linter/pkg/config"
2829

2930
"golang.org/x/tools/go/analysis"
@@ -75,13 +76,13 @@ func (a *analyzer) run(pass *analysis.Pass) (any, error) {
7576
}
7677

7778
func (a *analyzer) checkField(pass *analysis.Pass, field *ast.Field, tagInfo extractjsontags.FieldTagInfo) {
78-
var prefix string
79-
if len(field.Names) > 0 && field.Names[0] != nil {
80-
prefix = fmt.Sprintf("field %s", field.Names[0].Name)
81-
} else if ident, ok := field.Type.(*ast.Ident); ok {
82-
prefix = fmt.Sprintf("embedded field %s", ident.Name)
79+
prefix := "field %s"
80+
if len(field.Names) == 0 || field.Names[0] == nil {
81+
prefix = "embedded field %s"
8382
}
8483

84+
prefix = fmt.Sprintf(prefix, utils.FieldName(field))
85+
8586
if tagInfo.Missing {
8687
pass.Reportf(field.Pos(), "%s is missing json tag", prefix)
8788
return

pkg/analysis/maxlength/analyzer.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,11 @@ func run(pass *analysis.Pass) (any, error) {
5555
}
5656

5757
func checkField(pass *analysis.Pass, field *ast.Field, markersAccess markershelper.Markers) {
58-
if len(field.Names) == 0 || field.Names[0] == nil {
58+
fieldName := utils.FieldName(field)
59+
if fieldName == "" {
5960
return
6061
}
6162

62-
fieldName := field.Names[0].Name
6363
prefix := fmt.Sprintf("field %s", fieldName)
6464

6565
checkTypeExpr(pass, field.Type, field, nil, markersAccess, prefix, markers.KubebuilderMaxLengthMarker, needsStringMaxLength)

pkg/analysis/nomaps/analyzer.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/extractjsontags"
2727
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/inspector"
2828
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/markers"
29+
"sigs.k8s.io/kube-api-linter/pkg/analysis/utils"
2930
"sigs.k8s.io/kube-api-linter/pkg/config"
3031
)
3132

@@ -81,7 +82,7 @@ func (a *analyzer) checkField(pass *analysis.Pass, field *ast.Field) {
8182
}
8283

8384
if a.policy == config.NoMapsEnforce {
84-
report(pass, field.Pos(), field.Names[0].Name)
85+
report(pass, field.Pos(), utils.FieldName(field))
8586
return
8687
}
8788

@@ -90,7 +91,7 @@ func (a *analyzer) checkField(pass *analysis.Pass, field *ast.Field) {
9091
return
9192
}
9293

93-
report(pass, field.Pos(), field.Names[0].Name)
94+
report(pass, field.Pos(), utils.FieldName(field))
9495
}
9596

9697
if a.policy == config.NoMapsIgnore {
@@ -104,7 +105,7 @@ func (a *analyzer) checkField(pass *analysis.Pass, field *ast.Field) {
104105
return
105106
}
106107

107-
report(pass, field.Pos(), field.Names[0].Name)
108+
report(pass, field.Pos(), utils.FieldName(field))
108109
}
109110
}
110111

pkg/analysis/nophase/analyzer.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"golang.org/x/tools/go/ast/inspector"
2525
kalerrors "sigs.k8s.io/kube-api-linter/pkg/analysis/errors"
2626
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/extractjsontags"
27+
"sigs.k8s.io/kube-api-linter/pkg/analysis/utils"
2728
)
2829

2930
const name = "nophase"
@@ -68,7 +69,7 @@ func run(pass *analysis.Pass) (any, error) {
6869
return
6970
}
7071

71-
fieldName := field.Names[0].Name
72+
fieldName := utils.FieldName(field)
7273

7374
// First check if the struct field name contains 'phase'
7475
if strings.Contains(strings.ToLower(fieldName), "phase") {

pkg/analysis/optionalorrequired/analyzer.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/extractjsontags"
2525
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/inspector"
2626
markershelper "sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/markers"
27+
"sigs.k8s.io/kube-api-linter/pkg/analysis/utils"
2728
"sigs.k8s.io/kube-api-linter/pkg/config"
2829
"sigs.k8s.io/kube-api-linter/pkg/markers"
2930
)
@@ -107,13 +108,13 @@ func (a *analyzer) checkField(pass *analysis.Pass, field *ast.Field, fieldMarker
107108
return
108109
}
109110

110-
var prefix string
111-
if len(field.Names) > 0 && field.Names[0] != nil {
112-
prefix = fmt.Sprintf("field %s", field.Names[0].Name)
113-
} else if ident, ok := field.Type.(*ast.Ident); ok {
114-
prefix = fmt.Sprintf("embedded field %s", ident.Name)
111+
prefix := "field %s"
112+
if len(field.Names) == 0 || field.Names[0] == nil {
113+
prefix = "embedded field %s"
115114
}
116115

116+
prefix = fmt.Sprintf(prefix, utils.FieldName(field))
117+
117118
hasPrimaryOptional := fieldMarkers.Has(a.primaryOptionalMarker)
118119
hasPrimaryRequired := fieldMarkers.Has(a.primaryRequiredMarker)
119120

pkg/analysis/requiredfields/analyzer.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/extractjsontags"
2626
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/inspector"
2727
markershelper "sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/markers"
28+
"sigs.k8s.io/kube-api-linter/pkg/analysis/utils"
2829
"sigs.k8s.io/kube-api-linter/pkg/config"
2930
"sigs.k8s.io/kube-api-linter/pkg/markers"
3031
)
@@ -71,12 +72,11 @@ func (a *analyzer) run(pass *analysis.Pass) (any, error) {
7172
}
7273

7374
func (a *analyzer) checkField(pass *analysis.Pass, field *ast.Field, fieldMarkers markershelper.MarkerSet, fieldTagInfo extractjsontags.FieldTagInfo) {
74-
if field == nil || len(field.Names) == 0 {
75+
fieldName := utils.FieldName(field)
76+
if fieldName == "" {
7577
return
7678
}
7779

78-
fieldName := field.Names[0].Name
79-
8080
if !fieldMarkers.Has(markers.RequiredMarker) && !fieldMarkers.Has(markers.KubebuilderRequiredMarker) {
8181
// The field is not marked required, so we don't need to check it.
8282
return

pkg/analysis/utils/type_check.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,11 @@ func (t *typeChecker) CheckNode(pass *analysis.Pass, node ast.Node) {
6262
}
6363

6464
func (t *typeChecker) checkField(pass *analysis.Pass, field *ast.Field) {
65-
if field == nil || len(field.Names) == 0 || field.Names[0] == nil {
65+
fieldName := FieldName(field)
66+
if fieldName == "" {
6667
return
6768
}
6869

69-
fieldName := field.Names[0].Name
7070
prefix := fmt.Sprintf("field %s", fieldName)
7171

7272
t.checkTypeExpr(pass, field.Type, field, prefix)

pkg/analysis/utils/utils.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,26 @@ func LookupTypeSpec(pass *analysis.Pass, ident *ast.Ident) (*ast.TypeSpec, bool)
7777
return nil, false
7878
}
7979

80+
// FieldName returns the name of the field. If the field has a name, it returns that name.
81+
// If the field is embedded and it can be converted to an identifier, it returns the name of the identifier.
82+
// If it doesn't have a name and can't be converted to an identifier, it returns an empty string.
83+
func FieldName(field *ast.Field) string {
84+
if len(field.Names) > 0 && field.Names[0] != nil {
85+
return field.Names[0].Name
86+
}
87+
88+
switch typ := field.Type.(type) {
89+
case *ast.Ident:
90+
return typ.Name
91+
case *ast.StarExpr:
92+
if ident, ok := typ.X.(*ast.Ident); ok {
93+
return ident.Name
94+
}
95+
}
96+
97+
return ""
98+
}
99+
80100
func getFilesForType(pass *analysis.Pass, ident *ast.Ident) (*token.File, *ast.File) {
81101
namedType, ok := pass.TypesInfo.TypeOf(ident).(*types.Named)
82102
if !ok {
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package utils_test
17+
18+
import (
19+
"testing"
20+
21+
. "github.com/onsi/ginkgo/v2"
22+
. "github.com/onsi/gomega"
23+
)
24+
25+
func TestValidation(t *testing.T) {
26+
RegisterFailHandler(Fail)
27+
RunSpecs(t, "Utils")
28+
}

pkg/analysis/utils/utils_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package utils_test
17+
18+
import (
19+
"go/ast"
20+
21+
. "github.com/onsi/ginkgo/v2"
22+
. "github.com/onsi/gomega"
23+
24+
"sigs.k8s.io/kube-api-linter/pkg/analysis/utils"
25+
)
26+
27+
var _ = Describe("FieldName", func() {
28+
type fieldNameInput struct {
29+
field *ast.Field
30+
want string
31+
}
32+
33+
DescribeTable("Should extract the field name", func(in fieldNameInput) {
34+
Expect(utils.FieldName(in.field)).To(Equal(in.want), "expect to match the extracted field name")
35+
},
36+
Entry("field has Names", fieldNameInput{
37+
field: &ast.Field{
38+
Names: []*ast.Ident{
39+
{
40+
Name: "foo",
41+
},
42+
},
43+
},
44+
want: "foo",
45+
}),
46+
Entry("field has no Names, but is an Ident", fieldNameInput{
47+
field: &ast.Field{
48+
Type: &ast.Ident{
49+
Name: "foo",
50+
},
51+
},
52+
want: "foo",
53+
}),
54+
Entry("field has no Names, but is a StarExpr with an Ident", fieldNameInput{
55+
field: &ast.Field{
56+
Type: &ast.StarExpr{
57+
X: &ast.Ident{
58+
Name: "foo",
59+
},
60+
},
61+
},
62+
want: "foo",
63+
}),
64+
Entry("field has no Names, and is not an Ident or StarExpr", fieldNameInput{
65+
field: &ast.Field{
66+
Type: &ast.ArrayType{
67+
Elt: &ast.Ident{
68+
Name: "foo",
69+
},
70+
},
71+
},
72+
want: "",
73+
}),
74+
)
75+
})

0 commit comments

Comments
 (0)