Skip to content
70 changes: 68 additions & 2 deletions gnovm/cmd/gno/lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"flag"
"fmt"
"go/types"
goio "io"
"io/fs"
"path/filepath"
Expand Down Expand Up @@ -256,7 +257,7 @@ func execLint(cmd *lintCmd, args []string, io commands.IO) error {
if cmd.autoGnomod {
tcmode = gno.TCLatestRelaxed
}
errs := lintTypeCheck(io, dir, mpkg, gno.TypeCheckOptions{
tcPkg, errs := lintTypeCheck(io, dir, mpkg, gno.TypeCheckOptions{
Getter: newProdGnoStore(),
TestGetter: newTestGnoStore(true),
Mode: tcmode,
Expand All @@ -268,6 +269,14 @@ func execLint(cmd *lintCmd, args []string, io commands.IO) error {
return
}

// ensure the 'Render' function is correct
err = lintRenderSignature(io, tcPkg)
if err != nil {
// io.ErrPrintln(err) printed above.
hasError = true
return
}

// Construct machine for testing.
tm := test.Machine(newProdGnoStore(), goio.Discard, pkgPath, false)
defer tm.Release()
Expand Down Expand Up @@ -368,10 +377,11 @@ func lintTypeCheck(
mpkg *std.MemPackage,
opts gno.TypeCheckOptions) (
// Results:
tcPkg *types.Package,
lerr error,
) {
// gno.TypeCheckMemPackage(mpkg, testStore).
_, tcErrs := gno.TypeCheckMemPackage(mpkg, opts)
tcPkg, tcErrs := gno.TypeCheckMemPackage(mpkg, opts)

// Print errors, and return the first unexpected error.
errors := multierr.Errors(tcErrs)
Expand All @@ -390,3 +400,59 @@ func lintTargetName(pkg *packages.Package) string {

return tryRelativizePath(pkg.Dir)
}

// lintRenderSignature checks if a Render function in the package has the
// expected signature: func Render(string) string
// Methods are ignored (e.g. func (t *Type) Render()).
// Returns error if the signature is incorrect.
func lintRenderSignature(io commands.IO, pkg *types.Package) error {
if pkg == nil {
return nil
}

o := pkg.Scope().Lookup("Render")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests don't pass due to lint applying on every package (pure package + realm) instead of only realm.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if o == nil {
return nil
}

var (
stringType = "string"
errPrintln = func() error {
err := fmt.Errorf("the 'Render' function signature is incorrect for the '%s' package. the signature must be of the form: func Render(string) string", pkg.Name())
fmt.Fprintln(io.Err(), gnoIssue{
Code: gnoParserError,
Msg: err.Error(),
Confidence: 1,
Location: pkg.Path(),
})
return err
}
isSingleString = func(t *types.Tuple) bool {
return t != nil &&
t.Len() == 1 &&
t.At(0) != nil &&
t.At(0).Type().String() == stringType
}
)

fn, ok := o.(*types.Func)
if !ok {
return nil
}

s, ok := fn.Type().(*types.Signature)
if !ok {
return nil
}

if s.Recv() != nil {
return nil
}

switch {
case !isSingleString(s.Params()), !isSingleString(s.Results()):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get why using a switch case, but since we only have one case I think a if would be more adapted

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return errPrintln()
default:
return nil
}
}
36 changes: 36 additions & 0 deletions gnovm/cmd/gno/lint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,42 @@ func TestLintApp(t *testing.T) {
// stderr / stdout should be empty; the init function and statements
// should not be executed
},
{
args: []string{"lint", "."},
testDir: "../../tests/integ/render_invalid1",
simulateExternalRepo: true,
stderrShouldBe: "gno.land/t/render_invalid1: the 'Render' function signature is incorrect for the 'main' package. the signature must be of the form: func Render(string) string (code=gnoParserError)\n",
errShouldBe: "exit code: 1",
},
{
args: []string{"lint", "."},
testDir: "../../tests/integ/render_invalid2",
simulateExternalRepo: true,
stderrShouldBe: "gno.land/t/render_invalid2: the 'Render' function signature is incorrect for the 'main' package. the signature must be of the form: func Render(string) string (code=gnoParserError)\n",
errShouldBe: "exit code: 1",
},
{
args: []string{"lint", "."},
testDir: "../../tests/integ/render_invalid3",
simulateExternalRepo: true,
stderrShouldBe: "gno.land/t/render_invalid3: the 'Render' function signature is incorrect for the 'main' package. the signature must be of the form: func Render(string) string (code=gnoParserError)\n",
errShouldBe: "exit code: 1",
},
{
args: []string{"lint", "."},
testDir: "../../tests/integ/render_valid1",
simulateExternalRepo: true,
},
{
args: []string{"lint", "."},
testDir: "../../tests/integ/render_valid2",
simulateExternalRepo: true,
},
{
args: []string{"lint", "."},
testDir: "../../tests/integ/render_valid3",
simulateExternalRepo: true,
},

// TODO: 'gno mod' is valid?
// TODO: are dependencies valid?
Expand Down
2 changes: 1 addition & 1 deletion gnovm/cmd/gno/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ func execTest(cmd *testCmd, args []string, io commands.IO) error {
startedAt := time.Now()
didPanic = catchPanic(pkg.Dir, pkgPath, io.Err(), func() {
if mod == nil || !mod.Ignore {
errs := lintTypeCheck(io, pkg.Dir, mpkg, gno.TypeCheckOptions{
_, errs := lintTypeCheck(io, pkg.Dir, mpkg, gno.TypeCheckOptions{
Getter: opts.TestStore,
TestGetter: opts.TestStore,
Mode: gno.TCLatestRelaxed,
Expand Down
1 change: 1 addition & 0 deletions gnovm/tests/integ/render_invalid1/gnomod.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module = "gno.land/t/render_invalid1"
7 changes: 7 additions & 0 deletions gnovm/tests/integ/render_invalid1/main.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

// Invalid 'Render' function: wrong parameter (int instead of string)

func Render(input int) string {

This comment was marked as resolved.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i already have a test that try the number of parameter, i don't think its relevant to add this test.

return "hello"
}
1 change: 1 addition & 0 deletions gnovm/tests/integ/render_invalid2/gnomod.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module = "gno.land/t/render_invalid2"
7 changes: 7 additions & 0 deletions gnovm/tests/integ/render_invalid2/main.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

// Invalid 'Render' function: wrong return type (int instead of string)

func Render(input string) int {
return 9001
}
1 change: 1 addition & 0 deletions gnovm/tests/integ/render_invalid3/gnomod.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module = "gno.land/t/render_invalid3"
7 changes: 7 additions & 0 deletions gnovm/tests/integ/render_invalid3/main.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

// Invalid 'Render' function: too many parameters (2 instead of 1)

func Render(input string, extra int) string {
return input
}
1 change: 1 addition & 0 deletions gnovm/tests/integ/render_valid1/gnomod.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module = "gno.land/t/render_valid1"
5 changes: 5 additions & 0 deletions gnovm/tests/integ/render_valid1/main.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package main

// No 'Render' function is considered as valid

func Random() {}
1 change: 1 addition & 0 deletions gnovm/tests/integ/render_valid2/gnomod.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module = "gno.land/t/render_valid2"
9 changes: 9 additions & 0 deletions gnovm/tests/integ/render_valid2/main.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package main

type Test struct{}

// 'Render' method does not need to follow the signature of the pkg scope 'Render' function

func (t *Test) Render(input int) int {
return input
}
1 change: 1 addition & 0 deletions gnovm/tests/integ/render_valid3/gnomod.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module = "gno.land/t/render_valid3"
7 changes: 7 additions & 0 deletions gnovm/tests/integ/render_valid3/main.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

// Valid 'Render' pkg scope function

func Render(input string) string {
return input
}
Loading