Repository: goplus/gop
Pull request #1029 (2021-12-06 14:38:44)

feat: convert go code style to go+ code style #1010
processed:
* fmt.Print
* fmt.Printf
* fmt.Println

cases handled:
* variables, like `var _ fmt.Stringer`
* assignments, like `_ = fmt.Errorf(...)`
* func calls, like `fmt.Println(...)`


Patch:
diff --git a/x/format/gopstyle.go b/x/format/gopstyle.go
index 66828b29..4acf7b2f 100644
--- a/x/format/gopstyle.go
+++ b/x/format/gopstyle.go
@@ -9,6 +9,19 @@ import (
 	"github.com/goplus/gop/token"
 )
 
+var printFuncs = map[string]string{
+	"Errorf":   "errorf",
+	"Print":    "print",
+	"Printf":   "printf",
+	"Println":  "println",
+	"Fprint":   "fprint",
+	"Fprintf":  "fprintf",
+	"Fprintln": "fprintln",
+	"Sprint":   "sprint",
+	"Sprintf":  "sprintf",
+	"Sprintln": "sprintln",
+}
+
 // -----------------------------------------------------------------------------
 
 func GopstyleSource(src []byte, filename ...string) (ret []byte, err error) {
@@ -47,12 +60,40 @@ func Gopstyle(file *ast.File) {
 			*/
 		}
 	}
+
+	replacePrintCalls(file)
 }
 
 func identEqual(v *ast.Ident, name string) bool {
 	return v != nil && v.Name == name
 }
 
