...
1
2
3
4
5
6
7 package timeformat
8
9 import (
10 _ "embed"
11 "go/ast"
12 "go/constant"
13 "go/token"
14 "go/types"
15 "strings"
16
17 "golang.org/x/tools/go/analysis"
18 "golang.org/x/tools/go/analysis/passes/inspect"
19 "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
20 "golang.org/x/tools/go/ast/inspector"
21 "golang.org/x/tools/go/types/typeutil"
22 )
23
24 const badFormat = "2006-02-01"
25 const goodFormat = "2006-01-02"
26
27
28 var doc string
29
30 var Analyzer = &analysis.Analyzer{
31 Name: "timeformat",
32 Doc: analysisutil.MustExtractDoc(doc, "timeformat"),
33 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/timeformat",
34 Requires: []*analysis.Analyzer{inspect.Analyzer},
35 Run: run,
36 }
37
38 func run(pass *analysis.Pass) (interface{}, error) {
39
40
41
42
43
44 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
45
46 nodeFilter := []ast.Node{
47 (*ast.CallExpr)(nil),
48 }
49 inspect.Preorder(nodeFilter, func(n ast.Node) {
50 call := n.(*ast.CallExpr)
51 fn, ok := typeutil.Callee(pass.TypesInfo, call).(*types.Func)
52 if !ok {
53 return
54 }
55 if !isTimeDotFormat(fn) && !isTimeDotParse(fn) {
56 return
57 }
58 if len(call.Args) > 0 {
59 arg := call.Args[0]
60 badAt := badFormatAt(pass.TypesInfo, arg)
61
62 if badAt > -1 {
63
64 if _, ok := arg.(*ast.BasicLit); ok {
65 pos := int(arg.Pos()) + badAt + 1
66 end := pos + len(badFormat)
67
68 pass.Report(analysis.Diagnostic{
69 Pos: token.Pos(pos),
70 End: token.Pos(end),
71 Message: badFormat + " should be " + goodFormat,
72 SuggestedFixes: []analysis.SuggestedFix{{
73 Message: "Replace " + badFormat + " with " + goodFormat,
74 TextEdits: []analysis.TextEdit{{
75 Pos: token.Pos(pos),
76 End: token.Pos(end),
77 NewText: []byte(goodFormat),
78 }},
79 }},
80 })
81 } else {
82 pass.Reportf(arg.Pos(), badFormat+" should be "+goodFormat)
83 }
84 }
85 }
86 })
87 return nil, nil
88 }
89
90 func isTimeDotFormat(f *types.Func) bool {
91 if f.Name() != "Format" || f.Pkg() == nil || f.Pkg().Path() != "time" {
92 return false
93 }
94
95 recv := f.Type().(*types.Signature).Recv()
96 return recv != nil && analysisutil.IsNamedType(recv.Type(), "time", "Time")
97 }
98
99 func isTimeDotParse(f *types.Func) bool {
100 return analysisutil.IsFunctionNamed(f, "time", "Parse")
101 }
102
103
104 func badFormatAt(info *types.Info, e ast.Expr) int {
105 tv, ok := info.Types[e]
106 if !ok {
107 return -1
108 }
109
110 t, ok := tv.Type.(*types.Basic)
111 if !ok || t.Info()&types.IsString == 0 {
112 return -1
113 }
114
115 if tv.Value == nil {
116 return -1
117 }
118
119 return strings.Index(constant.StringVal(tv.Value), badFormat)
120 }
121
View as plain text