Repository: goplus/gop
Pull request #1404 (2023-08-26 17:02:23)

support main.spx; classfile: register => import; IsClass => ClassKind


Patch:
diff --git a/.gitignore b/.gitignore
index ffb5ce04..18f4ec7e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,8 +33,7 @@ coverage.txt
 .gop/
 gop_autogen*.go
 go.json
-go.work
-go.work.sum
+go.work*
 format.result
 *.cache
 
diff --git a/ast/ast.go b/ast/ast.go
index 521971d3..ac758818 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -1135,6 +1135,7 @@ type File struct {
 	NoPkgDecl    bool // no `package xxx` declaration
 	IsClass      bool // is a classfile
 	IsProj       bool // is a project classfile
+	IsNormalGox  bool // is a normal .gox file
 }
 
 // Pos returns position of first character belonging to the node.
diff --git a/ast/filter.go b/ast/filter.go
index 2a48f4b2..baa1e8be 100644
--- a/ast/filter.go
+++ b/ast/filter.go
@@ -37,7 +37,6 @@ func exportFilter(name string) bool {
 // stripped. The File.Comments list is not changed.
 //
 // FileExports reports whether there are exported declarations.
-//
 func FileExports(src *File) bool {
 	return filterFile(src, exportFilter, true)
 }
@@ -48,7 +47,6 @@ func FileExports(src *File) bool {
 //
 // PackageExports reports whether there are exported declarations;
 // it returns false otherwise.
-//
 func PackageExports(pkg *Package) bool {
 	return filterPackage(pkg, exportFilter, true)
 }
@@ -73,7 +71,6 @@ func filterIdentList(list []*Ident, f Filter) []*Ident {
 // fieldName assumes that x is the type of an anonymous field and
 // returns the corresponding field name. If x is not an acceptable
 // anonymous field, the result is nil.
-//
 func fieldName(x Expr) *Ident {
 	switch t := x.(type) {
 	case *Ident:
@@ -243,7 +240,6 @@ func filterSpecList(list []Spec, f Filter, export bool) []Spec {
 //
 // FilterDecl reports whether there are any declared names left after
 // filtering.
-//
 func FilterDecl(decl Decl, f Filter) bool {
 	return filterDecl(decl, f, false)
 }
@@ -268,7 +264,6 @@ func filterDecl(decl Decl, f Filter, export bool) bool {
 //
 // FilterFile reports whether there are any top-level declarations
 // left after filtering.
-//
 func FilterFile(src *File, f Filter) bool {
 	return filterFile(src, f, false)
 }
@@ -295,7 +290,6 @@ func filterFile(src *File, f Filter, export bool) bool {
 //
 // FilterPackage reports whether there are any top-level declarations
 // left after filtering.
-//
 func FilterPackage(pkg *Package, f Filter) bool {
 	return filterPackage(pkg, f, false)
 }
@@ -329,7 +323,6 @@ const (
 // nameOf returns the function (foo) or method name (foo.bar) for
 // the given function declaration. If the AST is incorrect for the
 // receiver, it assumes a function instead.
-//
 func nameOf(f *FuncDecl) string {
 	if r := f.Recv; r != nil && len(r.List) == 1 {
 		// looks like a correct receiver declaration
@@ -349,12 +342,10 @@ func nameOf(f *FuncDecl) string {
 
 // separator is an empty //-style comment that is interspersed between
 // different comment groups when they are concatenated into a single group
-//
 var separator = &Comment{Slash: token.NoPos, Text: "//"}
 
 // MergePackageFiles creates a file AST by merging the ASTs of the
 // files belonging to a package. The mode flags control merging behavior.
-//
 func MergePackageFiles(pkg *Package, mode MergeMode) *File {
 	// Count the number of package docs, comments and declarations across
 	// all package files. Also, compute sorted list of filenames, so that
@@ -509,6 +500,6 @@ func MergePackageFiles(pkg *Package, mode MergeMode) *File {
 	// TODO(gri) need to compute unresolved identifiers!
 	return &File{
 		doc, pos, NewIdent(pkg.Name), decls, pkg.Scope,
-		imports, nil, comments, nil, false, false, false, false,
+		imports, nil, comments, nil, false, false, false, false, false,
 	}
 }
diff --git a/cl/classfile.go b/cl/classfile.go
index cd2079fb..6e0fe6c4 100644
--- a/cl/classfile.go
+++ b/cl/classfile.go
@@ -60,14 +60,14 @@ func (p *gmxSettings) getScheds(cb *gox.CodeBuilder) []goast.Stmt {
 	return p.schedStmts
 }
 
-func newGmx(ctx *pkgCtx, pkg *gox.Package, file string, conf *Config) *gmxSettings {
-	ext, _ := parser.ClassFileExt(file)
+func newGmx(ctx *pkgCtx, pkg *gox.Package, file string, f *ast.File, conf *Config) *gmxSettings {
+	ext := parser.ClassFileExt(file)
 	gt, ok := conf.LookupClass(ext)
 	if !ok {
 		panic("TODO: class not found")
 	}
 	var name string
-	if gt.Ext == ext {
+	if f.IsProj {
 		_, name = filepath.Split(file)
 		if idx := strings.Index(name, "."); idx > 0 {
 			name = name[:idx]
diff --git a/cl/compile.go b/cl/compile.go
index 232e27ea..b1d71e1f 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -402,17 +402,15 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 	})
 	for file, gmx := range files {
 		if gmx.IsProj {
-			ctx.gmxSettings = newGmx(ctx, p, file, conf)
+			ctx.gmxSettings = newGmx(ctx, p, file, gmx, conf)
 			break
 		}
 	}
 	if ctx.gmxSettings == nil {
 		for file, gmx := range files {
-			if gmx.IsClass {
-				if ext, _ := parser.ClassFileExt(file); ext != ".gox" {
-					ctx.gmxSettings = newGmx(ctx, p, file, conf)
-					break
-				}
+			if gmx.IsClass && !gmx.IsNormalGox {
+				ctx.gmxSettings = newGmx(ctx, p, file, gmx, conf)
+				break
 			}
 		}
 	}
@@ -562,7 +560,7 @@ func preloadGopFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, con
 	case f.IsClass:
 		classType = getDefaultClass(file)
 		if parent.gmxSettings != nil {
-			ext, _ := parser.ClassFileExt(file)
+			ext := parser.ClassFileExt(file)
 			o, ok := parent.sprite[ext]
 			if ok {
 				baseTypeName, baseType, spxClass = o.Name(), o.Type(), true
diff --git a/cl/expr.go b/cl/expr.go
index 6349d347..5aa4d80b 100644
--- a/cl/expr.go
+++ b/cl/expr.go
@@ -887,7 +887,6 @@ func compileSliceLit(ctx *blockCtx, v *ast.SliceLit, typ types.Type) {
 	} else {
 		ctx.cb.SliceLit(typ, n)
 	}
-	return
 }
 
 func compileRangeExpr(ctx *blockCtx, v *ast.RangeExpr) {
diff --git a/cl/func_type_and_var.go b/cl/func_type_and_var.go
index 503b1053..7fe59d7f 100644
--- a/cl/func_type_and_var.go
+++ b/cl/func_type_and_var.go
@@ -243,7 +243,7 @@ func lookupType(ctx *blockCtx, name string) (types.Object, types.Object) {
 }
 
 type checkRedecl struct {
-	ctx   *blockCtx
+	// ctx *blockCtx
 	names map[string]token.Pos
 }
 
diff --git a/cl/types_go118.go b/cl/types_go118.go
index 4c723913..52b2ea4b 100644
--- a/cl/types_go118.go
+++ b/cl/types_go118.go
@@ -81,7 +81,7 @@ L:
 	}
 	switch t := typ.(type) {
 	case *ast.IndexExpr:
-		if 1 != orgTypeParams.Len() {
+		if orgTypeParams.Len() != 1 {
 			panic(ctx.newCodeErrorf(typ.Pos(), "got 1 type parameter, but receiver base type declares %v", orgTypeParams.Len()))
 		}
 		v := t.Index.(*ast.Ident)
diff --git a/cmd/gopfmt/gopfmt.go b/cmd/gopfmt/gopfmt.go
index 9855735e..500eb557 100644
--- a/cmd/gopfmt/gopfmt.go
+++ b/cmd/gopfmt/gopfmt.go
@@ -24,7 +24,6 @@ import (
 	"go/token"
 	"io"
 	"io/fs"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strings"
@@ -64,7 +63,7 @@ func processFile(filename string, class bool, in io.Reader, out io.Writer) error
 			return err
 		}
 	}
-	src, err := ioutil.ReadAll(in)
+	src, err := io.ReadAll(in)
 	if err != nil {
 		return err
 	}
@@ -90,7 +89,7 @@ func processFile(filename string, class bool, in io.Reader, out io.Writer) error
 
 	if *write {
 		dir, file := filepath.Split(filename)
-		f, err := ioutil.TempFile(dir, file)
+		f, err := os.CreateTemp(dir, file)
 		if err != nil {
 			return err
 		}
@@ -141,10 +140,8 @@ func (w *walker) walk(path string, d fs.DirEntry, err error) error {
 					case ".gox", ".spx", ".gmx":
 						ok, class = true, true
 					default:
-						_, class = mod.IsClass(ext)
-						if class {
-							ok = true
-						}
+						class = mod.IsClass(ext)
+						ok = class
 					}
 					return
 				}
diff --git a/cmd/internal/gopfmt/fmt.go b/cmd/internal/gopfmt/fmt.go
index a90c2de8..d03045d0 100644
--- a/cmd/internal/gopfmt/fmt.go
+++ b/cmd/internal/gopfmt/fmt.go
@@ -21,7 +21,6 @@ import (
 	"bytes"
 	"fmt"
 	"io/fs"
-	"io/ioutil"
 	"log"
 	"os"
 	"path/filepath"
@@ -63,7 +62,7 @@ var (
 )
 
 func gopfmt(path string, class, smart, mvgo bool) (err error) {
-	src, err := ioutil.ReadFile(path)
+	src, err := os.ReadFile(path)
 	if err != nil {
 		return
 	}
@@ -106,7 +105,7 @@ func gopfmt(path string, class, smart, mvgo bool) (err error) {
 
 func writeFileWithBackup(path string, target []byte) (err error) {
 	dir, file := filepath.Split(path)
-	f, err := ioutil.TempFile(dir, file)
+	f, err := os.CreateTemp(dir, file)
 	if err != nil {
 		return
 	}
@@ -150,10 +149,8 @@ func (w *walker) walk(path string, d fs.DirEntry, err error) error {
 					case ".gox", ".spx", ".gmx":
 						ok, class = true, true
 					default:
-						_, class = mod.IsClass(ext)
-						if class {
-							ok = true
-						}
+						class = mod.IsClass(ext)
+						ok = class
 					}
 					return
 				}
diff --git a/cmd/internal/gopget/get.go b/cmd/internal/gopget/get.go
index b4fb4f86..1142f37f 100644
--- a/cmd/internal/gopget/get.go
+++ b/cmd/internal/gopget/get.go
@@ -82,7 +82,7 @@ func get(pkgPath string) {
 	pkgMod, err := modload.Load(pkgModRoot, 0)
 	check(err)
 	if pkgMod.Project != nil {
-		mod.AddRegister(pkgModVer.Path)
+		mod.AddImport(pkgModVer.Path)
 		fmt.Fprintf(os.Stderr, "gop get: registered %s\n", pkgModVer.Path)
 	}
 
diff --git a/gendeps.go b/gendeps.go
index be9bf9e2..c02d461a 100644
--- a/gendeps.go
+++ b/gendeps.go
@@ -36,7 +36,7 @@ import (
 func GenDepMods(mod *gopmod.Module, dir string, recursively bool) (ret map[string]struct{}, err error) {
 	modBase := mod.Path()
 	ret = make(map[string]struct{})
-	for _, r := range mod.Register {
+	for _, r := range mod.Import {
 		ret[r.ClassfileMod] = struct{}{}
 	}
 	err = HandleDeps(mod, dir, recursively, func(pkgPath string) {
@@ -81,8 +81,8 @@ type depsGen struct {
 
 func (p depsGen) gen(dir string) (err error) {
 	pkgs, err := parser.ParseDirEx(p.fset, dir, parser.Config{
-		IsClass: p.mod.IsClass,
-		Mode:    parser.ImportsOnly,
+		ClassKind: p.mod.ClassKind,
+		Mode:      parser.ImportsOnly,
 	})
 	if err != nil {
 		return
diff --git a/go.mod b/go.mod
index c433c26e..410cdcf1 100644
--- a/go.mod
+++ b/go.mod
@@ -6,6 +6,6 @@ require (
 	github.com/goplus/c2go v0.7.13
 	github.com/goplus/gox v1.11.37
 	github.com/goplus/libc v0.3.13
-	github.com/goplus/mod v0.10.1
+	github.com/goplus/mod v0.11.2
 	github.com/qiniu/x v1.11.9
 )
diff --git a/go.sum b/go.sum
index 807da170..75d4d088 100644
--- a/go.sum
+++ b/go.sum
@@ -12,8 +12,8 @@ github.com/goplus/gox v1.11.37/go.mod h1:NkgUJWIjKxrhUwM4bgyUt3ZE0WlTunqfksMzrbn
 github.com/goplus/libc v0.3.13 h1:2yHG8ghezBTK3Da7Fw1lNiitmt+Va+RzwKJ5GuqypCQ=
 github.com/goplus/libc v0.3.13/go.mod h1:xqG4/g3ilKBE/UDn5vkaE7RRQPQPyspj7ecuMuvlQJ8=
 github.com/goplus/mod v0.9.12/go.mod h1:YoPIowz71rnLLROA4YG0AC8bzDtPRyMaQwgTRLr8ri4=
-github.com/goplus/mod v0.10.1 h1:us4GMfWL+7tcK8diI7Z90awoVmUgDhftt83NeeqgYE4=
-github.com/goplus/mod v0.10.1/go.mod h1:cD7VktDd88SM0fPJ9oT3z5r8KTBW+EGi0gA3G8cDKHo=
+github.com/goplus/mod v0.11.2 h1:kgIdro905lTZgqGecTbZ416O8ayN+ca2DR5OB4ayeWI=
+github.com/goplus/mod v0.11.2/go.mod h1:d40I3nOr2qkGfLUjwc/BqLSq3oUUlQy5+/SpdiBKgY4=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
@@ -37,7 +37,6 @@ golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIi
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
 golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
diff --git a/load.go b/load.go
index ad568952..3394b74b 100644
--- a/load.go
+++ b/load.go
@@ -58,7 +58,7 @@ func LoadMod(dir string, gop *env.Gop, conf *Config) (mod *gopmod.Module, err er
 		return
 	}
 	if mod != nil {
-		err = mod.RegisterClasses()
+		err = mod.ImportClasses()
 		if err != nil {
 			err = errors.NewWith(err, `mod.RegisterClasses()`, -2, "(*gopmod.Module).RegisterClasses", mod)
 			return
@@ -107,9 +107,9 @@ func LoadDir(dir string, conf *Config, genTestPkg bool, promptGenGo ...bool) (ou
 	}
 
 	pkgs, err := parser.ParseDirEx(fset, dir, parser.Config{
-		IsClass: mod.IsClass,
-		Filter:  conf.Filter,
-		Mode:    parser.ParseComments,
+		ClassKind: mod.ClassKind,
+		Filter:    conf.Filter,
+		Mode:      parser.ParseComments,
 	})
 	if err != nil {
 		return
diff --git a/parser/parser.go b/parser/parser.go
index 18d0f16b..2e5a803f 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -2104,6 +2104,7 @@ func isLiteralType(x ast.Expr) bool {
 	return true
 }
 
+/*
 // If x is of the form *T, deref returns T, otherwise it returns x.
 func deref(x ast.Expr) ast.Expr {
 	if p, isPtr := x.(*ast.StarExpr); isPtr {
@@ -2111,6 +2112,7 @@ func deref(x ast.Expr) ast.Expr {
 	}
 	return x
 }
+*/
 
 // If x is of the form (T), unparen returns unparen(T), otherwise it returns x.
 func unparen(x ast.Expr) ast.Expr {
diff --git a/parser/parser_gop.go b/parser/parser_gop.go
index 3e7d6f74..3f3cac3d 100644
--- a/parser/parser_gop.go
+++ b/parser/parser_gop.go
@@ -24,6 +24,7 @@ import (
 	"io/fs"
 	"io/ioutil"
 	"os"
+	"path"
 	"path/filepath"
 	"strings"
 
@@ -105,9 +106,9 @@ func ParseDir(fset *token.FileSet, path string, filter func(fs.FileInfo) bool, m
 }
 
 type Config struct {
-	IsClass func(ext string) (isProj bool, ok bool)
-	Filter  func(fs.FileInfo) bool
-	Mode    Mode
+	ClassKind func(fname string) (isProj, ok bool)
+	Filter    func(fs.FileInfo) bool
+	Mode      Mode
 }
 
 // ParseDirEx calls ParseFSDir by passing a local filesystem.
@@ -116,11 +117,11 @@ func ParseDirEx(fset *token.FileSet, path string, conf Config) (pkgs map[string]
 }
 
 // ClassFileExt returns the classfile extension
-func ClassFileExt(path string) (ext string, compositeGox bool) {
+func ClassFileExt(path string) (ext string) {
 	ext = filepath.Ext(path)
 	if ext == ".gox" {
 		if c := filepath.Ext(path[:len(path)-4]); c != "" {
-			return c, true
+			return c
 		}
 	}
 	return
@@ -143,8 +144,8 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, path string, conf Config) (p
 	if err != nil {
 		return nil, err
 	}
-	if conf.IsClass == nil {
-		conf.IsClass = defaultIsClass
+	if conf.ClassKind == nil {
+		conf.ClassKind = defaultClassKind
 	}
 	pkgs = make(map[string]*ast.Package)
 	for _, d := range list {
@@ -152,8 +153,9 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, path string, conf Config) (p
 			continue
 		}
 		fname := d.Name()
-		ext, compositeGox := ClassFileExt(fname)
-		var isProj, isClass, useGoParser bool
+		fnameRmGox := fname
+		ext := filepath.Ext(fname)
+		var isProj, isClass, isNormalGox, useGoParser, rmGox bool
 		switch ext {
 		case ".gop":
 		case ".go":
@@ -163,16 +165,25 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, path string, conf Config) (p
 			useGoParser = (conf.Mode & ParseGoAsGoPlus) == 0
 		case ".gox":
 			isClass = true
+			t := fname[:len(fname)-4]
+			if c := filepath.Ext(t); c != "" {
+				ext, fnameRmGox, rmGox = c, t, true
+			} else {
+				isNormalGox = true
+			}
+			fallthrough
 		default:
-			if isProj, isClass = conf.IsClass(ext); !isClass {
-				if compositeGox {
-					return nil, fmt.Errorf("not found Go+ class by ext %q for %q", ext, fname)
+			if !isNormalGox {
+				if isProj, isClass = conf.ClassKind(fnameRmGox); !isClass {
+					if rmGox {
+						return nil, fmt.Errorf("not found Go+ class by ext %q for %q", ext, fname)
+					}
+					continue
 				}
-				continue
 			}
 		}
 		mode := conf.Mode
-		if isProj || isClass {
+		if isClass {
 			mode |= ParseGoPlusClass
 		}
 		if !strings.HasPrefix(fname, "_") && (conf.Filter == nil || conf.Filter(d)) {
@@ -193,6 +204,7 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, path string, conf Config) (p
 				}
 			} else if src, err := ParseFSFile(fset, fs, filename, nil, mode); err == nil {
 				src.IsProj, src.IsClass = isProj, isClass
+				src.IsNormalGox = isNormalGox
 				pkg := reqPkg(pkgs, src.Name.Name)
 				pkg.Files[filename] = src
 			} else if first == nil {
@@ -215,13 +227,13 @@ func reqPkg(pkgs map[string]*ast.Package, name string) *ast.Package {
 	return pkg
 }
 
-func defaultIsClass(ext string) (isProj bool, ok bool) {
+func defaultClassKind(fname string) (isProj bool, ok bool) {
+	ext := path.Ext(fname)
 	switch ext {
 	case ".gmx":
-		isProj = true
-		fallthrough
+		return true, true
 	case ".spx":
-		ok = true
+		return ext == "main.spx", true
 	}
 	return
 }


Test patch:
diff --git a/cl/builtin_test.go b/cl/builtin_test.go
index 1f0d3ff1..dff76851 100644
--- a/cl/builtin_test.go
+++ b/cl/builtin_test.go
@@ -114,7 +114,7 @@ func TestClRangeStmt(t *testing.T) {
 
 func TestGmxSettings(t *testing.T) {
 	pkg := gox.NewPackage("", "foo", goxConf)
-	gmx := newGmx(nil, pkg, "main.t2gmx", &Config{
+	gmx := newGmx(nil, pkg, "main.t2gmx", &ast.File{IsProj: true}, &Config{
 		LookupClass: lookupClass,
 	})
 	scheds := gmx.getScheds(pkg.CB())
diff --git a/cl/compile_spx_test.go b/cl/compile_spx_test.go
index cd0a8b1a..fc25f66f 100644
--- a/cl/compile_spx_test.go
+++ b/cl/compile_spx_test.go
@@ -19,6 +19,7 @@ package cl_test
 import (
 	"bytes"
 	"os"
+	"path"
 	"testing"
 
 	"github.com/goplus/gop/cl"
@@ -52,7 +53,8 @@ func lookupClass(ext string) (c *modfile.Project, ok bool) {
 
 func spxParserConf() parser.Config {
 	return parser.Config{
-		IsClass: func(ext string) (isProj bool, ok bool) {
+		ClassKind: func(fname string) (isProj bool, ok bool) {
+			ext := path.Ext(fname)
 			c, ok := lookupClass(ext)
 			if ok {
 				isProj = (c.Ext == ext)
diff --git a/parser/parser_test.go b/parser/parser_test.go
index 028e574f..e1e866d5 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -25,6 +25,20 @@ import (
 
 // -----------------------------------------------------------------------------
 
+func TestExt(t *testing.T) {
+	cases := [][2]string{
+		{"t.spx.gox", ".spx"},
+		{"t.spx", ".spx"},
+		{"t.gox", ".gox"},
+		{"t.abc", ".abc"},
+	}
+	for _, c := range cases {
+		if ret := ClassFileExt(c[0]); ret != c[1] {
+			t.Fatal("ClassFileExt:", c[0], "expected:", c[1], "got:", ret)
+		}
+	}
+}
+
 func panicMsg(e interface{}) string {
 	switch v := e.(type) {
 	case string: