Repository: goplus/gop
Pull request #1044 (2021-12-08 15:45:45)

feat: process print on smart style, for more cases


Patch:
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 5052546c..ea06a35a 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -21,6 +21,9 @@ jobs:
       with:
         go-version: ${{ matrix.go-version }}
 
+    - name: Test Go+ installer
+      run: go test -v cmd/make_test.go
+
     - name: Compile gop and related tools
       run: go install ./...
 
diff --git a/README.md b/README.md
index 5fe28f26..d7afc11c 100644
--- a/README.md
+++ b/README.md
@@ -87,10 +87,10 @@ It's for Go, and it's also for Go+.
 
 ## Playground
 
-Go+ Playground based on Docker (recommended):
+Go+ Playground based on Docker:
 * https://play.goplus.org/
 
-Go+ Playground based on GopherJS (currently only available in v0.7.x):
+Go+ Playground based on GopherJS:
 * https://jsplay.goplus.org/
 
 
diff --git a/cl/expr.go b/cl/expr.go
index 67551bce..342afe8e 100644
--- a/cl/expr.go
+++ b/cl/expr.go
@@ -114,10 +114,10 @@ func compileIdent(ctx *blockCtx, ident *ast.Ident, flags int) *gox.PkgRef {
 	}
 
 	// universe object
