aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRuss Cox <rsc@golang.org>2011-12-01 13:59:57 -0500
committerRuss Cox <rsc@golang.org>2011-12-01 13:59:57 -0500
commitc52b7db470880681d8467e87830dc24a1196be63 (patch)
tree46ca7f26e5dc4d41f0bf2ac00ed387fa4d781c14
parenta6729b3085d764b845de505606008311860512b7 (diff)
downloadgo-c52b7db470880681d8467e87830dc24a1196be63.tar.gz
go-c52b7db470880681d8467e87830dc24a1196be63.zip
gofix: add time+fileinfo fix
R=adg, rogpeppe, r, cw CC=golang-dev https://golang.org/cl/5450050
-rw-r--r--src/cmd/gofix/Makefile1
-rw-r--r--src/cmd/gofix/timefileinfo.go298
-rw-r--r--src/cmd/gofix/timefileinfo_test.go161
3 files changed, 460 insertions, 0 deletions
diff --git a/src/cmd/gofix/Makefile b/src/cmd/gofix/Makefile
index dc42b3055d..e6b9503faf 100644
--- a/src/cmd/gofix/Makefile
+++ b/src/cmd/gofix/Makefile
@@ -33,6 +33,7 @@ GOFILES=\
sortslice.go\
stringssplit.go\
template.go\
+ timefileinfo.go\
typecheck.go\
url.go\
diff --git a/src/cmd/gofix/timefileinfo.go b/src/cmd/gofix/timefileinfo.go
new file mode 100644
index 0000000000..9a037d79a6
--- /dev/null
+++ b/src/cmd/gofix/timefileinfo.go
@@ -0,0 +1,298 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "go/ast"
+ "go/token"
+ "strings"
+)
+
+func init() {
+ register(timefileinfoFix)
+}
+
+var timefileinfoFix = fix{
+ "time+fileinfo",
+ "2011-11-29",
+ timefileinfo,
+ `Rewrite for new time and os.FileInfo APIs.
+
+This fix applies some of the more mechanical changes,
+but most code will still need manual cleanup.
+
+http://codereview.appspot.com/5392041
+http://codereview.appspot.com/5416060
+`,
+}
+
+var timefileinfoTypeConfig = &TypeConfig{
+ Type: map[string]*Type{
+ "os.File": &Type{
+ Method: map[string]string{
+ "Readdir": "func() []*os.FileInfo",
+ "Stat": "func() (*os.FileInfo, error)",
+ },
+ },
+ "time.Time": &Type{
+ Method: map[string]string{
+ "Seconds": "time.raw",
+ "Nanoseconds": "time.raw",
+ },
+ },
+ },
+ Func: map[string]string{
+ "ioutil.ReadDir": "([]*os.FileInfo, error)",
+ "os.Stat": "(*os.FileInfo, error)",
+ "os.Lstat": "(*os.FileInfo, error)",
+ "time.LocalTime": "*time.Time",
+ "time.UTC": "*time.Time",
+ "time.SecondsToLocalTime": "*time.Time",
+ "time.SecondsToUTC": "*time.Time",
+ "time.NanosecondsToLocalTime": "*time.Time",
+ "time.NanosecondsToUTC": "*time.Time",
+ "time.Parse": "(*time.Time, error)",
+ "time.Nanoseconds": "time.raw",
+ "time.Seconds": "time.raw",
+ },
+}
+
+// timefileinfoIsOld reports whether f has evidence of being
+// "old code", from before the API changes. Evidence means:
+//
+// a mention of *os.FileInfo (the pointer)
+// a mention of *time.Time (the pointer)
+// a mention of old functions from package time
+// an attempt to call time.UTC
+//
+func timefileinfoIsOld(f *ast.File, typeof map[interface{}]string) bool {
+ old := false
+
+ // called records the expressions that appear as
+ // the function part of a function call, so that
+ // we can distinguish a ref to the possibly new time.UTC
+ // from the definitely old time.UTC() function call.
+ called := make(map[interface{}]bool)
+
+ before := func(n interface{}) {
+ if old {
+ return
+ }
+ if star, ok := n.(*ast.StarExpr); ok {
+ if isPkgDot(star.X, "os", "FileInfo") || isPkgDot(star.X, "time", "Time") {
+ old = true
+ return
+ }
+ }
+ if sel, ok := n.(*ast.SelectorExpr); ok {
+ if isTopName(sel.X, "time") {
+ if timefileinfoOldTimeFunc[sel.Sel.Name] {
+ old = true
+ return
+ }
+ }
+ if typeof[sel.X] == "os.FileInfo" || typeof[sel.X] == "*os.FileInfo" {
+ switch sel.Sel.Name {
+ case "Mtime_ns", "IsDirectory", "IsRegular":
+ old = true
+ return
+ case "Name", "Mode", "Size":
+ if !called[sel] {
+ old = true
+ return
+ }
+ }
+ }
+ }
+ call, ok := n.(*ast.CallExpr)
+ if ok && isPkgDot(call.Fun, "time", "UTC") {
+ old = true
+ return
+ }
+ if ok {
+ called[call.Fun] = true
+ }
+ }
+ walkBeforeAfter(f, before, nop)
+ return old
+}
+
+var timefileinfoOldTimeFunc = map[string]bool{
+ "LocalTime": true,
+ "SecondsToLocalTime": true,
+ "SecondsToUTC": true,
+ "NanosecondsToLocalTime": true,
+ "NanosecondsToUTC": true,
+ "Seconds": true,
+ "Nanoseconds": true,
+}
+
+var isTimeNow = map[string]bool{
+ "LocalTime": true,
+ "UTC": true,
+ "Seconds": true,
+ "Nanoseconds": true,
+}
+
+func timefileinfo(f *ast.File) bool {
+ if !imports(f, "os") && !imports(f, "time") && !imports(f, "io/ioutil") {
+ return false
+ }
+
+ typeof, _ := typecheck(timefileinfoTypeConfig, f)
+
+ if !timefileinfoIsOld(f, typeof) {
+ return false
+ }
+
+ fixed := false
+ walk(f, func(n interface{}) {
+ p, ok := n.(*ast.Expr)
+ if !ok {
+ return
+ }
+ nn := *p
+
+ // Rewrite *os.FileInfo and *time.Time to drop the pointer.
+ if star, ok := nn.(*ast.StarExpr); ok {
+ if isPkgDot(star.X, "os", "FileInfo") || isPkgDot(star.X, "time", "Time") {
+ fixed = true
+ *p = star.X
+ return
+ }
+ }
+
+ // Rewrite old time API calls to new calls.
+ // The code will still not compile after this edit,
+ // but the compiler will catch that, and the replacement
+ // code will be the correct functions to use in the new API.
+ if sel, ok := nn.(*ast.SelectorExpr); ok && isTopName(sel.X, "time") {
+ fn := sel.Sel.Name
+ if fn == "LocalTime" || fn == "Seconds" || fn == "Nanoseconds" {
+ fixed = true
+ sel.Sel.Name = "Now"
+ return
+ }
+ }
+
+ if call, ok := nn.(*ast.CallExpr); ok {
+ if sel, ok := call.Fun.(*ast.SelectorExpr); ok {
+ // Rewrite time.UTC but only when called (there's a new time.UTC var now).
+ if isPkgDot(sel, "time", "UTC") {
+ fixed = true
+ sel.Sel.Name = "Now"
+ // rewrite time.Now() into time.Now().UTC()
+ *p = &ast.CallExpr{
+ Fun: &ast.SelectorExpr{
+ X: call,
+ Sel: ast.NewIdent("UTC"),
+ },
+ }
+ return
+ }
+
+ // Rewrite conversions.
+ if ok && isTopName(sel.X, "time") && len(call.Args) == 1 {
+ fn := sel.Sel.Name
+ switch fn {
+ case "SecondsToLocalTime", "SecondsToUTC",
+ "NanosecondsToLocalTime", "NanosecondsToUTC":
+ fixed = true
+ sel.Sel.Name = "Unix"
+ call.Args = append(call.Args, nil)
+ if strings.HasPrefix(fn, "Seconds") {
+ // Unix(sec, 0)
+ call.Args[1] = ast.NewIdent("0")
+ } else {
+ // Unix(0, nsec)
+ call.Args[1] = call.Args[0]
+ call.Args[0] = ast.NewIdent("0")
+ }
+ if strings.HasSuffix(fn, "ToUTC") {
+ // rewrite call into call.UTC()
+ *p = &ast.CallExpr{
+ Fun: &ast.SelectorExpr{
+ X: call,
+ Sel: ast.NewIdent("UTC"),
+ },
+ }
+ }
+ return
+ }
+ }
+
+ // Rewrite method calls.
+ switch typeof[sel.X] {
+ case "*time.Time", "time.Time":
+ switch sel.Sel.Name {
+ case "Seconds":
+ fixed = true
+ sel.Sel.Name = "Unix"
+ return
+ case "Nanoseconds":
+ fixed = true
+ sel.Sel.Name = "UnixNano"
+ return
+ }
+
+ case "*os.FileInfo", "os.FileInfo":
+ switch sel.Sel.Name {
+ case "IsDirectory":
+ fixed = true
+ sel.Sel.Name = "IsDir"
+ return
+ case "IsRegular":
+ fixed = true
+ sel.Sel.Name = "IsDir"
+ *p = &ast.UnaryExpr{
+ Op: token.NOT,
+ X: call,
+ }
+ return
+ }
+ }
+ }
+ }
+
+ // Rewrite subtraction of two times.
+ // Cannot handle +=/-=.
+ if bin, ok := nn.(*ast.BinaryExpr); ok &&
+ bin.Op == token.SUB &&
+ (typeof[bin.X] == "time.raw" || typeof[bin.Y] == "time.raw") {
+ fixed = true
+ *p = &ast.CallExpr{
+ Fun: &ast.SelectorExpr{
+ X: bin.X,
+ Sel: ast.NewIdent("Sub"),
+ },
+ Args: []ast.Expr{bin.Y},
+ }
+ }
+
+ // Rewrite field references for os.FileInfo.
+ if sel, ok := nn.(*ast.SelectorExpr); ok {
+ if typ := typeof[sel.X]; typ == "*os.FileInfo" || typ == "os.FileInfo" {
+ addCall := false
+ switch sel.Sel.Name {
+ case "Name", "Size", "Mode":
+ fixed = true
+ addCall = true
+ case "Mtime_ns":
+ fixed = true
+ sel.Sel.Name = "ModTime"
+ addCall = true
+ }
+ if addCall {
+ *p = &ast.CallExpr{
+ Fun: sel,
+ }
+ return
+ }
+ }
+ }
+ })
+
+ return true
+}
diff --git a/src/cmd/gofix/timefileinfo_test.go b/src/cmd/gofix/timefileinfo_test.go
new file mode 100644
index 0000000000..76d5c1f7ff
--- /dev/null
+++ b/src/cmd/gofix/timefileinfo_test.go
@@ -0,0 +1,161 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+func init() {
+ addTestCases(timefileinfoTests, timefileinfo)
+}
+
+var timefileinfoTests = []testCase{
+ {
+ Name: "timefileinfo.0",
+ In: `package main
+
+import "os"
+
+func main() {
+ st, _ := os.Stat("/etc/passwd")
+ _ = st.Name
+}
+`,
+ Out: `package main
+
+import "os"
+
+func main() {
+ st, _ := os.Stat("/etc/passwd")
+ _ = st.Name()
+}
+`,
+ },
+ {
+ Name: "timefileinfo.1",
+ In: `package main
+
+import "os"
+
+func main() {
+ st, _ := os.Stat("/etc/passwd")
+ _ = st.Size
+ _ = st.Mode
+ _ = st.Mtime_ns
+ _ = st.IsDirectory()
+ _ = st.IsRegular()
+}
+`,
+ Out: `package main
+
+import "os"
+
+func main() {
+ st, _ := os.Stat("/etc/passwd")
+ _ = st.Size()
+ _ = st.Mode()
+ _ = st.ModTime()
+ _ = st.IsDir()
+ _ = !st.IsDir()
+}
+`,
+ },
+ {
+ Name: "timefileinfo.2",
+ In: `package main
+
+import "os"
+
+func f(st *os.FileInfo) {
+ _ = st.Name
+ _ = st.Size
+ _ = st.Mode
+ _ = st.Mtime_ns
+ _ = st.IsDirectory()
+ _ = st.IsRegular()
+}
+`,
+ Out: `package main
+
+import "os"
+
+func f(st os.FileInfo) {
+ _ = st.Name()
+ _ = st.Size()
+ _ = st.Mode()
+ _ = st.ModTime()
+ _ = st.IsDir()
+ _ = !st.IsDir()
+}
+`,
+ },
+ {
+ Name: "timefileinfo.3",
+ In: `package main
+
+import "time"
+
+func main() {
+ _ = time.Seconds()
+ _ = time.Nanoseconds()
+ _ = time.LocalTime()
+ _ = time.UTC()
+ _ = time.SecondsToLocalTime(sec)
+ _ = time.SecondsToUTC(sec)
+ _ = time.NanosecondsToLocalTime(nsec)
+ _ = time.NanosecondsToUTC(nsec)
+}
+`,
+ Out: `package main
+
+import "time"
+
+func main() {
+ _ = time.Now()
+ _ = time.Now()
+ _ = time.Now()
+ _ = time.Now().UTC()
+ _ = time.Unix(sec, 0)
+ _ = time.Unix(sec, 0).UTC()
+ _ = time.Unix(0, nsec)
+ _ = time.Unix(0, nsec).UTC()
+}
+`,
+ },
+ {
+ Name: "timefileinfo.4",
+ In: `package main
+
+import "time"
+
+func f(*time.Time)
+
+func main() {
+ t := time.LocalTime()
+ _ = t.Seconds()
+ _ = t.Nanoseconds()
+
+ t1 := time.Nanoseconds()
+ f(nil)
+ t2 := time.Nanoseconds()
+ dt := t2 - t1
+}
+`,
+ Out: `package main
+
+import "time"
+
+func f(time.Time)
+
+func main() {
+ t := time.Now()
+ _ = t.Unix()
+ _ = t.UnixNano()
+
+ t1 := time.Now()
+ f(nil)
+ t2 := time.Now()
+ dt := t2.Sub(t1)
+}
+`,
+ },
+}