@@ -17,14 +17,17 @@ limitations under the License.
17
17
package notimestamp
18
18
19
19
import (
20
+ "fmt"
20
21
"go/ast"
22
+ "go/token"
23
+ "regexp"
24
+ markershelper "sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/markers"
21
25
"strings"
22
26
23
27
"golang.org/x/tools/go/analysis"
24
- "golang.org/x/tools/go/analysis/passes/inspect"
25
- "golang.org/x/tools/go/ast/inspector"
26
28
kalerrors "sigs.k8s.io/kube-api-linter/pkg/analysis/errors"
27
29
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/extractjsontags"
30
+ "sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/inspector"
28
31
"sigs.k8s.io/kube-api-linter/pkg/analysis/utils"
29
32
)
30
33
@@ -35,63 +38,74 @@ const name = "notimestamp"
35
38
// substring are present.
36
39
var Analyzer = & analysis.Analyzer {
37
40
Name : name ,
38
- Doc : "check that non of the struct field named timestamp or contain timestamp as a substring" ,
41
+ Doc : "check that none of the struct field named timestamp or contain timestamp as a substring" ,
39
42
Run : run ,
40
- Requires : []* analysis.Analyzer {inspect . Analyzer , extractjsontags .Analyzer },
43
+ Requires : []* analysis.Analyzer {inspector .Analyzer },
41
44
}
42
45
46
+ // case insensitive regular expression to match 'timestamp' string in field or json tag.
47
+ var timeStampRegEx = regexp .MustCompile ("(?i)timestamp" )
48
+
43
49
func run (pass * analysis.Pass ) (any , error ) {
44
- inspect , ok := pass .ResultOf [inspect .Analyzer ].(* inspector.Inspector )
50
+ inspect , ok := pass .ResultOf [inspector .Analyzer ].(inspector.Inspector )
45
51
if ! ok {
46
52
return nil , kalerrors .ErrCouldNotGetInspector
47
53
}
48
54
49
- jsonTags , ok := pass .ResultOf [extractjsontags .Analyzer ].(extractjsontags.StructFieldTags )
50
- if ! ok {
51
- return nil , kalerrors .ErrCouldNotGetJSONTags
55
+ inspect .InspectFields (func (field * ast.Field , stack []ast.Node , jsonTagInfo extractjsontags.FieldTagInfo , markersAccess markershelper.Markers ) {
56
+ checkFieldsAndTags (pass , field , jsonTagInfo )
57
+ })
58
+
59
+ return nil , nil //nolint:nilnil
60
+ }
61
+
62
+ func checkFieldsAndTags (pass * analysis.Pass , field * ast.Field , tagInfo extractjsontags.FieldTagInfo ) {
63
+ fieldName := utils .FieldName (field )
64
+ if fieldName == "" {
65
+ return
52
66
}
53
67
54
- // Filter to fields so that we can iterate over fields in a struct.
55
- nodeFilter := []ast.Node {
56
- (* ast .Field )(nil ),
68
+ var suggestedFixes []analysis.SuggestedFix
69
+
70
+ // check if filed name contains timestamp in it.
71
+ fieldReplacementName := timeStampRegEx .ReplaceAllString (fieldName , "Time" )
72
+ if fieldReplacementName != fieldName {
73
+ suggestedFixes = append (suggestedFixes , analysis.SuggestedFix {
74
+ Message : fmt .Sprintf ("replace field with Time" ),
75
+ TextEdits : []analysis.TextEdit {
76
+ {
77
+ Pos : field .Pos (),
78
+ NewText : []byte (fieldReplacementName ),
79
+ End : field .Pos () + token .Pos (len (fieldName )),
80
+ },
81
+ },
82
+ })
57
83
}
58
84
59
- // Preorder visits all the nodes of the AST in depth-first order. It calls
60
- // f(n) for each node n before it visits n's children.
61
- //
62
- // We use the filter defined above, ensuring we only look at struct fields.
63
- inspect .Preorder (nodeFilter , func (n ast.Node ) {
64
- field , ok := n .(* ast.Field )
65
- if ! ok {
66
- return
67
- }
68
-
69
- if field == nil || len (field .Names ) == 0 {
70
- return
71
- }
72
-
73
- fieldName := utils .FieldName (field )
74
-
75
- // First check if the struct field name contains 'timestamp'
76
- if strings .Contains (strings .ToLower (fieldName ), "timestamp" ) {
77
- pass .Reportf (field .Pos (),
78
- "field %s: fields with timestamp substring should be avoided" ,
79
- fieldName ,
80
- )
81
-
82
- return
83
- }
84
-
85
- // Then check if the json serialization of the field contains 'timestamp'
86
- tagInfo := jsonTags .FieldTags (field )
87
-
88
- if strings .Contains (strings .ToLower (tagInfo .Name ), "timestamp" ) {
89
- pass .Reportf (field .Pos (),
90
- "field %s: fields with timestamp substring should be avoided" ,
91
- fieldName ,
92
- )
93
- }
94
- })
85
+ // check if the tag contains timestamp in it.
86
+ tagReplacementName := timeStampRegEx .ReplaceAllString (tagInfo .Name , "Time" )
87
+ if strings .HasPrefix (strings .ToLower (tagInfo .Name ), "time" ) {
88
+ // If tag is starts with 'timeStamp', the replacement should be 'time' not 'Time'.
89
+ tagReplacementName = timeStampRegEx .ReplaceAllString (tagInfo .Name , "time" )
90
+ }
91
+ if tagReplacementName != tagInfo .Name {
92
+ suggestedFixes = append (suggestedFixes , analysis.SuggestedFix {
93
+ Message : fmt .Sprintf ("replace json tag with Time" ),
94
+ TextEdits : []analysis.TextEdit {
95
+ {
96
+ Pos : tagInfo .Pos ,
97
+ NewText : []byte (tagReplacementName ),
98
+ End : tagInfo .Pos + token .Pos (len (tagInfo .Name )),
99
+ },
100
+ },
101
+ })
102
+ }
95
103
96
- return nil , nil //nolint:nilnil
104
+ if len (suggestedFixes ) > 0 {
105
+ pass .Report (analysis.Diagnostic {
106
+ Pos : field .Pos (),
107
+ Message : fmt .Sprintf ("field %s: prefer use of the term time over timestamp" , fieldName ),
108
+ SuggestedFixes : suggestedFixes ,
109
+ })
110
+ }
97
111
}
0 commit comments