-	if (flags&clIdentAllowBuiltin) == 0 && isBuiltin(o) {
-		panic(ctx.newCodeErrorf(ident.Pos(), "use of builtin %s not in function call", name))
-	}
 	if obj := ctx.pkg.Builtin().TryRef(name); obj != nil {
+		if (flags&clIdentAllowBuiltin) == 0 && isBuiltin(o) && !strings.HasPrefix(o.Name(), "print") {
+			panic(ctx.newCodeErrorf(ident.Pos(), "use of builtin %s not in function call", name))
+		}
 		o = obj
 	} else if o == nil {
 		if (clIdentGoto & flags) != 0 {
@@ -144,24 +144,16 @@ func isBuiltin(o types.Object) bool {
 }
 
 func compileMember(ctx *blockCtx, v ast.Node, name string, flags int) error {
-	cb := ctx.cb
-	lhs := (flags & clIdentLHS) != 0
-	kind, err := cb.Member(name, lhs, v)
-	if kind != 0 {
-		return nil
-	}
-	if c := name[0]; c >= 'a' && c <= 'z' {
-		name = string(rune(c)+('A'-'a')) + name[1:]
-		switch kind, _ = cb.Member(name, lhs, v); kind {
-		case gox.MemberMethod:
-			if (flags & clIdentAutoCall) != 0 {
-				cb.Call(0)
-			}
-			return nil
-		case gox.MemberField:
-			return nil
-		}
+	var mflag gox.MemberFlag
+	switch {
+	case (flags & clIdentLHS) != 0:
+		mflag = gox.MemberFlagRef
+	case (flags & clIdentAutoCall) != 0:
+		mflag = gox.MemberFlagAutoProperty
+	default:
+		mflag = gox.MemberFlagMethodAlias
 	}
+	_, err := ctx.cb.Member(name, mflag, v)
 	return err
 }
 
@@ -330,39 +322,46 @@ func compileSelectorExpr(ctx *blockCtx, v *ast.SelectorExpr, flags int) {
 	}
 }
 
-func pkgRef(at *gox.PkgRef, name string) (o types.Object, canAutoCall bool) {
+func pkgRef(at *gox.PkgRef, name string) (o types.Object, alias bool) {
 	if c := name[0]; c >= 'a' && c <= 'z' {
 		name = string(rune(c)+('A'-'a')) + name[1:]
-		return at.TryRef(name), true
+		if v := at.TryRef(name); v != nil && gox.IsFunc(v.Type()) {
+			return v, true
+		}
+		return
 	}
 	return at.TryRef(name), false
 }
 
-func lookupPkgRef(ctx *blockCtx, pkg *gox.PkgRef, x *ast.Ident) (o types.Object, canAutoCall bool) {
+func lookupPkgRef(ctx *blockCtx, pkg *gox.PkgRef, x *ast.Ident) (o types.Object, alias bool) {
 	if pkg != nil {
 		return pkgRef(pkg, x.Name)
 	}
 	for _, at := range ctx.lookups {
-		if o2, canAutoCall2 := pkgRef(at, x.Name); o2 != nil {
+		if o2, alias2 := pkgRef(at, x.Name); o2 != nil {
 			if o != nil {
 				panic(ctx.newCodeErrorf(
 					x.Pos(), "confliction: %s declared both in \"%s\" and \"%s\"",
 					x.Name, at.Types.Path(), pkg.Types.Path()))
 			}
-			pkg, o, canAutoCall = at, o2, canAutoCall2
+			pkg, o, alias = at, o2, alias2
 		}
 	}
 	return
 }
 
 func compilePkgRef(ctx *blockCtx, at *gox.PkgRef, x *ast.Ident, flags int) bool {
-	if v, canAutoCall := lookupPkgRef(ctx, at, x); v != nil {
+	if v, alias := lookupPkgRef(ctx, at, x); v != nil {
 		cb := ctx.cb
 		if (flags & clIdentLHS) != 0 {
 			cb.VarRef(v, x)
 		} else {
+			autoprop := alias && (flags&clIdentAutoCall) != 0
+			if autoprop && !gox.HasAutoProperty(v.Type()) {
+				return false
+			}
 			cb.Val(v, x)
-			if canAutoCall && (flags&clIdentAutoCall) != 0 && isFunc(v.Type()) {
+			if autoprop {
 				cb.Call(0)
 			}
 		}
@@ -371,14 +370,6 @@ func compilePkgRef(ctx *blockCtx, at *gox.PkgRef, x *ast.Ident, flags int) bool
 	return false
 }
 
-func isFunc(typ types.Type) bool {
-	switch typ.(type) {
-	case *types.Signature:
-		return true
-	}
-	return false
-}
-
 type fnType struct {
 	params   *types.Tuple
 	n1       int
diff --git a/cl/internal/spx/game.go b/cl/internal/spx/game.go
index f2f8c356..9162dea9 100644
--- a/cl/internal/spx/game.go
+++ b/cl/internal/spx/game.go
@@ -52,6 +52,14 @@ func Sched() {
 func SchedNow() {
 }
 
+func Rand__0(int) int {
+	return 0
+}
+
+func Rand__1(float64) float64 {
+	return 0
+}
+
 var (
 	TestIntValue int
 )
diff --git a/cl/stmt.go b/cl/stmt.go
index 6381083d..02b66f76 100644
--- a/cl/stmt.go
+++ b/cl/stmt.go
@@ -81,14 +81,9 @@ func compileStmt(ctx *blockCtx, stmt ast.Stmt) {
 	commentStmt(ctx, stmt)
 	switch v := stmt.(type) {
 	case *ast.ExprStmt:
-		if obj, ok := isBuiltinAutoCall(ctx, v.X); ok {
-			ctx.cb.Val(obj)
+		compileExpr(ctx, v.X)
+		if canAutoCall(v.X) && gox.IsFunc(ctx.cb.InternalStack().Get(-1).Type) {
 			ctx.cb.Call(0)
-		} else {
-			compileExpr(ctx, v.X)
-			if canAutoCall(v.X) && isFunc(ctx.cb.InternalStack().Get(-1).Type) {
-				ctx.cb.Call(0)
-			}
 		}
 	case *ast.AssignStmt:
 		compileAssignStmt(ctx, v)
@@ -147,19 +142,6 @@ retry:
 	return false
 }
 
-func isBuiltinAutoCall(ctx *blockCtx, expr ast.Expr) (types.Object, bool) {
-	if ident, ok := expr.(*ast.Ident); ok {
-		switch ident.Name {
-		case "print", "println":
-			_, builtin := lookupType(ctx, ident.Name)
-			if isBuiltin(builtin) {
-				return builtin, true
-			}
-		}
-	}
-	return nil, false
-}
-
 func compileReturnStmt(ctx *blockCtx, expr *ast.ReturnStmt) {
 	var n = -1
 	var results *types.Tuple
diff --git a/cmd/make.go b/cmd/make.go
index 1760318f..7d8506d3 100644
--- a/cmd/make.go
+++ b/cmd/make.go
@@ -83,8 +83,8 @@ const (
 )
 
 func init() {
-	for index, file := range gopBinFiles {
-		if inWindows {
+	if inWindows {
+		for index, file := range gopBinFiles {
 			file += ".exe"
 			gopBinFiles[index] = file
 		}
@@ -233,6 +233,9 @@ func buildGoplusTools(useGoProxy bool) {
 	}
 	print(buildOutput)
 
+	// Clear gop run cache
+	cleanGopRunCache()
+
 	installPath := linkGoplusToLocalBin()
 
 	println("\nGo+ tools installed successfully!")
@@ -293,6 +296,22 @@ func clean() {
 			log.Fatalln(err)
 		}
 	}
+
+	cleanGopRunCache()
+}
+
+func cleanGopRunCache() {
+	homeDir, _ := os.UserHomeDir()
+	runCacheDir := filepath.Join(homeDir, ".gop", "run")
+	files := []string{"go.mod", "go.sum"}
+	for _, file := range files {
+		fullPath := filepath.Join(runCacheDir, file)
+		if checkPathExist(fullPath, false) {
+			if err := os.Remove(fullPath); err != nil {
+				log.Fatalln(err)
+			}
+		}
+	}
 }
 
 func uninstall() {
diff --git a/go.mod b/go.mod
index 1b56ba14..65b1c556 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module github.com/goplus/gop
 go 1.16
 
 require (
-	github.com/goplus/gox v1.8.1
+	github.com/goplus/gox v1.8.7
 	github.com/qiniu/x v1.11.5
 	golang.org/x/mod v0.5.1
 	golang.org/x/tools v0.1.8
diff --git a/go.sum b/go.sum
index d0e06736..e18552a7 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,5 @@
-github.com/goplus/gox v1.8.1 h1:nMn6J3Jv6zNHr6Ul2GkUqhLCm6m0tWt8jGpI15iSaEw=
-github.com/goplus/gox v1.8.1/go.mod h1:XWqV75HImeysI8+Q9gAdYyvwlP8jds24xsgYRwTO3JI=
+github.com/goplus/gox v1.8.7 h1:XtUEazI+EMSyN9A0IGbrPe99dpT5lnD1k98HwmVL9Nc=
+github.com/goplus/gox v1.8.7/go.mod h1:XWqV75HImeysI8+Q9gAdYyvwlP8jds24xsgYRwTO3JI=
 github.com/qiniu/x v1.11.5 h1:TYr5cl4g2yoHAZeDK4MTjKF6CMoG+IHlCDvvM5qym6U=
 github.com/qiniu/x v1.11.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
 github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
diff --git a/x/format/format.go b/x/format/format.go
index 112489ef..76212821 100644
--- a/x/format/format.go
+++ b/x/format/format.go
@@ -24,24 +24,26 @@ import (
 
 // -----------------------------------------------------------------------------
 
-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",
+var printFuncs = [][2]string{
+	{"Errorf", "errorf"},
+	{"Print", "print"},
+	{"Printf", "printf"},
+	{"Println", "println"},
+	{"Fprint", "fprint"},
+	{"Fprintf", "fprintf"},
+	{"Fprintln", "fprintln"},
+	{"Sprint", "sprint"},
+	{"Sprintf", "sprintf"},
+	{"Sprintln", "sprintln"},
 }
 
 func fmtToBuiltin(ctx *importCtx, sel *ast.Ident, ref *ast.Expr) bool {
 	if ctx.pkgPath == "fmt" {
-		if v, ok := printFuncs[sel.Name]; ok {
-			*ref = &ast.Ident{NamePos: sel.NamePos, Name: v}
-			return true
+		for _, fns := range printFuncs {
+			if fns[0] == sel.Name || fns[1] == sel.Name {
+				*ref = &ast.Ident{NamePos: sel.NamePos, Name: fns[1]}
+				return true
+			}
 		}
 	}
 	return false
diff --git a/x/format/gopstyle.go b/x/format/gopstyle.go
index 9afe25ee..92b31a0f 100644
--- a/x/format/gopstyle.go
+++ b/x/format/gopstyle.go
@@ -142,16 +142,20 @@ type importCtx struct {
 
 type formatCtx struct {
 	imports map[string]*importCtx
+	scp     *scope
 }
 
 func formatFile(file *ast.File) {
+	var funcs []*ast.FuncDecl
 	ctx := &formatCtx{
 		imports: make(map[string]*importCtx),
+		scp:     newScope(),
 	}
 	for _, decl := range file.Decls {
 		switch v := decl.(type) {
 		case *ast.FuncDecl:
-			formatFuncDecl(ctx, v)
+			// delay the process, because package level vars need to be processed first.
+			funcs = append(funcs, v)
 		case *ast.GenDecl:
 			switch v.Tok {
 			case token.IMPORT:
@@ -174,6 +178,10 @@ func formatFile(file *ast.File) {
 			}
 		}
 	}
+
+	for _, fn := range funcs {
+		formatFuncDecl(ctx, fn)
+	}
 	for _, imp := range ctx.imports {
 		if imp.pkgPath == "fmt" && !imp.isUsed {
 			if len(imp.decl.Specs) == 1 {
@@ -192,6 +200,7 @@ func formatGenDecl(ctx *formatCtx, v *ast.GenDecl) {
 			spec := item.(*ast.ValueSpec)
 			formatType(ctx, spec.Type, &spec.Type)
 			formatExprs(ctx, spec.Values)
+			fillVarCtx(ctx, spec)
 		}
 	case token.TYPE:
 		for _, item := range v.Specs {
@@ -206,4 +215,42 @@ func formatFuncDecl(ctx *formatCtx, v *ast.FuncDecl) {
 	formatBlockStmt(ctx, v.Body)
 }
 
+func fillVarCtx(ctx *formatCtx, spec *ast.ValueSpec) {
+	for _, name := range spec.Names {
+		ctx.scp.addVar(name.Name)
+	}
+}
+
 // -----------------------------------------------------------------------------
+
+type scope struct {
+	vars []map[string]bool
+}
+
+func newScope() *scope {
+	return &scope{
+		vars: []map[string]bool{make(map[string]bool)},
+	}
+}
+
+func (s *scope) addVar(name string) {
+	s.vars[len(s.vars)-1][name] = true
+}
+
+func (s *scope) containsVar(name string) bool {
+	for _, m := range s.vars {
+		if m[name] {
+			return true
+		}
+	}
+
+	return false
+}
+
+func (s *scope) enterScope() {
+	s.vars = append(s.vars, make(map[string]bool))
+}
+
+func (s *scope) exitScope() {
+	s.vars = s.vars[:len(s.vars)-1]
+}
diff --git a/x/format/stmt_expr_or_type.go b/x/format/stmt_expr_or_type.go
index 60bec773..e9f51462 100644
--- a/x/format/stmt_expr_or_type.go
+++ b/x/format/stmt_expr_or_type.go
@@ -170,7 +170,9 @@ func formatCallExpr(ctx *formatCtx, v *ast.CallExpr) {
 func formatSelectorExpr(ctx *formatCtx, v *ast.SelectorExpr, ref *ast.Expr) {
 	switch x := v.X.(type) {
 	case *ast.Ident:
-		// TODO: maybe have bugs: `x.Name` may be a variable, not a import.
+		if ctx.scp.containsVar(x.Name) {
+			break
+		}
 		if imp, ok := ctx.imports[x.Name]; ok {
 			if !fmtToBuiltin(imp, v.Sel, ref) {
 				imp.isUsed = true
@@ -185,7 +187,9 @@ func formatSelectorExpr(ctx *formatCtx, v *ast.SelectorExpr, ref *ast.Expr) {
 
 func formatBlockStmt(ctx *formatCtx, stmt *ast.BlockStmt) {
 	if stmt != nil {
+		ctx.scp.enterScope()
 		formatStmts(ctx, stmt.List)
+		ctx.scp.exitScope()
 	}
 }
 


Test patch:
diff --git a/cl/builtin_test.go b/cl/builtin_test.go
index 8025f6f0..045c08e6 100644
--- a/cl/builtin_test.go
+++ b/cl/builtin_test.go
@@ -57,12 +57,6 @@ func TestGetTypeName(t *testing.T) {
 	getTypeName(types.NewSlice(types.Typ[types.Int]))
 }
 
-func TestIsFunc(t *testing.T) {
-	if isFunc(nil) {
-		t.Fatal("TestIsFunc failed")
-	}
-}
-
 func TestHandleRecover(t *testing.T) {
 	var ctx pkgCtx
 	ctx.handleRecover("hello")
diff --git a/cl/compile_spx_test.go b/cl/compile_spx_test.go
index 04b20236..032ea2f0 100644
--- a/cl/compile_spx_test.go
+++ b/cl/compile_spx_test.go
@@ -203,7 +203,7 @@ func TestSpxMethod(t *testing.T) {
 func onInit() {
 	sched
 	broadcast "msg1"
-	testIntValue = 1
+	TestIntValue = 1
 	x := round(1.2)
 }
 `, `
@@ -301,6 +301,8 @@ var (
 	t   Sound
 )
 
+var x float64 = rand(1.2)
+
 run "hzip://open.qiniu.us/weather/res.zip"
 `, `
 println "Hi"
@@ -311,6 +313,8 @@ import (
 	spx "github.com/goplus/gop/cl/internal/spx"
 )
 
+var x float64 = spx.Rand__1(1.2)
+
 type index struct {
 	*spx.MyGame
 	Kai Kai
diff --git a/cl/compile_test.go b/cl/compile_test.go
index 4fe15af4..d2b7e460 100644
--- a/cl/compile_test.go
+++ b/cl/compile_test.go
@@ -148,6 +148,21 @@ func main() {
 `)
 }
 
+func TestAssignPrintln(t *testing.T) {
+	gopClTest(t, `
+p := println
+p "Hello world"
+`, `package main
+
+import fmt "fmt"
+
+func main() {
+	p := fmt.Println
+	p("Hello world")
+}
+`)
+}
+
 func TestRedefineBuiltin(t *testing.T) {
 	gopClTest(t, `
 func main() {
diff --git a/cl/error_msg_test.go b/cl/error_msg_test.go
index 86138ac3..4906a553 100644
--- a/cl/error_msg_test.go
+++ b/cl/error_msg_test.go
@@ -171,8 +171,8 @@ var a = 1, 2
 }
 `)
 	codeErrorTest(t,
-		"./bar.gop:2:9: use of builtin len not in function call", `func main() {
-println(len.x)
+		"./bar.gop:2:10: use of builtin len not in function call", `func main() {
+	println(len.x)
 }
 `)
 	codeErrorTest(t,
@@ -215,7 +215,7 @@ func foo(v map[int]bar) {
 }
 `)
 	codeErrorTest(t,
-		"./bar.gop:2:2: use of builtin println not in function call", `func main() {
+		"./bar.gop:2:2: println is not a variable", `func main() {
 	println = "hello"
 }
 `)
@@ -798,3 +798,11 @@ switch n.(type) {
 }
 `)
 }
+
+func TestErrAutoProperty(t *testing.T) {
+	codeErrorTest(t, `./bar.gop:4:11: cannot refer to unexported name fmt.println`, `
+import "fmt"
+
+n, err := fmt.println
+`)
+}
diff --git a/cmd/make_test.go b/cmd/make_test.go
new file mode 100644
index 00000000..b63bb311
--- /dev/null
+++ b/cmd/make_test.go
@@ -0,0 +1,104 @@
+//go:build ignore
+// +build ignore
+
+package make_test
+
+import (
+	"os"
+	"os/exec"
+	"path/filepath"
+	"runtime"
+	"testing"
+)
+
+const (
+	inWindows = (runtime.GOOS == "windows")
+)
+
+var script = "all.bash"
+var gopRoot = ""
+var gopBinFiles = []string{"gop", "gopfmt"}
+
+func checkPathExist(path string, isDir bool) bool {
+	stat, err := os.Stat(path)
+	isExists := !os.IsNotExist(err)
+	if isDir {
+		return isExists && stat.IsDir()
+	}
+	return isExists && !stat.IsDir()
+}
+
+func detectGoBinPath() string {
+	goBin, ok := os.LookupEnv("GOBIN")
+	if ok {
+		return goBin
+	}
+
+	goPath, ok := os.LookupEnv("GOPATH")
+	if ok {
+		list := filepath.SplitList(goPath)
+		if len(list) > 0 {
+			// Put in first directory of $GOPATH.
+			return filepath.Join(list[0], "bin")
+		}
+	}
+
+	homeDir, _ := os.UserHomeDir()
+	return filepath.Join(homeDir, "go", "bin")
+}
+
+func init() {
+	pwd, _ := os.Getwd()
+	gopRoot = filepath.Join(pwd, "..")
+	if inWindows {
+		script = "all.bat"
+		for index, file := range gopBinFiles {
+			file += ".exe"
+			gopBinFiles[index] = file
+		}
+	}
+}
+
+func TestAllScript(t *testing.T) {
+	os.Chdir(gopRoot)
+	cmd := exec.Command(filepath.Join(gopRoot, script))
+	if output, err := cmd.CombinedOutput(); err != nil {
+		t.Fatalf("Failed: %v:\nOut: %s\n", err, output)
+	}
+
+	goBinPath := detectGoBinPath()
+
+	for _, file := range gopBinFiles {
+		if !checkPathExist(filepath.Join(gopRoot, "bin", file), false) {
+			t.Fatalf("Failed: %s not found in ./bin directory\n", file)
+		}
+		if !checkPathExist(filepath.Join(goBinPath, file), false) {
+			t.Fatalf("Failed: %s not found in %s/bin directory\n", file, goBinPath)
+		}
+	}
+
+	cmd = exec.Command(filepath.Join(gopRoot, "bin", gopBinFiles[0]), "version")
+	if output, err := cmd.CombinedOutput(); err != nil {
+		t.Fatalf("Failed: %v:\nOut: %s\n", err, output)
+	}
+}
+
+func TestHandleMultiFlags(t *testing.T) {
+	os.Chdir(gopRoot)
+	cmd := exec.Command("go", "run", filepath.Join(gopRoot, "cmd/make.go"), "--install", "--test", "--uninstall")
+	if output, err := cmd.CombinedOutput(); err != nil {
+		t.Fatalf("Failed: %v:\nOut: %s\n", err, output)
+	}
+
+	goBinPath := detectGoBinPath()
+
+	// Uninstall will be the last action to run, test if all build assets being cleared.
+	for _, file := range gopBinFiles {
+		if checkPathExist(filepath.Join(gopRoot, "bin", file), false) {
+			t.Fatalf("Failed: %s found in ./bin directory\n", file)
+		}
+		if checkPathExist(filepath.Join(goBinPath, file), false) {
+			t.Fatalf("Failed: %s found in %s/bin directory\n", file, goBinPath)
+		}
+	}
+}
diff --git a/x/format/gopstyle_test.go b/x/format/gopstyle_test.go
index 5b1e8159..e2bb3e99 100644
--- a/x/format/gopstyle_test.go
+++ b/x/format/gopstyle_test.go
@@ -170,7 +170,7 @@ import (
 
 func f() {
 	fmt.Errorf("%w", errors.New("hello"))
-	fmt.Println("hello")
+	fmt.println "hello"
 }
 `, `import (
 	"errors"
@@ -191,7 +191,7 @@ import "fmt"
 
 func f() {
 	_ = fmt.Errorf("%w", errors.New("hello"))
-	fmt.Println("hello")
+	fmt.println("hello")
 }
 `, `import "errors"
 
@@ -342,3 +342,139 @@ func f() {
 }
 `)
 }
+
+func TestErrorfWithPackageLevelVar(t *testing.T) {
+	testFormat(t, "print", `package main
+
+import "errors"
+import "fmt"
+
+var _ = fmt.Errorf("hello %w", errors.New("world"))
+`, `import "errors"
+
+var _ = errorf("hello %w", errors.new("world"))
+`)
+}
+
+func TestPrintlnWithFmtVarNoImport(t *testing.T) {
+	testFormat(t, "print", `package main
+
+func f() {
+	var fmt Foo
+	fmt.Println(1)
+}
+`, `func f() {
+	var fmt Foo
+	fmt.println 1
+}
+`)
+}
+
+func TestPrintlnWithFmtVar(t *testing.T) {
+	testFormat(t, "print", `package main
+
+import "fmt"
+
+var _ fmt.Stringer
+
+func f() {
+	var fmt Foo
+	fmt.Println(1)
+}
+`, `import "fmt"
+
+var _ fmt.Stringer
+
+func f() {
+	var fmt Foo
+	fmt.println 1
+}
+`)
+}
+
+// todo: fix inner scope vars
+// func TestPrintlnWithScopedFmtVar(t *testing.T) {
+// 	testFormat(t, "print", `package main
+//
+// import "fmt"
+//
+// func f() {
+// 	{
+// 		var fmt Foo
+// 		_ = fmt
+// 	}
+// 	fmt.Println(1)
+// }
+// `, `func f() {
+// 	{
+// 		var fmt Foo
+// 		_ = fmt
+// 	}
+// 	println 1
+// }
+// `)
+// }
+
+func TestPrintlnWithFmtVarAfter(t *testing.T) {
+	testFormat(t, "print", `package main
+
+import "fmt"
+
+func f() {
+	fmt.Println(1)
+	var fmt Foo
+	_ = fmt
+}
+`, `func f() {
+	println 1
+	var fmt Foo
+	_ = fmt
+}
+`)
+}
+
+func TestPrintlnWithPackageLevelFmtVar(t *testing.T) {
+	testFormat(t, "print", `package main
+
+var fmt Foo
+
+func f() {
+	fmt.Println(1)
+}
+`, `var fmt Foo
+
+func f() {
+	fmt.println 1
+}
+`)
+}
+
+func TestPrintlnWithPackageLevelFmtVarAfter(t *testing.T) {
+	testFormat(t, "print", `package main
+
+func f() {
+	fmt.Println(1)
+}
+
+var fmt Foo
+`, `func f() {
+	fmt.println 1
+}
+
+var fmt Foo
+`)
+}
+
+func TestPrintlnWithVarFromCall(t *testing.T) {
+	testFormat(t, "print", `package main
+
+func f() {
+	var fmt = Foo()
+	fmt.Println(1)
+}
+`, `func f() {
+	var fmt = Foo()
+	fmt.println 1
+}
+`)
+}