+func findFmtImports(decls []ast.Decl) []string {
+	var aliases []string
+
+	for _, decl := range decls {
+		if genDecl, ok := decl.(*ast.GenDecl); ok {
+			if genDecl.Tok != token.IMPORT {
+				continue
+			}
+
+			for _, spec := range genDecl.Specs {
+				if imp, ok := spec.(*ast.ImportSpec); ok {
+					if imp.Path.Value == `"fmt"` {
+						if imp.Name != nil {
+							aliases = append(aliases, imp.Name.Name)
+						} else {
+							aliases = append(aliases, "fmt")
+						}
+					}
+				}
+			}
+		}
+	}
+
+	return aliases
+}
+
 func findFuncDecl(decls []ast.Decl, name string) int {
 	for i, decl := range decls {
 		if fn, ok := decl.(*ast.FuncDecl); ok {
@@ -64,4 +105,183 @@ func findFuncDecl(decls []ast.Decl, name string) int {
 	return -1
 }
 
+func processAssignment(stmt *ast.AssignStmt, fmts []string) []string {
+	var keeps []string
+
+	for _, expr := range stmt.Rhs {
+		switch val := expr.(type) {
+		case *ast.CallExpr:
+			keeps = append(keeps, processCallExpr(val, fmts)...)
+		case *ast.SelectorExpr:
+			keeps = append(keeps, processSelectorExpr(val, fmts)...)
+		}
+	}
+
+	return keeps
+}
+
+func processCallExpr(expr *ast.CallExpr, fmts []string) (keeps []string) {
+	if selExpr, ok := expr.Fun.(*ast.SelectorExpr); ok {
+		if ident, ok := selExpr.X.(*ast.Ident); ok {
+			if contains(fmts, ident.Name) {
+				p, ok := printFuncs[selExpr.Sel.Name]
+				if !ok {
+					return []string{ident.Name}
+				}
+
+				selExpr.Sel.Name = p
+				expr.Fun = selExpr.Sel
+			}
+		}
+	}
+
+	return
+}
+
+func processDeclStmt(stmt *ast.DeclStmt, fmts []string) []string {
+	var keeps []string
+	if decl, ok := stmt.Decl.(*ast.GenDecl); ok {
+		for _, spec := range decl.Specs {
+			if valSpec, ok := spec.(*ast.ValueSpec); ok {
+				if selExpr, ok := valSpec.Type.(*ast.SelectorExpr); ok {
+					keeps = append(keeps, processSelectorExpr(selExpr, fmts)...)
+				}
+			}
+		}
+	}
+
+	return keeps
+}
+
+func processExprStmt(stmt *ast.ExprStmt, fmts []string) (keeps []string) {
+	if callExpr, ok := stmt.X.(*ast.CallExpr); ok {
+		return processCallExpr(callExpr, fmts)
+	}
+
+	return
+}
+
+func processSelectorExpr(expr *ast.SelectorExpr, fmts []string) (keeps []string) {
+	if ident, ok := expr.X.(*ast.Ident); ok {
+		if contains(fmts, ident.Name) {
+			if _, ok := printFuncs[expr.Sel.Name]; !ok {
+				return []string{ident.Name}
+			}
+		}
+	}
+
+	return
+}
+
+// replacePrintCalls replaces fmt print funcs to builtin print funcs.
+// also removes fmt import if necessary.
+func replacePrintCalls(file *ast.File) {
+	var keeps []string
+	fmts := findFmtImports(file.Decls)
+	for _, decl := range file.Decls {
+		keeps = append(keeps, replacePrintCall(decl, fmts)...)
+	}
+
+	trimFmtImport(file, keeps)
+}
+
+func replacePrintCall(decl ast.Decl, fmts []string) []string {
+	var keeps []string
+
+	if fn, ok := decl.(*ast.FuncDecl); ok {
+		for _, stmt := range fn.Body.List {
+			switch val := stmt.(type) {
+			case *ast.AssignStmt:
+				keeps = append(keeps, processAssignment(val, fmts)...)
+			case *ast.DeclStmt:
+				keeps = append(keeps, processDeclStmt(val, fmts)...)
+			case *ast.ExprStmt:
+				keeps = append(keeps, processExprStmt(val, fmts)...)
+			}
+		}
+	}
+
+	return keeps
+}
+
+func trimFmtImport(file *ast.File, keeps []string) {
+	var declIndice []int
+
+	for i, decl := range file.Decls {
+		genDecl, ok := decl.(*ast.GenDecl)
+		if !ok {
+			continue
+		}
+		if genDecl.Tok != token.IMPORT {
+			continue
+		}
+
+		var indice []int
+		for j, spec := range genDecl.Specs {
+			if imp, ok := spec.(*ast.ImportSpec); ok {
+				if imp.Path.Value == `"fmt"` {
+					// has alias
+					if imp.Name != nil && !contains(keeps, imp.Name.Name) {
+						indice = append(indice, j)
+					} else if !contains(keeps, "fmt") {
+						indice = append(indice, j)
+					}
+				}
+			}
+		}
+
+		genDecl.Specs = removeSpecsIf(genDecl.Specs, func(i int) bool {
+			for _, val := range indice {
+				if i == val {
+					return true
+				}
+			}
+			return false
+		})
+		if len(genDecl.Specs) == 0 {
+			declIndice = append(declIndice, i)
+		}
+	}
+
+	file.Decls = removeDeclsIf(file.Decls, func(i int) bool {
+		for _, val := range declIndice {
+			if i == val {
+				return true
+			}
+		}
+		return false
+	})
+}
+
 // -----------------------------------------------------------------------------
+
+func contains(items []string, val string) bool {
+	for _, item := range items {
+		if val == item {
+			return true
+		}
+	}
+	return false
+}
+
+func removeDeclsIf(decls []ast.Decl, fn func(i int) bool) []ast.Decl {
+	ret := decls[:0]
+	for i := range decls {
+		if fn(i) {
+			continue
+		}
+		ret = append(ret, decls[i])
+	}
+	return ret
+}
+
+func removeSpecsIf(specs []ast.Spec, fn func(i int) bool) []ast.Spec {
+	ret := specs[:0]
+	for i := range specs {
+		if fn(i) {
+			continue
+		}
+		ret = append(ret, specs[i])
+	}
+	return ret
+}


Test patch:
diff --git a/x/format/gopstyle_test.go b/x/format/gopstyle_test.go
index b0a0f037..accc1e6f 100644
--- a/x/format/gopstyle_test.go
+++ b/x/format/gopstyle_test.go
@@ -28,12 +28,10 @@ func main() {
 	// say hello
 	fmt.Println("Hello world")
 }
-`, `import "fmt"
-
-// this is main
+`, `// this is main
 
 // say hello
-fmt.Println("Hello world")
+println("Hello world")
 `)
 }
 
@@ -50,12 +48,10 @@ func main() {
 
 func f() {
 }
-`, `import "fmt"
-
-// this is main
+`, `// this is main
 func main() {
 	// say hello
-	fmt.Println("Hello world")
+	println("Hello world")
 }
 
 func f() {
@@ -70,11 +66,245 @@ import "fmt"
 
 func f() {
 }
+`, `func f() {
+}
+`)
+}
+
+// -----------------------------------------------------------------------------
+
+func TestPrint(t *testing.T) {
+	testFormat(t, "print", `package main
+
+import "fmt"
+
+func f() {
+	fmt.Print("hello")
+}
+`, `func f() {
+	print("hello")
+}
+`)
+}
+
+func TestPrintf(t *testing.T) {
+	testFormat(t, "print", `package main
+
+import "fmt"
+
+func f() {
+	fmt.Printf("hello")
+}
+`, `func f() {
+	printf("hello")
+}
+`)
+}
+
+func TestPrintln(t *testing.T) {
+	testFormat(t, "print", `package main
+
+import "fmt"
+
+func f() {
+	fmt.Println("hello")
+}
+`, `func f() {
+	println("hello")
+}
+`)
+}
+
+func TestPrintlnGroup(t *testing.T) {
+	testFormat(t, "print", `package main
+
+import (
+	"fmt"
+)
+
+func f() {
+	fmt.Println("hello")
+}
+`, `func f() {
+	println("hello")
+}
+`)
+}
+
+func TestPrintlnWithOtherFmtCalls(t *testing.T) {
+	testFormat(t, "print", `package main
+
+import "errors"
+import "fmt"
+
+func f() {
+	fmt.Errorf("%w", errors.New("hello"))
+	fmt.Println("hello")
+}
+`, `import "errors"
+
+func f() {
+	errorf("%w", errors.New("hello"))
+	println("hello")
+}
+`)
+}
+
+func TestPrintlnWithOtherFmtCallsGroup(t *testing.T) {
+	testFormat(t, "print", `package main
+
+import (
+	"errors"
+	"fmt"
+)
+
+func f() {
+	fmt.Errorf("%w", errors.New("hello"))
+	fmt.Println("hello")
+}
+`, `import (
+	"errors"
+)
+
+func f() {
+	errorf("%w", errors.New("hello"))
+	println("hello")
+}
+`)
+}
+
+func TestPrintlnWithOtherFmtCallsWithAssign(t *testing.T) {
+	testFormat(t, "print", `package main
+
+import "errors"
+import "fmt"
+
+func f() {
+	_ = fmt.Errorf("%w", errors.New("hello"))
+	fmt.Println("hello")
+}
+`, `import "errors"
+
+func f() {
+	_ = errorf("%w", errors.New("hello"))
+	println("hello")
+}
+`)
+}
+
+func TestPrintlnWithOtherFmtCallsWithGroupWithAssign(t *testing.T) {
+	testFormat(t, "print", `package main
+
+import (
+	"errors"
+	"fmt"
+)
+
+func f() {
+	_ = fmt.Errorf("%w", errors.New("hello"))
+	fmt.Println("hello")
+}
+`, `import (
+	"errors"
+)
+
+func f() {
+	_ = errorf("%w", errors.New("hello"))
+	println("hello")
+}
+`)
+}
+
+func TestPrintlnWithOtherFmtDecls(t *testing.T) {
+	testFormat(t, "print", `package main
+
+import "fmt"
+
+func f() {
+	_ = fmt.Stringer
+	fmt.Println("hello")
+}
 `, `import "fmt"
 
 func f() {
+	_ = fmt.Stringer
+	println("hello")
 }
 `)
 }
 
-// -----------------------------------------------------------------------------
+func TestPrintlnWithOtherFmtVars(t *testing.T) {
+	testFormat(t, "print", `package main
+
+import "fmt"
+
+func f() {
+	var _ fmt.Stringer
+	fmt.Println("hello")
+}
+`, `import "fmt"
+
+func f() {
+	var _ fmt.Stringer
+	println("hello")
+}
+`)
+}
+
+func TestPrintlnImportAlias(t *testing.T) {
+	testFormat(t, "print", `package main
+
+import fmt1 "fmt"
+
+func f() {
+	fmt1.Println("hello")
+}
+`, `func f() {
+	println("hello")
+}
+`)
+}
+
+func TestPrintlnImportMultiAliases(t *testing.T) {
+	testFormat(t, "print", `package main
+
+import (
+	fmt1 "fmt"
+	fmt2 "fmt"
+)
+
+func f() {
+	fmt1.Println(1)
+	fmt2.Println(2)
+}
+`, `func f() {
+	println(1)
+	println(2)
+}
+`)
+}
+
+func TestPrintlnImportMultiAliasesDifferentGroups(t *testing.T) {
+	testFormat(t, "print", `package main
+
+import "fmt"
+
+import (
+	fmt1 "fmt"
+	fmt2 "fmt"
+)
+
+func f() {
+	var _ fmt.Stringer
+	fmt1.Println(1)
+	fmt2.Println(2)
+}
+`, `import "fmt"
+
+func f() {
+	var _ fmt.Stringer
+	println(1)
+	println(2)
+}
+`)
+}