diff --git a/fix/README.md b/fix/README.md deleted file mode 100644 index 8b6a429..0000000 --- a/fix/README.md +++ /dev/null @@ -1,35 +0,0 @@ -## client api changes - -`goirc.go` is a fix compatible with `go tool fix` that attempts to -programaticallly change old client code to work with the newer, shinier -API. It might even work, depending on the complexity of your code. You'll -need to have a Go source tree in the path specified by GOROOT. GOOS and -GOARCH may not be set in your environment; use your head. - -### To install: - - cd $GOROOT/src/cmd/fix - ln -s $GOPATH/src/github.com/fluffle/goirc/fix/goirc.go - go build - mv fix $GOROOT/pkg/tool/$GOOS_$GOARCH - -### To fix: - - go tool fix -r goirc -diff /path/to/code - - go tool fix -r goirc /path/to/code - -### Things that aren't fixed by this - -This fix doesn't take care of some bad design decisions I made: - - - conn.State is left as-is. If you're using this you'll need to rewrite - things to get scope into your handlers in a different fashion. - - conn.ER and conn.ED are left as-is. These should be completely removed - (and probably shouldn't have been left accessible in the first place). - -It's also quite likely that this won't produce the nicest "fixed" code. In -particular, if you're seeing lots of lines like `conn.Config().XXX = "foo"`, -you probably want to consider creating a `Config` struct and then passing -it to `client.Client()`. - diff --git a/fix/goirc.go b/fix/goirc.go deleted file mode 100644 index 5931169..0000000 --- a/fix/goirc.go +++ /dev/null @@ -1,213 +0,0 @@ -package main - -import ( - "fmt" - "go/ast" - "go/token" - "strings" -) - -// DISCLAIMER: will probably not fix everything. Notable omissions: -// - conn.State will not be removed, if you were using it. -// - conn.ER/ED will not be removed; exposing them was a Bad Idea, and -// the required changes are quite a challenge to express programatically. - -func init() { - register(goircFix) -} - -var goircFix = fix{ - "goirc", - "2013-03-24", - goircNewApi, - `Update code that uses goirc/client to new API.`, -} - -const ( - clientPath = "github.com/fluffle/goirc/client" - statePath = "github.com/fluffle/goirc/state" -) - -var goircConstants = map[string]string{ - `"REGISTER"`: "REGISTER", - `"CONNECTED"`: "CONNECTED", - `"DISCONNECTED"`: "DISCONNECTED", - `"ACTION"`: "ACTION", - `"AWAY"`: "AWAY", - `"CTCP"`: "CTCP", - `"CTCPREPLY"`: "CTCPREPLY", - `"INVITE"`: "INVITE", - `"JOIN"`: "JOIN", - `"KICK"`: "KICK", - `"MODE"`: "MODE", - `"NICK"`: "NICK", - `"NOTICE"`: "NOTICE", - `"OPER"`: "OPER", - `"PART"`: "PART", - `"PASS"`: "PASS", - `"PING"`: "PING", - `"PONG"`: "PONG", - `"PRIVMSG"`: "PRIVMSG", - `"QUIT"`: "QUIT", - `"TOPIC"`: "TOPIC", - `"USER"`: "USER", - `"VERSION"`: "VERSION", - `"VHOST"`: "VHOST", - `"WHO"`: "WHO", - `"WHOIS"`: "WHOIS", -} - -var goircStructToConfig = map[string]string{ - "Host": "Server", - "Network": "Server", - "NewNick": "NewNick", - "SSL": "SSL", - "SSLConfig": "SSLConfig", - "PingFreq": "PingFreq", - "Flood": "Flood", -} - -var goircStructToMethod = map[string]string{ - "Me": "Me", - "ST": "StateTracker", - "Connected": "Connected", -} - -var goircMethodRename = map[string]string{ - "AddHandler": "HandleFunc", - "Connect": "ConnectTo", -} - -func addCall(t ast.Expr, method string) *ast.CallExpr { - return &ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: t, - Sel: ast.NewIdent(method), - }, - } -} - -func goircNewApi(f *ast.File) bool { - state := stateApi(f) - client := clientApi(f) - return client || state -} - -func stateApi(f *ast.File) bool { - spec := importSpec(f, statePath) - if spec == nil { - return false - } - stateImport := "state" - if spec.Name != nil { - stateImport = spec.Name.Name - } - return renameFixTab(f, []rename{ - {"github.com/fluffle/goirc/state", "", - stateImport + ".StateTracker", stateImport + ".Tracker"}, - {"github.com/fluffle/goirc/state", "", - stateImport + ".MockStateTracker", stateImport + ".MockTracker"}, - }) -} - -func clientApi(f *ast.File) bool { - spec := importSpec(f, clientPath) - if spec == nil { - return false - } - clientImport := "client" - if spec.Name != nil { - clientImport = spec.Name.Name - } - fixed := false - - maybeReplaceBasicLit := func (expr *ast.Expr) { - str, ok := (*expr).(*ast.BasicLit) - if !ok || str == nil || str.Kind != token.STRING { return } - if repl, ok := goircConstants[strings.ToUpper(str.Value)]; ok { - *expr = &ast.SelectorExpr{ - ast.NewIdent(clientImport), - ast.NewIdent(repl), - } - fixed = true - } - } - - maybeReplaceConnSelectors := func (expr *ast.Expr) { - sel, ok := (*expr).(*ast.SelectorExpr) - if !ok || !isClientConn(sel.X, clientImport) { return } - name := sel.Sel.String() - if rep, ok := goircStructToConfig[name]; ok { - sel.X = addCall(sel.X, "Config") - sel.Sel = ast.NewIdent(rep) - fixed = true - } else if meth, ok := goircStructToMethod[name]; ok { - *expr = addCall(sel.X, meth) - fixed = true - } else if meth, ok := goircMethodRename[name]; ok { - sel.Sel = ast.NewIdent(meth) - fixed = true - } - } - - walk(f, func(n interface{}) { - if expr, ok := n.(*ast.Expr); ok { - maybeReplaceBasicLit(expr) - maybeReplaceConnSelectors(expr) - } - if expr, ok := n.(*ast.CallExpr); ok { - if sel, ok := n.(*ast.SelectorExpr); ok && - isPkgDot(sel, clientImport, "Client") { - // s/Client/SimpleClient/ - sel.Sel = ast.NewIdent("SimpleClient") - // and delete the last arg from args - expr.Args = expr.Args[:3] - fixed = true - } - } - }) - return fixed -} - -func isClientConn(t ast.Expr, pkg string) bool { - // TODO(fluffle): when Conn is a struct member and we're looking for e.g. - // struct.Conn.AddHandler() - // we will pass in the *ast.SelectorExpr{X: struct, Sel: Conn} to this. - // Unfortunately the *ast.Ident{Conn} in this case often has no *ast.Object - // associated with it, so to divine it's type we need to recurse down until - // X is an *ast.Ident instead of another *ast.SelectorExpr, then look for - // the struct member types all the way back up until we get to "Conn". - // This is a massive pain-in-the-arse; I can see why type checking of this - // sort is often not done, s/AddHandler/HandleFunc/g is much simpler. - id, ok := t.(*ast.Ident) - if !ok || id.Obj == nil { return false } - switch dec := id.Obj.Decl.(type) { - case *ast.ValueSpec: - // Declared with var X Type - return dec.Type != nil && isPtrPkgDot(dec.Type, pkg, "Conn") - case *ast.AssignStmt: - // Declared with X := Expr producing Type - // NOTE: not taking care of multiple-assignment case atm! - switch rhs := dec.Rhs[0].(type) { - case *ast.CallExpr: - // X := client.Client() or client.SimpleClient() - return isPkgDot(rhs.Fun, pkg, "Client") || - isPkgDot(rhs.Fun, pkg, "SimpleClient") - case *ast.UnaryExpr: - // X := &client.Conn{} - lit, ok := rhs.X.(*ast.CompositeLit) - return ok && isPkgDot(lit.Type, pkg, "Conn") - case *ast.CompositeLit: - // X := client.Conn{} - return isPkgDot(rhs.Type, pkg, "Conn") - default: - fmt.Printf("rhs: %#v\n", rhs) - } - case *ast.Field: - // Declared with func f(X Type) - return isPkgDot(dec.Type, pkg, "Conn") - default: - fmt.Printf("dec: %#v\n", dec) - } - return false -}