From 885063290c725684dab0d4cd8157af78a07e1213 Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Sun, 10 Sep 2023 15:11:11 +0200 Subject: [PATCH 1/4] feat: import work from https://github.com/gnolang/gno/pull/1092 Signed-off-by: Manfred Touron <94029+moul@users.noreply.github.com> --- cmd/gnoffee/main.go | 141 +++++++++++++++ cmd/gnoffee/main_test.go | 50 ++++++ .../testdata/valid_sample_with_export.txtar | 61 +++++++ pkg/gnoffee/doc.go | 30 ++++ pkg/gnoffee/gnoffee_test.go | 76 +++++++++ pkg/gnoffee/stage1.go | 18 ++ pkg/gnoffee/stage1_test.go | 104 ++++++++++++ pkg/gnoffee/stage2.go | 160 ++++++++++++++++++ pkg/gnoffee/stage2_test.go | 139 +++++++++++++++ pkg/gnoffee/utils.go | 51 ++++++ pkg/gnoffee/utils_test.go | 85 ++++++++++ 11 files changed, 915 insertions(+) create mode 100644 cmd/gnoffee/main.go create mode 100644 cmd/gnoffee/main_test.go create mode 100644 cmd/gnoffee/testdata/valid_sample_with_export.txtar create mode 100644 pkg/gnoffee/doc.go create mode 100644 pkg/gnoffee/gnoffee_test.go create mode 100644 pkg/gnoffee/stage1.go create mode 100644 pkg/gnoffee/stage1_test.go create mode 100644 pkg/gnoffee/stage2.go create mode 100644 pkg/gnoffee/stage2_test.go create mode 100644 pkg/gnoffee/utils.go create mode 100644 pkg/gnoffee/utils_test.go diff --git a/cmd/gnoffee/main.go b/cmd/gnoffee/main.go new file mode 100644 index 0000000..29a03a6 --- /dev/null +++ b/cmd/gnoffee/main.go @@ -0,0 +1,141 @@ +package main + +import ( + "flag" + "fmt" + "go/ast" + "go/parser" + "go/printer" + "go/token" + "io/ioutil" + "os" + "path/filepath" + + "github.com/gnolang/gno/gnovm/pkg/gnoffee" +) + +var writeFlag bool + +func init() { + flag.BoolVar(&writeFlag, "w", false, "write result to gnoffee.gen.go file instead of stdout") +} + +func main() { + flag.Parse() + args := flag.Args() + + if len(args) < 1 { + fmt.Fprintln(os.Stderr, "Usage: gnoffee [-w] ") + return + } + + err := doMain(args[0]) + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } +} + +func doMain(arg string) error { + fset, pkg, err := processPackageOrFileOrStdin(arg) + if err != nil { + return fmt.Errorf("parse error: %w", err) + } + + newFile, err := gnoffee.Stage2(pkg) + if err != nil { + return fmt.Errorf("processing the AST: %w", err) + } + + // combine existing files into newFile to generate a unique file for the whole package. + for _, file := range pkg { + newFile.Decls = append(newFile.Decls, file.Decls...) + } + + // Create a new package comment. + commentText := "// Code generated by \"gnoffee\". DO NOT EDIT." + + if writeFlag { + filename := "gnoffee.gen.go" + f, err := os.Create(filename) + if err != nil { + return fmt.Errorf("creating file %q: %w", filename, err) + } + defer f.Close() + + _, err = fmt.Fprintln(f, commentText) + if err != nil { + return fmt.Errorf("writing to file %q: %w", filename, err) + } + err = printer.Fprint(f, fset, newFile) + if err != nil { + return fmt.Errorf("writing to file %q: %w", filename, err) + } + } else { + _, _ = fmt.Println(commentText) + _ = printer.Fprint(os.Stdout, fset, newFile) + } + return nil +} + +func processPackageOrFileOrStdin(arg string) (*token.FileSet, map[string]*ast.File, error) { + fset := token.NewFileSet() + pkg := map[string]*ast.File{} + + processFile := func(data []byte, filename string) error { + source := string(data) + source = gnoffee.Stage1(source) + + parsedFile, err := parser.ParseFile(fset, filename, source, parser.ParseComments) + if err != nil { + return fmt.Errorf("parsing file %q: %w", filename, err) + } + pkg[filename] = parsedFile + return nil + } + + // process arg + if arg == "-" { + // Read from stdin and process + data, err := ioutil.ReadAll(os.Stdin) + if err != nil { + return nil, nil, fmt.Errorf("reading from stdin: %w", err) + } + if err := processFile(data, "stdin.gnoffee"); err != nil { + return nil, nil, err + } + } else { + // If it's a directory, gather all .go and .gnoffee files and process accordingly + if info, err := os.Stat(arg); err == nil && info.IsDir() { + err := filepath.Walk(arg, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + ext := filepath.Ext(path) + if ext == ".gnoffee" { + data, err := ioutil.ReadFile(path) + if err != nil { + return fmt.Errorf("reading file %q: %w", path, err) + } + if err := processFile(data, path); err != nil { + return err + } + } + return nil + }) + if err != nil { + return nil, nil, err + } + } else { + data, err := ioutil.ReadFile(arg) + if err != nil { + return nil, nil, fmt.Errorf("reading file %q: %w", arg, err) + } + if err := processFile(data, arg); err != nil { + return nil, nil, err + } + } + } + return fset, pkg, nil +} diff --git a/cmd/gnoffee/main_test.go b/cmd/gnoffee/main_test.go new file mode 100644 index 0000000..5cee00d --- /dev/null +++ b/cmd/gnoffee/main_test.go @@ -0,0 +1,50 @@ +package main + +import ( + "os/exec" + "path/filepath" + "testing" + + "github.com/jaekwon/testify/require" + "github.com/rogpeppe/go-internal/testscript" +) + +func TestTest(t *testing.T) { + testscript.Run(t, setupTestScript(t, "testdata")) +} + +func setupTestScript(t *testing.T, txtarDir string) testscript.Params { + t.Helper() + // Get root location of github.com/gnolang/gno + goModPath, err := exec.Command("go", "env", "GOMOD").CombinedOutput() + require.NoError(t, err) + rootDir := filepath.Dir(string(goModPath)) + // Build a fresh gno binary in a temp directory + gnoffeeBin := filepath.Join(t.TempDir(), "gnoffee") + err = exec.Command("go", "build", "-o", gnoffeeBin, filepath.Join(rootDir, "gnovm", "cmd", "gnoffee")).Run() + require.NoError(t, err) + // Define script params + return testscript.Params{ + Setup: func(env *testscript.Env) error { + return nil + }, + Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){ + // add a custom "gnoffee" command so txtar files can easily execute "gno" + // without knowing where is the binary or how it is executed. + "gnoffee": func(ts *testscript.TestScript, neg bool, args []string) { + err := ts.Exec(gnoffeeBin, args...) + if err != nil { + ts.Logf("[%v]\n", err) + if !neg { + ts.Fatalf("unexpected gnoffee command failure") + } + } else { + if neg { + ts.Fatalf("unexpected gnoffee command success") + } + } + }, + }, + Dir: txtarDir, + } +} diff --git a/cmd/gnoffee/testdata/valid_sample_with_export.txtar b/cmd/gnoffee/testdata/valid_sample_with_export.txtar new file mode 100644 index 0000000..5d07e7e --- /dev/null +++ b/cmd/gnoffee/testdata/valid_sample_with_export.txtar @@ -0,0 +1,61 @@ +# Test with a valid sample.gnoffee + +gnoffee -w . + +! stderr .+ +! stdout .+ + +cmp gen.golden gnoffee.gen.go + +-- sample.gnoffee -- +package sample + +type foo struct{} + +export baz as Bar + +var baz = foo{} + +func (f *foo) Hello() string { + return "Hello from foo!" +} + +func (f *foo) Bye() { + println("Goodbye from foo!") +} + +type Bar interface { + Hello() string + Bye() +} + +-- gen.golden -- +// Code generated by "gnoffee". DO NOT EDIT. +package sample + +// This function was generated by gnoffee due to the export directive. +func Hello() string { + return baz.Hello() +} + +// This function was generated by gnoffee due to the export directive. +func Bye() { + baz.Bye() +} + +type foo struct{} + +var baz = foo{} + +func (f *foo) Hello() string { + return "Hello from foo!" +} + +func (f *foo) Bye() { + println("Goodbye from foo!") +} + +type Bar interface { + Hello() string + Bye() +} diff --git a/pkg/gnoffee/doc.go b/pkg/gnoffee/doc.go new file mode 100644 index 0000000..f2c17cb --- /dev/null +++ b/pkg/gnoffee/doc.go @@ -0,0 +1,30 @@ +// Package gnoffee provides a transpiler that extends the Go language +// with additional, custom keywords. These keywords offer enhanced +// functionality, aiming to make Go programming even more efficient +// and expressive. +// +// Current supported keywords and transformations: +// - `export as `: +// This allows for the automatic generation of top-level functions +// in the package that call methods on a specific instance of the struct. +// It's a way to "expose" or "proxy" methods of a struct via free functions. +// +// How Gnoffee Works: +// Gnoffee operates in multiple stages. The first stage transforms +// gnoffee-specific keywords into their comment directive equivalents, +// paving the way for the second stage to handle the transpiling logic. +// +// The Package Path: +// Gnoffee is currently housed under the gnovm namespace, with the +// package path being: github.com/gnolang/gno/gnovm/pkg/gnoffee. +// +// However, it's important to note that while gnoffee resides in the gnovm +// namespace, it operates independently from the gnovm. There's potential +// for gnoffee to be relocated in the future based on its evolving role +// and development trajectory. +// +// Future Changes: +// As the Go and Gno ecosystems and requirements evolve, gnoffee might see the +// introduction of new keywords or alterations to its current functionality. +// Always refer to the package documentation for the most up-to-date details. +package gnoffee diff --git a/pkg/gnoffee/gnoffee_test.go b/pkg/gnoffee/gnoffee_test.go new file mode 100644 index 0000000..57ae3aa --- /dev/null +++ b/pkg/gnoffee/gnoffee_test.go @@ -0,0 +1,76 @@ +package gnoffee + +import ( + "bytes" + "go/ast" + "go/format" + "go/parser" + "go/token" + "testing" +) + +func TestPackage(t *testing.T) { + inputCode := ` +package sample + +export foo as Bar + +type foo struct{} + +func (f *foo) Hello() string { + return "Hello from foo!" +} + +func (f *foo) Bye() { + println("Goodbye from foo!") +} + +type Bar interface { + Hello() string + Bye() +} +` + expectedOutput := ` +package sample + +// This function was generated by gnoffee due to the export directive. +func Hello() string { + return foo.Hello() +} + +// This function was generated by gnoffee due to the export directive. +func Bye() { + foo.Bye() +} +` + + // Stage 1 + inputCode = Stage1(inputCode) + + // Stage 2 + fset := token.NewFileSet() + file, err := parser.ParseFile(fset, "sample.go", inputCode, parser.ParseComments) + if err != nil { + t.Fatalf("Failed to parse input: %v", err) + } + + files := map[string]*ast.File{ + "sample.go": file, + } + + generatedFile, err := Stage2(files) + if err != nil { + t.Fatalf("Error during Stage2 generation: %v", err) + } + + var buf bytes.Buffer + if err := format.Node(&buf, fset, generatedFile); err != nil { + t.Fatalf("Failed to format generated output: %v", err) + } + + generatedCode := normalizeGoCode(buf.String()) + expected := normalizeGoCode(expectedOutput) + if generatedCode != expected { + t.Errorf("Generated code does not match expected output.\nExpected:\n\n%v\n\nGot:\n\n%v", expected, generatedCode) + } +} diff --git a/pkg/gnoffee/stage1.go b/pkg/gnoffee/stage1.go new file mode 100644 index 0000000..07b3437 --- /dev/null +++ b/pkg/gnoffee/stage1.go @@ -0,0 +1,18 @@ +package gnoffee + +import ( + "regexp" +) + +// Stage1 converts the gnoffee-specific keywords into their comment directive equivalents. +func Stage1(src string) string { + // Handling the 'export' keyword + exportRegex := regexp.MustCompile(`(?m)^export\s+`) + src = exportRegex.ReplaceAllString(src, "//gnoffee:export ") + + // Handling the 'invar' keyword + invarRegex := regexp.MustCompile(`(?m)^invar\s+([\w\d_]+)\s+(.+)`) + src = invarRegex.ReplaceAllString(src, "//gnoffee:invar $1\nvar $1 $2") + + return src +} diff --git a/pkg/gnoffee/stage1_test.go b/pkg/gnoffee/stage1_test.go new file mode 100644 index 0000000..b05c244 --- /dev/null +++ b/pkg/gnoffee/stage1_test.go @@ -0,0 +1,104 @@ +package gnoffee + +import ( + "testing" +) + +func TestStage1(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + { + name: "Basic Export Functionality", + input: ` +export Foo as FooInstance +invar BarInterface = Baz +`, + expected: ` +//gnoffee:export Foo as FooInstance +//gnoffee:invar BarInterface +var BarInterface = Baz +`, + }, + { + name: "Complex Input with Mixed Code", + input: ` +func someFunction() { + println("Hello, World!") +} + +export Baz as BazInstance +invar QuxInterface = Baz + +func anotherFunction() bool { + return true +} + +export Quux as QuuxInstance +`, + expected: ` +func someFunction() { + println("Hello, World!") +} + +//gnoffee:export Baz as BazInstance +//gnoffee:invar QuxInterface +var QuxInterface = Baz + +func anotherFunction() bool { + return true +} + +//gnoffee:export Quux as QuuxInstance +`, + }, + { + name: "Input with No Changes", + input: ` +func simpleFunction() { + println("Just a simple function!") +} +`, + expected: ` +func simpleFunction() { + println("Just a simple function!") +} +`, + }, + { + name: "Already Annotated Source", + input: ` +// Some comment +//gnoffee:export AlreadyExported as AlreadyInstance +func someFunction() { + println("This function is already annotated!") +} + +//gnoffee:invar AlreadyInterface +var AlreadyInterface Already +`, + expected: ` +// Some comment +//gnoffee:export AlreadyExported as AlreadyInstance +func someFunction() { + println("This function is already annotated!") +} + +//gnoffee:invar AlreadyInterface +var AlreadyInterface Already +`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + output := Stage1(tt.input) + + if output != tt.expected { + t.Errorf("Expected:\n%s\nGot:\n%s\n", tt.expected, output) + } + }) + } +} diff --git a/pkg/gnoffee/stage2.go b/pkg/gnoffee/stage2.go new file mode 100644 index 0000000..ed8bbbc --- /dev/null +++ b/pkg/gnoffee/stage2.go @@ -0,0 +1,160 @@ +package gnoffee + +import ( + "errors" + "fmt" + "go/ast" + "strings" +) + +// Stage2 transforms the given AST files based on gnoffee directives +// and returns an AST for a new generated file based on the provided files. +func Stage2(files map[string]*ast.File) (*ast.File, error) { + return generateFile(files) +} + +func generateFile(pkg map[string]*ast.File) (*ast.File, error) { + exportMapping := make(map[string]string) + var packageName string + + for _, f := range pkg { + if packageName == "" && f.Name != nil { + packageName = f.Name.Name + } + + // Iterate over all comments in the file. + for _, commentGroup := range f.Comments { + for _, comment := range commentGroup.List { + // Make sure the comment starts a new line. + if strings.HasPrefix(comment.Text, "//") { + parts := strings.Fields(comment.Text) + switch parts[0] { + case "//gnoffee:export": + if len(parts) == 4 && parts[2] == "as" { + k, v := parts[1], parts[3] + exportMapping[k] = v + } else { + return nil, errors.New("invalid gnoffee:export syntax") + } + case "//gnoffee:invar": + return nil, errors.New("unimplemented: invar keyword") + default: + if strings.HasPrefix(parts[0], "//gnoffee:") { + return nil, fmt.Errorf("unknown gnoffee keyword: %s", parts[0]) + } + } + } + } + } + } + + newFile := &ast.File{ + Name: &ast.Ident{Name: packageName}, + Decls: make([]ast.Decl, 0), + } + + // Now, populate the newFile with the necessary declarations based on the exportMapping. + for k, v := range exportMapping { + for _, f := range pkg { + for _, decl := range f.Decls { + genDecl, ok := decl.(*ast.GenDecl) + if !ok { + continue + } + + for _, spec := range genDecl.Specs { + typeSpec, ok := spec.(*ast.TypeSpec) + if !ok { + continue + } + + iface, ok := typeSpec.Type.(*ast.InterfaceType) + if !ok { + continue + } + + if typeSpec.Name.Name != v { + continue + } + + for _, method := range iface.Methods.List { + fnDecl := &ast.FuncDecl{ + Name: method.Names[0], + Doc: &ast.CommentGroup{ + List: []*ast.Comment{ + { + Text: "\n// This function was generated by gnoffee due to the export directive.", + }, + }, + }, + Type: method.Type.(*ast.FuncType), + Body: &ast.BlockStmt{ + List: make([]ast.Stmt, 0), + }, + } + + callExpr := &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent(k), + Sel: method.Names[0], + }, + Args: funcTypeToIdentList(method.Type.(*ast.FuncType).Params), + } + + // Check if the method has return values + if method.Type.(*ast.FuncType).Results != nil && len(method.Type.(*ast.FuncType).Results.List) > 0 { + retStmt := &ast.ReturnStmt{ + Results: []ast.Expr{callExpr}, + } + fnDecl.Body.List = append(fnDecl.Body.List, retStmt) + } else { + exprStmt := &ast.ExprStmt{X: callExpr} + fnDecl.Body.List = append(fnDecl.Body.List, exprStmt) + } + + newFile.Decls = append(newFile.Decls, fnDecl) + } + } + } + } + } + + return newFile, nil +} + +func funcTypeToIdentList(fields *ast.FieldList) []ast.Expr { + var idents []ast.Expr + for _, field := range fields.List { + for _, name := range field.Names { + idents = append(idents, ast.NewIdent(name.Name)) + } + } + return idents +} + +func findObjectByName(file *ast.File, objectName string) ast.Node { + for _, decl := range file.Decls { + switch d := decl.(type) { + case *ast.GenDecl: + for _, spec := range d.Specs { + switch s := spec.(type) { + case *ast.TypeSpec: + if s.Name.Name == objectName { + return s.Type + } + case *ast.ValueSpec: + for _, name := range s.Names { + if name.Name == objectName { + return s.Type + } + } + } + } + case *ast.FuncDecl: + if d.Name.Name == objectName { + return d.Type + } + } + } + return nil +} diff --git a/pkg/gnoffee/stage2_test.go b/pkg/gnoffee/stage2_test.go new file mode 100644 index 0000000..cc20d64 --- /dev/null +++ b/pkg/gnoffee/stage2_test.go @@ -0,0 +1,139 @@ +package gnoffee + +import ( + "bytes" + "go/ast" + "go/format" + "go/parser" + "go/token" + "testing" +) + +func TestStage2(t *testing.T) { + tests := []struct { + name string + input string + wantOutput string + wantErr bool + }{ + { + name: "Basic Test", + input: ` + package test + + //gnoffee:export baz as Helloer + + type Helloer interface { + Hello() string + } + + type foo struct{} + + func (f *foo) Hello() string { + return "Hello from foo!" + } + + func (f *foo) Bye() { } + + var baz = foo{} + + var _ Helloer = &foo{} + `, + wantOutput: ` + package test + + // This function was generated by gnoffee due to the export directive. + func Hello() string { + return baz.Hello() + } + `, + wantErr: false, + }, + { + name: "Invalid Export Syntax", + input: ` + package test + + var foo struct{} + //gnoffee:export foo MyInterface3 + type MyInterface3 interface { + Baz() + } + `, + wantErr: true, + }, + { + name: "Already Annotated With gnoffee Comment", + input: ` + package test + + var foo = struct{} + + //gnoffee:export foo as MyInterface4 + type MyInterface4 interface { + Qux() + } + `, + wantOutput: ` + package test + + // This function was generated by gnoffee due to the export directive. + func Qux() { + foo.Qux() + } + `, + wantErr: false, + }, + { + name: "No Export Directive", + input: ` + package test + + type SimpleInterface interface { + Moo() + } + `, + wantOutput: ` + package test + `, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fset := token.NewFileSet() + file, err := parser.ParseFile(fset, "", tt.input, parser.ParseComments) + if err != nil { + t.Fatalf("Failed to parse input: %v", err) + } + + files := map[string]*ast.File{ + "test.go": file, + } + + generatedFile, err := Stage2(files) + switch { + case err == nil && tt.wantErr: + t.Fatalf("Expected an error") + case err != nil && !tt.wantErr: + t.Fatalf("Error during Stage2 generation: %v", err) + case err != nil && tt.wantErr: + return + case err == nil && !tt.wantErr: + // noop + } + + var buf bytes.Buffer + if err := format.Node(&buf, fset, generatedFile); err != nil { + t.Fatalf("Failed to format generated output: %v", err) + } + + generatedCode := normalizeGoCode(buf.String()) + expected := normalizeGoCode(tt.wantOutput) + if generatedCode != expected { + t.Errorf("Transformed code does not match expected output.\nExpected:\n\n%v\n\nGot:\n\n%v", expected, generatedCode) + } + }) + } +} diff --git a/pkg/gnoffee/utils.go b/pkg/gnoffee/utils.go new file mode 100644 index 0000000..9675ad2 --- /dev/null +++ b/pkg/gnoffee/utils.go @@ -0,0 +1,51 @@ +package gnoffee + +import ( + "strings" +) + +// normalizeGoCode normalizes a multi-line Go code string by +// trimming the common leading white spaces from each line while preserving indentation. +func normalizeGoCode(code string) string { + code = strings.ReplaceAll(code, "\t", " ") + + lines := strings.Split(code, "\n") + + const defaultMax = 1337 // Initialize max with an arbitrary value + + // Determine the minimum leading whitespace across all lines + minLeadingSpaces := defaultMax + for _, line := range lines { + // skip empty lines + if len(strings.TrimSpace(line)) == 0 { + continue + } + + leadingSpaces := len(line) - len(strings.TrimLeft(line, " ")) + // println(len(line), len(strings.TrimLeft(line, " ")), "AAA", strings.TrimLeft(line, " "), "BBB") + if leadingSpaces < minLeadingSpaces { + minLeadingSpaces = leadingSpaces + } + } + // println(minLeadingSpaces) + // println() + + if minLeadingSpaces == defaultMax { + return code + } + + // Trim the determined number of leading whitespaces from all lines + var normalizedLines []string + for _, line := range lines { + if len(line) > minLeadingSpaces { + normalizedLines = append(normalizedLines, line[minLeadingSpaces:]) + } else { + normalizedLines = append(normalizedLines, strings.TrimSpace(line)) + } + } + + normalizedCode := strings.Join(normalizedLines, "\n") + normalizedCode = strings.ReplaceAll(normalizedCode, " ", "\t") + normalizedCode = strings.TrimSpace(normalizedCode) + return normalizedCode +} diff --git a/pkg/gnoffee/utils_test.go b/pkg/gnoffee/utils_test.go new file mode 100644 index 0000000..f090c49 --- /dev/null +++ b/pkg/gnoffee/utils_test.go @@ -0,0 +1,85 @@ +package gnoffee + +import ( + "testing" +) + +func TestNormalizeGoCode(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + { + name: "Basic normalization", + input: ` + func main() { + println("Hello, World!") + } + `, + expected: `func main() { + println("Hello, World!") +}`, + }, + { + name: "No indentation", + input: `func main() { +println("Hello, World!") +}`, + expected: `func main() { +println("Hello, World!") +}`, + }, + { + name: "Mixed indentation 1", + input: ` + func main() { + println("Hello, World!") + }`, + expected: `func main() { + println("Hello, World!") +}`, + }, + { + name: "Mixed indentation 2", + input: ` + func main() { + println("Hello, World!") + }`, + expected: `func main() { + println("Hello, World!") + }`, + }, + { + name: "Only one line with spaces", + input: " single line with spaces", + expected: "single line with spaces", + }, + { + name: "Empty lines", + input: ` + + func main() { + + println("Hello!") + + } + + `, + expected: `func main() { + + println("Hello!") + +}`, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + normalized := normalizeGoCode(test.input) + if normalized != test.expected { + t.Errorf("Expected:\n%s\nGot:\n%s", test.expected, normalized) + } + }) + } +} From a8c6258cc4bfa1ccb41c793d2c3f92a0359e06b6 Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Sun, 10 Sep 2023 19:17:30 +0200 Subject: [PATCH 2/4] chore: adapt to new repo Signed-off-by: Manfred Touron <94029+moul@users.noreply.github.com> --- Makefile | 5 +++++ cmd/gnoffee/main.go | 2 +- cmd/gnoffee/main_test.go | 4 ++-- go.mod | 19 +++++++++++++++++++ go.sum | 30 ++++++++++++++++++++++++++++++ pkg/gnoffee/doc.go | 4 ---- 6 files changed, 57 insertions(+), 7 deletions(-) create mode 100644 Makefile create mode 100644 go.mod create mode 100644 go.sum diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..bcda6c4 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +install: + go install ./cmd/... + +test: + go test -v ./... diff --git a/cmd/gnoffee/main.go b/cmd/gnoffee/main.go index 29a03a6..2b79bd4 100644 --- a/cmd/gnoffee/main.go +++ b/cmd/gnoffee/main.go @@ -11,7 +11,7 @@ import ( "os" "path/filepath" - "github.com/gnolang/gno/gnovm/pkg/gnoffee" + "github.com/gnolang/gnoffee/pkg/gnoffee" ) var writeFlag bool diff --git a/cmd/gnoffee/main_test.go b/cmd/gnoffee/main_test.go index 5cee00d..a707115 100644 --- a/cmd/gnoffee/main_test.go +++ b/cmd/gnoffee/main_test.go @@ -15,13 +15,13 @@ func TestTest(t *testing.T) { func setupTestScript(t *testing.T, txtarDir string) testscript.Params { t.Helper() - // Get root location of github.com/gnolang/gno + // Get root location of github.com/gnolang/gnoffee goModPath, err := exec.Command("go", "env", "GOMOD").CombinedOutput() require.NoError(t, err) rootDir := filepath.Dir(string(goModPath)) // Build a fresh gno binary in a temp directory gnoffeeBin := filepath.Join(t.TempDir(), "gnoffee") - err = exec.Command("go", "build", "-o", gnoffeeBin, filepath.Join(rootDir, "gnovm", "cmd", "gnoffee")).Run() + err = exec.Command("go", "build", "-o", gnoffeeBin, filepath.Join(rootDir, "cmd", "gnoffee")).Run() require.NoError(t, err) // Define script params return testscript.Params{ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9e0b2bb --- /dev/null +++ b/go.mod @@ -0,0 +1,19 @@ +module github.com/gnolang/gnoffee + +go 1.20 + +require ( + github.com/jaekwon/testify v1.6.1 + github.com/rogpeppe/go-internal v1.11.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.8.4 // indirect + golang.org/x/sys v0.12.0 // indirect + golang.org/x/tools v0.6.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3839c31 --- /dev/null +++ b/go.sum @@ -0,0 +1,30 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/jaekwon/testify v1.6.1 h1:4AtAJcR9GzXN5W4DdY7ie74iCPiJV1JJUJL90t2ZUyw= +github.com/jaekwon/testify v1.6.1/go.mod h1:Oun0RXIHI7osufabQ60i4Lqkj0GXLbqI1I7kgzBNm1U= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/gnoffee/doc.go b/pkg/gnoffee/doc.go index f2c17cb..41f73fb 100644 --- a/pkg/gnoffee/doc.go +++ b/pkg/gnoffee/doc.go @@ -14,10 +14,6 @@ // gnoffee-specific keywords into their comment directive equivalents, // paving the way for the second stage to handle the transpiling logic. // -// The Package Path: -// Gnoffee is currently housed under the gnovm namespace, with the -// package path being: github.com/gnolang/gno/gnovm/pkg/gnoffee. -// // However, it's important to note that while gnoffee resides in the gnovm // namespace, it operates independently from the gnovm. There's potential // for gnoffee to be relocated in the future based on its evolving role From d6640737d7f9fa33ac7702d9c11271c13a53a1cc Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Sun, 10 Sep 2023 19:18:06 +0200 Subject: [PATCH 3/4] chore: fixup Signed-off-by: Manfred Touron <94029+moul@users.noreply.github.com> --- pkg/gnoffee/doc.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pkg/gnoffee/doc.go b/pkg/gnoffee/doc.go index 41f73fb..a5a3998 100644 --- a/pkg/gnoffee/doc.go +++ b/pkg/gnoffee/doc.go @@ -14,11 +14,6 @@ // gnoffee-specific keywords into their comment directive equivalents, // paving the way for the second stage to handle the transpiling logic. // -// However, it's important to note that while gnoffee resides in the gnovm -// namespace, it operates independently from the gnovm. There's potential -// for gnoffee to be relocated in the future based on its evolving role -// and development trajectory. -// // Future Changes: // As the Go and Gno ecosystems and requirements evolve, gnoffee might see the // introduction of new keywords or alterations to its current functionality. From e71dcd2bd51b76977f87197d073a1c4cd84aeaff Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Sun, 10 Sep 2023 19:38:07 +0200 Subject: [PATCH 4/4] feat: add CI stuff Signed-off-by: Manfred Touron <94029+moul@users.noreply.github.com> --- .github/CODEOWNERS | 4 ++ .github/workflows/auto-author-assign.yml | 18 +++++ .github/workflows/gnoffee.yml | 83 ++++++++++++++++++++++++ .github/workflows/lint-pr.yml | 20 ++++++ 4 files changed, 125 insertions(+) create mode 100644 .github/CODEOWNERS create mode 100644 .github/workflows/auto-author-assign.yml create mode 100644 .github/workflows/gnoffee.yml create mode 100644 .github/workflows/lint-pr.yml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..0752e41 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,4 @@ +# CODEOWNERS: https://help.github.com/articles/about-codeowners/ + +# Primary repo maintainers. +* @moul diff --git a/.github/workflows/auto-author-assign.yml b/.github/workflows/auto-author-assign.yml new file mode 100644 index 0000000..8902a12 --- /dev/null +++ b/.github/workflows/auto-author-assign.yml @@ -0,0 +1,18 @@ +name: auto-author-assign + +on: + pull_request_target: + types: [ opened, reopened ] + +permissions: + pull-requests: write + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + assign-author: + runs-on: ubuntu-latest + steps: + - uses: toshimaru/auto-author-assign@v1.6.2 diff --git a/.github/workflows/gnoffee.yml b/.github/workflows/gnoffee.yml new file mode 100644 index 0000000..ae0f0f6 --- /dev/null +++ b/.github/workflows/gnoffee.yml @@ -0,0 +1,83 @@ +name: gnoffee + +on: + pull_request: + paths: + - "go.sum" + - "**.go" + - "**.gnoffee" + - ".github/workflows/gnoffee.yml" + push: + branches: [ "main" ] + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + build: + strategy: + fail-fast: false + matrix: + go-version: + - "1.20.x" + - "1.21.x" + goarch: + - "amd64" + goos: + - "linux" + program: + - "gnoffee" + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.go-version }} + - uses: actions/checkout@v3 + - name: go install + run: GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} go install ./cmd/${{ matrix.program }} + + test: + strategy: + fail-fast: false + matrix: + go-version: + - "1.20.x" + - "1.21.x" + args: + - "test" + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.go-version }} + - uses: actions/checkout@v3 + - name: test + run: | + export GOPATH=$HOME/go + export GOTEST_FLAGS="-v -p 1 -timeout=30m -coverprofile=coverage.out -covermode=atomic" + make ${{ matrix.args }} + #- if: runner.os == 'Linux' + # uses: codecov/codecov-action@v3 + # with: + # token: ${{ secrets.CODECOV_TOKEN }} + # name: gnoffee + # flags: gnoffee,gnoffee-${{matrix.args}},go-${{ matrix.go-version }} + # files: ./coverage.out + # #fail_ci_if_error: ${{ github.repository == 'gnolang/gnoffee' }} + # fail_ci_if_error: false # temporarily + + #docker-integration: + # strategy: + # fail-fast: false + # runs-on: ubuntu-latest + # timeout-minutes: 10 + # steps: + # - uses: actions/checkout@v3 + # # TODO: setup docker caching + # - run: make test.docker + # - run: docker logs int_gnoffee || true + + # TODO: docker-less integration test? diff --git a/.github/workflows/lint-pr.yml b/.github/workflows/lint-pr.yml new file mode 100644 index 0000000..75253d0 --- /dev/null +++ b/.github/workflows/lint-pr.yml @@ -0,0 +1,20 @@ +name: "lint-pr" + +on: + pull_request_target: + types: + - opened + - edited + - synchronize + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + pr-title: + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}