Skip to content

Commit 80add7a

Browse files
go/tools: add vet check for time formats with 2006-02-01
yyyy-dd-mm is a time format that isn't really used anywhere [1]. It is much more likely that the user intended to use yyyy-mm-dd instead and made a mistake. This happens quite often [2] because of the unusual way to handle time formatting and parsing in Go. Since the mistake is Go specific and happens so often a vet check will be useful. 1. https://stackoverflow.com/questions/2254014/are-there-locales-or-common-programs-that-use-yyyy-dd-mm-as-the-date-format 2. https://github.com/search?l=&p=1&q=%222006-02-01%22+language%3AGo&type=Code
1 parent 5d7ca8a commit 80add7a

File tree

6 files changed

+126
-0
lines changed

6 files changed

+126
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright 2019 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+
// This file contains tests for the timeformat checker.
6+
7+
package a
8+
9+
import (
10+
"time"
11+
)
12+
13+
func hasError() {
14+
a, _ := time.Parse("2006-02-01 15:04:05", "2021-01-01 00:00:00") // want `2006-02-01 should be 2006-01-02`
15+
a.Format("2006-02-01") // want `2006-02-01 should be 2006-01-02`
16+
a.Format("2006-02-01 15:04:05") // want `2006-02-01 should be 2006-01-02`
17+
}
18+
19+
func notHasError() {
20+
a, _ := time.Parse("2006-01-02 15:04:05", "2021-01-01 00:00:00")
21+
a.Format("2006-01-02")
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright 2019 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.
7+
package timeformat
8+
9+
import (
10+
"go/ast"
11+
"go/constant"
12+
"go/types"
13+
"strings"
14+
15+
"golang.org/x/tools/go/analysis"
16+
"golang.org/x/tools/go/analysis/passes/inspect"
17+
"golang.org/x/tools/go/ast/inspector"
18+
"golang.org/x/tools/go/types/typeutil"
19+
)
20+
21+
const Doc = `check for calls of (time.Time).Format or time.Parse with 2006-02-01
22+
23+
The timeformat checker looks time formats with the bad 2006-02-01 format
24+
which should be replaced by the intended 2006-01-02 format.
25+
`
26+
27+
var Analyzer = &analysis.Analyzer{
28+
Name: "timeformat",
29+
Doc: Doc,
30+
Requires: []*analysis.Analyzer{inspect.Analyzer},
31+
Run: run,
32+
}
33+
34+
func run(pass *analysis.Pass) (interface{}, error) {
35+
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
36+
37+
nodeFilter := []ast.Node{
38+
(*ast.CallExpr)(nil),
39+
}
40+
inspect.Preorder(nodeFilter, func(n ast.Node) {
41+
call := n.(*ast.CallExpr)
42+
fn, ok := typeutil.Callee(pass.TypesInfo, call).(*types.Func)
43+
if !ok {
44+
return
45+
}
46+
if (fn.FullName() == "(time.Time).Format" || fn.FullName() == "time.Parse") && isBadFormat(pass, call.Args[0]) {
47+
pass.ReportRangef(call, "2006-02-01 should be 2006-01-02")
48+
}
49+
})
50+
return nil, nil
51+
}
52+
53+
// isBadFormat return true when e is a string containing 2006-02-01.
54+
func isBadFormat(pass *analysis.Pass, e ast.Expr) bool {
55+
tv, ok := pass.TypesInfo.Types[e]
56+
if !ok { // no type info, assume good
57+
return false
58+
}
59+
60+
t, ok := tv.Type.(*types.Basic)
61+
if !ok || t.Info()&types.IsString == 0 {
62+
return false
63+
}
64+
65+
return strings.Contains(constant.StringVal(tv.Value), "2006-02-01")
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2019 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_test
6+
7+
import (
8+
"testing"
9+
10+
"golang.org/x/tools/go/analysis/analysistest"
11+
"golang.org/x/tools/go/analysis/passes/timeformat"
12+
)
13+
14+
func Test(t *testing.T) {
15+
testdata := analysistest.TestData()
16+
analysistest.Run(t, testdata, timeformat.Analyzer, "a")
17+
}

gopls/doc/analyzers.md

+9
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,15 @@ for the conventions that are enforced for Tests, Benchmarks, and Examples.
483483

484484
**Enabled by default.**
485485

486+
## **timeformat**
487+
488+
check for calls of (time.Time).Format or time.Parse with 2006-02-01
489+
490+
The timeformat checker looks time formats with the bad 2006-02-01 format
491+
which should be replaced by the intended 2006-01-02 format.
492+
493+
**Enabled by default.**
494+
486495
## **unmarshal**
487496

488497
report passing non-pointer or non-interface values to unmarshal

internal/lsp/source/api_json.go

+10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/lsp/source/options.go

+2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import (
4343
"golang.org/x/tools/go/analysis/passes/structtag"
4444
"golang.org/x/tools/go/analysis/passes/testinggoroutine"
4545
"golang.org/x/tools/go/analysis/passes/tests"
46+
"golang.org/x/tools/go/analysis/passes/timeformat"
4647
"golang.org/x/tools/go/analysis/passes/unmarshal"
4748
"golang.org/x/tools/go/analysis/passes/unreachable"
4849
"golang.org/x/tools/go/analysis/passes/unsafeptr"
@@ -1305,6 +1306,7 @@ func defaultAnalyzers() map[string]*Analyzer {
13051306
unusedwrite.Analyzer.Name: {Analyzer: unusedwrite.Analyzer, Enabled: false},
13061307
useany.Analyzer.Name: {Analyzer: useany.Analyzer, Enabled: false},
13071308
infertypeargs.Analyzer.Name: {Analyzer: infertypeargs.Analyzer, Enabled: true},
1309+
timeformat.Analyzer.Name: {Analyzer: timeformat.Analyzer, Enabled: true},
13081310

13091311
// gofmt -s suite:
13101312
simplifycompositelit.Analyzer.Name: {

0 commit comments

Comments
 (0)