blob: 9a037d79a693834ad6531b016423181425ff4891 [file] [log] [blame]
// 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
}