GoPLS Viewer

Home|gopls/go/analysis/passes/timeformat/timeformat.go
1// Copyright 2022 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package timeformat defines an Analyzer that checks for the use
6// of time.Format or time.Parse calls with a bad format.
7package timeformat
8
9import (
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
22const badFormat = "2006-02-01"
23const goodFormat = "2006-01-02"
24
25const Doc = `check for calls of (time.Time).Format or time.Parse with 2006-02-01
26
27The timeformat checker looks for time formats with the 2006-02-01 (yyyy-dd-mm)
28format. Internationally, "yyyy-dd-mm" does not occur in common calendar date
29standards, and so it is more likely that 2006-01-02 (yyyy-mm-dd) was intended.
30`
31
32var Analyzer = &analysis.Analyzer{
33    Name:     "timeformat",
34    Doc:      Doc,
35    Requires: []*analysis.Analyzer{inspect.Analyzer},
36    Run:      run,
37}
38
39func 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        fnok := typeutil.Callee(pass.TypesInfocall).(*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.TypesInfoarg)
57
58            if badAt > -1 {
59                // Check if it's a literal string, otherwise we can't suggest a fix.
60                if _ok := arg.(*ast.BasicLit); ok {
61                    pos := int(arg.Pos()) + badAt + 1 // +1 to skip the " or `
62                    end := pos + len(badFormat)
63
64                    pass.Report(analysis.Diagnostic{
65                        Pos:     token.Pos(pos),
66                        End:     token.Pos(end),
67                        MessagebadFormat + " 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 nilnil
84}
85
86func isTimeDotFormat(f *types.Funcbool {
87    if f.Name() != "Format" || f.Pkg().Path() != "time" {
88        return false
89    }
90    sigok := f.Type().(*types.Signature)
91    if !ok {
92        return false
93    }
94    // Verify that the receiver is time.Time.
95    recv := sig.Recv()
96    if recv == nil {
97        return false
98    }
99    namedok := recv.Type().(*types.Named)
100    return ok && named.Obj().Name() == "Time"
101}
102
103func isTimeDotParse(f *types.Funcbool {
104    if f.Name() != "Parse" || f.Pkg().Path() != "time" {
105        return false
106    }
107    // Verify that there is no receiver.
108    sigok := f.Type().(*types.Signature)
109    return ok && sig.Recv() == nil
110}
111
112// badFormatAt return the start of a bad format in e or -1 if no bad format is found.
113func badFormatAt(info *types.Infoe ast.Exprint {
114    tvok := info.Types[e]
115    if !ok { // no type info, assume good
116        return -1
117    }
118
119    tok := 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
MembersX
isTimeDotParse.f
constant
types
strings
inspector
badFormat
run.nodeFilter
isTimeDotFormat
badFormatAt.info
badFormatAt.e
analysis
typeutil
goodFormat
run
run.pass
ast
inspect
run.BlockStmt.BlockStmt.badAt
isTimeDotFormat.f
badFormatAt
token
Doc
isTimeDotFormat.recv
isTimeDotParse
Members
X