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