// 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": { Method: map[string]string{ "Readdir": "func() []*os.FileInfo", "Stat": "func() (*os.FileInfo, error)", }, }, "time.Time": { 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 }