From 5cd468ea02efd8513e5040ce6cf23874d9524f79 Mon Sep 17 00:00:00 2001 From: Pantani Date: Thu, 23 Nov 2023 00:23:35 +0100 Subject: [PATCH 01/35] add parameters placeholders and modifier --- ignite/templates/field/field.go | 8 ++ .../{{moduleName}}/params.proto.plush | 1 - .../x/{{moduleName}}/types/params.go.plush | 35 +++-- ignite/templates/module/create/options.go | 7 + ignite/templates/module/create/params.go | 122 ++++++++++++++++++ ignite/templates/module/placeholders.go | 9 ++ 6 files changed, 169 insertions(+), 13 deletions(-) create mode 100644 ignite/templates/module/create/params.go diff --git a/ignite/templates/field/field.go b/ignite/templates/field/field.go index 62fdee9115..5922f05c95 100644 --- a/ignite/templates/field/field.go +++ b/ignite/templates/field/field.go @@ -155,3 +155,11 @@ func (f Field) ProtoImports() []string { } return dt.ProtoImports } + +// Value returns the field assign value. +func (f Field) Value() string { + if f.DataType() == "string" { + return f.Name.Snake + } + return f.ValueIndex() +} diff --git a/ignite/templates/module/create/files/base/proto/{{appName}}/{{moduleName}}/params.proto.plush b/ignite/templates/module/create/files/base/proto/{{appName}}/{{moduleName}}/params.proto.plush index 1790a2ee02..05eb8fdc68 100644 --- a/ignite/templates/module/create/files/base/proto/{{appName}}/{{moduleName}}/params.proto.plush +++ b/ignite/templates/module/create/files/base/proto/{{appName}}/{{moduleName}}/params.proto.plush @@ -10,7 +10,6 @@ option go_package = "<%= modulePath %>/x/<%= moduleName %>/types"; message Params { option (amino.name) = "<%= appName %>/x/<%= moduleName %>/Params"; option (gogoproto.equal) = true; - <%= for (i, param) in params { %> <%= param.ProtoType(i+1) %> [(gogoproto.moretags) = "yaml:\"<%= param.Name.Snake %>\""];<% } %> } \ No newline at end of file diff --git a/ignite/templates/module/create/files/base/x/{{moduleName}}/types/params.go.plush b/ignite/templates/module/create/files/base/x/{{moduleName}}/types/params.go.plush index dbe9b6c49b..8cd9614a0a 100644 --- a/ignite/templates/module/create/files/base/x/{{moduleName}}/types/params.go.plush +++ b/ignite/templates/module/create/files/base/x/{{moduleName}}/types/params.go.plush @@ -10,53 +10,62 @@ var _ paramtypes.ParamSet = (*Params)(nil) <%= for (param) in params { %> var ( - Key<%= param.Name.UpperCamel %> = []byte("<%= param.Name.UpperCamel %>")<%= if (param.DataType() == "string") { %> - // TODO: Determine the default value - Default<%= param.Name.UpperCamel %> <%= param.DataType() %> = "<%= param.Name.Snake %>"<% } else { %> - // TODO: Determine the default value - Default<%= param.Name.UpperCamel %> <%= param.DataType() %> = <%= param.ValueIndex() %><% } %> + // Key<%= param.Name.UpperCamel %> represents the <%= param.Name.UpperCamel %> parameter. + Key<%= param.Name.UpperCamel %> = []byte("<%= param.Name.UpperCamel %>") + // TODO: Determine the default value. + // Default<%= param.Name.UpperCamel %> represents the <%= param.Name.UpperCamel %> default value. + Default<%= param.Name.UpperCamel %> <%= param.DataType() %> = <%= param.Value() %> ) <% } %> -// ParamKeyTable the param key table for launch module +// this line is used by starport scaffolding # types/params/vars + +// ParamKeyTable the param key table for launch module. func ParamKeyTable() paramtypes.KeyTable { return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) } -// NewParams creates a new Params instance +// NewParams creates a new Params instance. func NewParams(<%= for (param) in params { %> <%= param.Name.LowerCamel %> <%= param.DataType() %>,<% } %> + // this line is used by starport scaffolding # types/params/new/parameter ) Params { return Params{<%= for (param) in params { %> <%= param.Name.UpperCamel %>: <%= param.Name.LowerCamel %>,<% } %> + // this line is used by starport scaffolding # types/params/new/struct } } -// DefaultParams returns a default set of parameters +// DefaultParams returns a default set of parameters. func DefaultParams() Params { return NewParams(<%= for (param) in params { %> Default<%= param.Name.UpperCamel %>,<% } %> + // this line is used by starport scaffolding # types/params/default ) } -// ParamSetPairs get the params.ParamSet +// ParamSetPairs get the params.ParamSet. func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { return paramtypes.ParamSetPairs{<%= for (param) in params { %> paramtypes.NewParamSetPair(Key<%= param.Name.UpperCamel %>, &p.<%= param.Name.UpperCamel %>, validate<%= param.Name.UpperCamel %>),<% } %> + // this line is used by starport scaffolding # types/params/setpairs } } -// Validate validates the set of params +// Validate validates the set of params. func (p Params) Validate() error {<%= for (param) in params { %> if err := validate<%= param.Name.UpperCamel %>(p.<%= param.Name.UpperCamel %>); err != nil { return err } <% } %> + + // this line is used by starport scaffolding # types/params/validate + return nil } <%= for (param) in params { %> -// validate<%= param.Name.UpperCamel %> validates the <%= param.Name.UpperCamel %> param +// validate<%= param.Name.UpperCamel %> validates the <%= param.Name.UpperCamel %> parameter. func validate<%= param.Name.UpperCamel %>(v interface{}) error { <%= param.Name.LowerCamel %>, ok := v.(<%= param.DataType() %>) if !ok { @@ -68,4 +77,6 @@ func validate<%= param.Name.UpperCamel %>(v interface{}) error { return nil } -<% } %> \ No newline at end of file +<% } %> + +// this line is used by starport scaffolding # types/params/validation \ No newline at end of file diff --git a/ignite/templates/module/create/options.go b/ignite/templates/module/create/options.go index fc09404a0b..3baaf8c809 100644 --- a/ignite/templates/module/create/options.go +++ b/ignite/templates/module/create/options.go @@ -9,6 +9,13 @@ import ( ) type ( + // ParamsOptions represents the options to scaffold a Cosmos SDK module parameters. + ParamsOptions struct { + ModuleName string + AppPath string + Params field.Fields + } + // CreateOptions represents the options to scaffold a Cosmos SDK module. CreateOptions struct { ModuleName string diff --git a/ignite/templates/module/create/params.go b/ignite/templates/module/create/params.go new file mode 100644 index 0000000000..659d4bb1d6 --- /dev/null +++ b/ignite/templates/module/create/params.go @@ -0,0 +1,122 @@ +package modulecreate + +import ( + "fmt" + "path/filepath" + + "github.com/gobuffalo/genny/v2" + + "github.com/ignite/cli/ignite/pkg/placeholder" + "github.com/ignite/cli/ignite/templates/module" +) + +func paramsTypesModify(replacer placeholder.Replacer, opts ParamsOptions) genny.RunFn { + return func(r *genny.Runner) error { + path := filepath.Join(opts.AppPath, "x", opts.ModuleName, "types/params.go") + f, err := r.Disk.Find(path) + if err != nil { + return err + } + + content := f.String() + for _, param := range opts.Params { + // param key and default value. + templateVars := `var ( + // Key%[2]v represents the %[2]v parameter. + Key%[2]v = []byte("%[2]v") + // TODO: Determine the default value + // Default%[2]v represents the %[2]v default value. + Default%[2]v %[3]v = %[4]v +) + +%[1]v` + replacementVars := fmt.Sprintf( + templateVars, + module.PlaceholderParamsVars, + param.Name.UpperCamel, + param.DataType(), + param.Value(), + ) + content = replacer.Replace(content, module.PlaceholderParamsVars, replacementVars) + + // add parameter to the new method. + templateNewParam := "%[2]v %[3]v,\n%[1]v" + replacementNewParam := fmt.Sprintf( + templateNewParam, + module.PlaceholderParamsNewParam, + param.Name.LowerCamel, + param.DataType(), + ) + content = replacer.Replace(content, module.PlaceholderParamsNewParam, replacementNewParam) + + // add parameter to the struct into the new method. + templateNewStruct := "%[2]v,\n%[1]v" + replacementNewStruct := fmt.Sprintf( + templateNewStruct, + module.PlaceholderParamsNewStruct, + param.Name.LowerCamel, + ) + content = replacer.Replace(content, module.PlaceholderParamsNewStruct, replacementNewStruct) + + // add default parameter. + templateDefault := `Default%[2]v, +%[1]v` + replacementDefault := fmt.Sprintf( + templateDefault, + module.PlaceholderParamsDefault, + param.Name.UpperCamel, + ) + content = replacer.Replace(content, module.PlaceholderParamsDefault, replacementDefault) + + // add new param set pair. + templateSetPairs := `paramtypes.NewParamSetPair(Key%[2]v, &p.%[2]v, validate%[2]v), +%[1]v` + replacementSetPairs := fmt.Sprintf( + templateSetPairs, + module.PlaceholderParamsSetPairs, + param.Name.UpperCamel, + ) + content = replacer.Replace(content, module.PlaceholderParamsSetPairs, replacementSetPairs) + + // add param field to the validate method. + templateValidate := `if err := validate%[2]v(p.%[2]v); err != nil { + return err + } + %[1]v` + replacementValidate := fmt.Sprintf( + templateValidate, + module.PlaceholderParamsValidate, + param.Name.UpperCamel, + ) + content = replacer.Replace(content, module.PlaceholderParamsValidate, replacementValidate) + + // add param field to the validate method. + templateValidation := `// validate%[2]v validates the %[2]v parameter. +func validate%[2]v(v interface{}) error { + %[3]v, ok := v.(%[4]v) + if !ok { + return fmt.Errorf("invalid parameter type: %T", v) + } + + // TODO implement validation + _ = %[3]v + + return nil +} + +%[1]v` + replacementValidation := fmt.Sprintf( + templateValidation, + module.PlaceholderParamsValidation, + param.Name.UpperCamel, + param.Name.LowerCamel, + param.DataType(), + ) + content = replacer.Replace(content, module.PlaceholderParamsValidation, replacementValidation) + + } + + newFile := genny.NewFileS(path, content) + return r.File(newFile) + } +} diff --git a/ignite/templates/module/placeholders.go b/ignite/templates/module/placeholders.go index d8cf25041a..7ece674d37 100644 --- a/ignite/templates/module/placeholders.go +++ b/ignite/templates/module/placeholders.go @@ -27,4 +27,13 @@ const ( PlaceholderTypesGenesisValidField = "// this line is used by starport scaffolding # types/genesis/validField" PlaceholderGenesisTestState = "// this line is used by starport scaffolding # genesis/test/state" PlaceholderGenesisTestAssert = "// this line is used by starport scaffolding # genesis/test/assert" + + // Params + PlaceholderParamsVars = "// this line is used by starport scaffolding # types/params/vars" + PlaceholderParamsNewParam = "// this line is used by starport scaffolding # types/params/new/parameter" + PlaceholderParamsNewStruct = "// this line is used by starport scaffolding # types/params/new/struct" + PlaceholderParamsDefault = "// this line is used by starport scaffolding # types/params/default" + PlaceholderParamsSetPairs = "// this line is used by starport scaffolding # types/params/setpairs" + PlaceholderParamsValidate = "// this line is used by starport scaffolding # types/params/validate" + PlaceholderParamsValidation = "// this line is used by starport scaffolding # types/params/validation" ) From 6752e7b703786683f8e6b3dddab8459a7b06af05 Mon Sep 17 00:00:00 2001 From: Pantani Date: Thu, 23 Nov 2023 16:44:57 +0100 Subject: [PATCH 02/35] add scaffold params command --- ignite/cmd/scaffold.go | 1 + ignite/cmd/scaffold_params.go | 81 +++++++++++++++++++ ignite/services/scaffolder/params.go | 69 ++++++++++++++++ ignite/templates/field/field.go | 2 +- .../x/{{moduleName}}/types/params.go.plush | 2 +- ignite/templates/module/create/options.go | 1 + ignite/templates/module/create/params.go | 53 +++++++++++- 7 files changed, 204 insertions(+), 5 deletions(-) create mode 100644 ignite/cmd/scaffold_params.go create mode 100644 ignite/services/scaffolder/params.go diff --git a/ignite/cmd/scaffold.go b/ignite/cmd/scaffold.go index c182f72a79..bfa8ae91e8 100644 --- a/ignite/cmd/scaffold.go +++ b/ignite/cmd/scaffold.go @@ -123,6 +123,7 @@ with an "--ibc" flag. Note that the default module is not IBC-enabled. NewScaffoldMap(), NewScaffoldSingle(), NewScaffoldType(), + NewScaffoldParams(), NewScaffoldMessage(), NewScaffoldQuery(), NewScaffoldPacket(), diff --git a/ignite/cmd/scaffold_params.go b/ignite/cmd/scaffold_params.go new file mode 100644 index 0000000000..f30a7bf9bc --- /dev/null +++ b/ignite/cmd/scaffold_params.go @@ -0,0 +1,81 @@ +package ignitecmd + +import ( + "strings" + + "github.com/spf13/cobra" + + "github.com/ignite/cli/ignite/pkg/cliui" + "github.com/ignite/cli/ignite/pkg/placeholder" + "github.com/ignite/cli/ignite/services/scaffolder" +) + +// NewScaffoldParams returns the command to scaffold a Cosmos SDK parameters into a module. +func NewScaffoldParams() *cobra.Command { + c := &cobra.Command{ + Use: "params [param]...", + Short: "Parameters for a custom Cosmos SDK module", + Long: `Scaffold a new parameter for a Cosmos SDK module. + +A Cosmos SDK module can have parameters (or "params"). Params are values that +can be set at the genesis of the blockchain and can be modified while the +blockchain is running. An example of a param is "Inflation rate change" of the +"mint" module. A params can be scaffolded into a module using the "--params" into +the scaffold module command or using the scaffold params command. By default +params are of type "string", but you can specify a type for each param. For example: + + ignite scaffold params foo baz:uint bar:bool + +Refer to Cosmos SDK documentation to learn more about modules, dependencies and +params. +`, + Args: cobra.MinimumNArgs(1), + PreRunE: migrationPreRunHandler, + RunE: scaffoldParamsHandler, + } + + flagSetPath(c) + flagSetClearCache(c) + + c.Flags().AddFlagSet(flagSetYes()) + + c.Flags().String(flagModule, "", "module to add the query into. Default: app's main module") + + return c +} + +func scaffoldParamsHandler(cmd *cobra.Command, args []string) error { + var ( + params = args[0:] + appPath = flagGetPath(cmd) + moduleName = flagGetModule(cmd) + ) + + session := cliui.New(cliui.StartSpinnerWithText(statusScaffolding)) + defer session.End() + + cacheStorage, err := newCache(cmd) + if err != nil { + return err + } + + sc, err := scaffolder.New(appPath) + if err != nil { + return err + } + + sm, err := sc.CreateParams(cmd.Context(), cacheStorage, placeholder.New(), moduleName, params...) + if err != nil { + return err + } + + modificationsStr, err := sourceModificationToString(sm) + if err != nil { + return err + } + + session.Println(modificationsStr) + session.Printf("\nšŸŽ‰ New parameters added to the module `%s`:\n`%s`.\n\n", moduleName, strings.Join(params, "\n")) + + return nil +} diff --git a/ignite/services/scaffolder/params.go b/ignite/services/scaffolder/params.go new file mode 100644 index 0000000000..3c10159afd --- /dev/null +++ b/ignite/services/scaffolder/params.go @@ -0,0 +1,69 @@ +package scaffolder + +import ( + "context" + "fmt" + + "github.com/gobuffalo/genny/v2" + + "github.com/ignite/cli/ignite/pkg/cache" + "github.com/ignite/cli/ignite/pkg/multiformatname" + "github.com/ignite/cli/ignite/pkg/placeholder" + "github.com/ignite/cli/ignite/pkg/xgenny" + "github.com/ignite/cli/ignite/templates/field" + modulecreate "github.com/ignite/cli/ignite/templates/module/create" +) + +// CreateParams creates a new params in the scaffolded module. +func (s Scaffolder) CreateParams( + ctx context.Context, + cacheStorage cache.Storage, + tracer *placeholder.Tracer, + moduleName string, + params ...string, +) (sm xgenny.SourceModification, err error) { + // If no module is provided, we add the type to the app's module + if moduleName == "" { + moduleName = s.modpath.Package + } + mfName, err := multiformatname.NewName(moduleName, multiformatname.NoNumber) + if err != nil { + return sm, err + } + moduleName = mfName.LowerCase + + // Check if the module already exist + ok, err := moduleExists(s.path, moduleName) + if err != nil { + return sm, err + } + if !ok { + return sm, fmt.Errorf("the module %v not exist", moduleName) + } + + // Parse params with the associated type + paramsFields, err := field.ParseFields(params, checkForbiddenTypeIndex) + if err != nil { + return sm, err + } + + opts := modulecreate.ParamsOptions{ + ModuleName: moduleName, + Params: paramsFields, + AppName: s.modpath.Package, + AppPath: s.path, + } + + g, err := modulecreate.NewModuleParam(tracer, opts) + if err != nil { + return sm, err + } + gens := []*genny.Generator{g} + + sm, err = xgenny.RunWithValidation(tracer, gens...) + if err != nil { + return sm, err + } + + return sm, finish(ctx, cacheStorage, opts.AppPath, s.modpath.RawPath) +} diff --git a/ignite/templates/field/field.go b/ignite/templates/field/field.go index 5922f05c95..97bfa57ce9 100644 --- a/ignite/templates/field/field.go +++ b/ignite/templates/field/field.go @@ -159,7 +159,7 @@ func (f Field) ProtoImports() []string { // Value returns the field assign value. func (f Field) Value() string { if f.DataType() == "string" { - return f.Name.Snake + return fmt.Sprintf("\"%s\"", f.Name.Snake) } return f.ValueIndex() } diff --git a/ignite/templates/module/create/files/base/x/{{moduleName}}/types/params.go.plush b/ignite/templates/module/create/files/base/x/{{moduleName}}/types/params.go.plush index 8cd9614a0a..c5c81e9eea 100644 --- a/ignite/templates/module/create/files/base/x/{{moduleName}}/types/params.go.plush +++ b/ignite/templates/module/create/files/base/x/{{moduleName}}/types/params.go.plush @@ -12,8 +12,8 @@ var _ paramtypes.ParamSet = (*Params)(nil) var ( // Key<%= param.Name.UpperCamel %> represents the <%= param.Name.UpperCamel %> parameter. Key<%= param.Name.UpperCamel %> = []byte("<%= param.Name.UpperCamel %>") - // TODO: Determine the default value. // Default<%= param.Name.UpperCamel %> represents the <%= param.Name.UpperCamel %> default value. + // TODO: Determine the default value. Default<%= param.Name.UpperCamel %> <%= param.DataType() %> = <%= param.Value() %> ) <% } %> diff --git a/ignite/templates/module/create/options.go b/ignite/templates/module/create/options.go index 3baaf8c809..09deebf2ce 100644 --- a/ignite/templates/module/create/options.go +++ b/ignite/templates/module/create/options.go @@ -12,6 +12,7 @@ type ( // ParamsOptions represents the options to scaffold a Cosmos SDK module parameters. ParamsOptions struct { ModuleName string + AppName string AppPath string Params field.Fields } diff --git a/ignite/templates/module/create/params.go b/ignite/templates/module/create/params.go index 659d4bb1d6..7ae16ae7e8 100644 --- a/ignite/templates/module/create/params.go +++ b/ignite/templates/module/create/params.go @@ -7,9 +7,55 @@ import ( "github.com/gobuffalo/genny/v2" "github.com/ignite/cli/ignite/pkg/placeholder" + "github.com/ignite/cli/ignite/pkg/protoanalysis/protoutil" "github.com/ignite/cli/ignite/templates/module" ) +// NewModuleParam returns the generator to scaffold a new parameter inside a module. +func NewModuleParam(replacer placeholder.Replacer, opts ParamsOptions) (*genny.Generator, error) { + g := genny.New() + g.RunFn(paramsProtoModify(opts)) + g.RunFn(paramsTypesModify(replacer, opts)) + return g, nil +} + +func paramsProtoModify(opts ParamsOptions) genny.RunFn { + return func(r *genny.Runner) error { + path := filepath.Join(opts.AppPath, "proto", opts.AppName, opts.ModuleName, "params.proto") + f, err := r.Disk.Find(path) + if err != nil { + return err + } + protoFile, err := protoutil.ParseProtoFile(f) + if err != nil { + return err + } + + params, err := protoutil.GetMessageByName(protoFile, "Params") + if err != nil { + return fmt.Errorf("couldn't find message 'GenesisState' in %s: %w", path, err) + } + for _, paramField := range opts.Params { + yamlOption := protoutil.NewOption( + "gogoproto.moretags", + fmt.Sprintf("yaml:\\\"%s\\\"", + paramField.Name.LowerCamel), + protoutil.Custom(), + ) + param := protoutil.NewField( + paramField.Name.LowerCamel, + paramField.DataType(), + protoutil.NextUniqueID(params), + protoutil.WithFieldOptions(yamlOption), + ) + protoutil.Append(params, param) + } + + newFile := genny.NewFileS(path, protoutil.Print(protoFile)) + return r.File(newFile) + } +} + func paramsTypesModify(replacer placeholder.Replacer, opts ParamsOptions) genny.RunFn { return func(r *genny.Runner) error { path := filepath.Join(opts.AppPath, "x", opts.ModuleName, "types/params.go") @@ -24,8 +70,8 @@ func paramsTypesModify(replacer placeholder.Replacer, opts ParamsOptions) genny. templateVars := `var ( // Key%[2]v represents the %[2]v parameter. Key%[2]v = []byte("%[2]v") - // TODO: Determine the default value // Default%[2]v represents the %[2]v default value. + // TODO: Determine the default value Default%[2]v %[3]v = %[4]v ) @@ -50,10 +96,11 @@ func paramsTypesModify(replacer placeholder.Replacer, opts ParamsOptions) genny. content = replacer.Replace(content, module.PlaceholderParamsNewParam, replacementNewParam) // add parameter to the struct into the new method. - templateNewStruct := "%[2]v,\n%[1]v" + templateNewStruct := "%[2]v: %[3]v,\n%[1]v" replacementNewStruct := fmt.Sprintf( templateNewStruct, module.PlaceholderParamsNewStruct, + param.Name.UpperCamel, param.Name.LowerCamel, ) content = replacer.Replace(content, module.PlaceholderParamsNewStruct, replacementNewStruct) @@ -95,7 +142,7 @@ func paramsTypesModify(replacer placeholder.Replacer, opts ParamsOptions) genny. func validate%[2]v(v interface{}) error { %[3]v, ok := v.(%[4]v) if !ok { - return fmt.Errorf("invalid parameter type: %T", v) + return fmt.Errorf("invalid parameter type: %%T", v) } // TODO implement validation From b862349902e2b5c4fa0dd5694d47a374d9d74814 Mon Sep 17 00:00:00 2001 From: Pantani Date: Thu, 23 Nov 2023 16:46:41 +0100 Subject: [PATCH 03/35] add changelog --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index d7f1dfbe1c..b9dff93f92 100644 --- a/changelog.md +++ b/changelog.md @@ -15,6 +15,7 @@ - [#3724](https://github.com/ignite/cli/pull/3724) Add or vendor proto packages from Go dependencies - [#3715](https://github.com/ignite/cli/pull/3715) Add test suite for the cli tests - [#3756](https://github.com/ignite/cli/pull/3756) Add faucet compatibility for latest sdk chains +- [#3684](https://github.com/ignite/cli/issues/3684) Add scaffold params command ### Changes From aef509a0dfa20906bae4ff1e73cb4d0b06bfe466 Mon Sep 17 00:00:00 2001 From: Pantani Date: Fri, 24 Nov 2023 01:54:50 +0100 Subject: [PATCH 04/35] check if the parameter already exist --- ignite/services/scaffolder/params.go | 74 ++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/ignite/services/scaffolder/params.go b/ignite/services/scaffolder/params.go index 3c10159afd..e7e0da0953 100644 --- a/ignite/services/scaffolder/params.go +++ b/ignite/services/scaffolder/params.go @@ -3,6 +3,12 @@ package scaffolder import ( "context" "fmt" + "go/ast" + "go/parser" + "go/token" + "os" + "path/filepath" + "strings" "github.com/gobuffalo/genny/v2" @@ -41,6 +47,10 @@ func (s Scaffolder) CreateParams( return sm, fmt.Errorf("the module %v not exist", moduleName) } + if err := checkParamCreated(s.path, moduleName, params...); err != nil { + return sm, err + } + // Parse params with the associated type paramsFields, err := field.ParseFields(params, checkForbiddenTypeIndex) if err != nil { @@ -67,3 +77,67 @@ func (s Scaffolder) CreateParams( return sm, finish(ctx, cacheStorage, opts.AppPath, s.modpath.RawPath) } + +// checkParamCreated checks if the parameter has been already created. +func checkParamCreated(appPath, moduleName string, params ...string) (err error) { + absPath, err := filepath.Abs(filepath.Join(appPath, "x", moduleName, "types")) + if err != nil { + return err + } + fileSet := token.NewFileSet() + all, err := parser.ParseDir(fileSet, absPath, func(os.FileInfo) bool { return true }, parser.ParseComments) + if err != nil { + return err + } + + paramsName := make(map[string]struct{}) + for _, param := range params { + paramSplit := strings.Split(param, ":") + if len(paramSplit) == 0 || len(paramSplit) > 2 { + return fmt.Errorf("invalid param format: %s", param) + } + paramsName[strings.ToLower(paramSplit[0])] = struct{}{} + } + + for _, pkg := range all { + for _, f := range pkg.Files { + ast.Inspect(f, func(x ast.Node) bool { + typeSpec, ok := x.(*ast.TypeSpec) + if !ok { + return true + } + + if _, ok := typeSpec.Type.(*ast.StructType); !ok || + typeSpec.Name.Name != "Params" || + typeSpec.Type == nil { + return true + } + + // Check if the struct has fields. + structType, ok := typeSpec.Type.(*ast.StructType) + if !ok { + return true + } + + // Iterate through the fields of the struct. + for _, paramField := range structType.Fields.List { + for _, fieldName := range paramField.Names { + if _, ok := paramsName[strings.ToLower(fieldName.Name)]; ok { + err = fmt.Errorf( + "param field '%s' already exist for module %s", + fieldName.Name, + moduleName, + ) + return false + } + } + } + return true + }) + if err != nil { + return err + } + } + } + return err +} From 58ece42d3cdb6581b0880d8e3500f0878068e976 Mon Sep 17 00:00:00 2001 From: Pantani Date: Fri, 24 Nov 2023 02:20:59 +0100 Subject: [PATCH 05/35] add integration tests and fix some import issues --- .../x/{{moduleName}}/types/params.go.plush | 9 ++- ignite/templates/module/create/params.go | 2 +- integration/params/cmd_params_test.go | 79 +++++++++++++++++++ 3 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 integration/params/cmd_params_test.go diff --git a/ignite/templates/module/create/files/base/x/{{moduleName}}/types/params.go.plush b/ignite/templates/module/create/files/base/x/{{moduleName}}/types/params.go.plush index c5c81e9eea..6bccf0c06b 100644 --- a/ignite/templates/module/create/files/base/x/{{moduleName}}/types/params.go.plush +++ b/ignite/templates/module/create/files/base/x/{{moduleName}}/types/params.go.plush @@ -1,12 +1,15 @@ package types import ( - <%= if (len(params) > 0) { %>"fmt"<% } %> + "fmt" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" ) -var _ paramtypes.ParamSet = (*Params)(nil) +var ( + _ = fmt.Errorf + _ paramtypes.ParamSet = (*Params)(nil) +) <%= for (param) in params { %> var ( @@ -14,7 +17,7 @@ var ( Key<%= param.Name.UpperCamel %> = []byte("<%= param.Name.UpperCamel %>") // Default<%= param.Name.UpperCamel %> represents the <%= param.Name.UpperCamel %> default value. // TODO: Determine the default value. - Default<%= param.Name.UpperCamel %> <%= param.DataType() %> = <%= param.Value() %> + Default<%= param.Name.UpperCamel %> <%= param.DataType() %> = <%= if (param.DataType() == "string") { %>"<%= param.Name.Snake %>"<% } else { %><%= param.ValueIndex() %><% } %> ) <% } %> diff --git a/ignite/templates/module/create/params.go b/ignite/templates/module/create/params.go index 7ae16ae7e8..4c1453692a 100644 --- a/ignite/templates/module/create/params.go +++ b/ignite/templates/module/create/params.go @@ -71,7 +71,7 @@ func paramsTypesModify(replacer placeholder.Replacer, opts ParamsOptions) genny. // Key%[2]v represents the %[2]v parameter. Key%[2]v = []byte("%[2]v") // Default%[2]v represents the %[2]v default value. - // TODO: Determine the default value + // TODO: Determine the default value. Default%[2]v %[3]v = %[4]v ) diff --git a/integration/params/cmd_params_test.go b/integration/params/cmd_params_test.go new file mode 100644 index 0000000000..45d087172a --- /dev/null +++ b/integration/params/cmd_params_test.go @@ -0,0 +1,79 @@ +//go:build !relayer + +package params_test + +import ( + "testing" + + "github.com/ignite/cli/ignite/pkg/cmdrunner/step" + envtest "github.com/ignite/cli/integration" +) + +func TestCreateModuleParameters(t *testing.T) { + var ( + env = envtest.New(t) + app = env.Scaffold("github.com/test/mars") + ) + + env.Must(env.Exec("create an module with parameter", + step.NewSteps(step.New( + step.Exec(envtest.IgniteApp, + "s", + "module", + "--yes", + "foo", + "--params", + "bla,baz:uint,bar:bool", + "--require-registration"), + step.Workdir(app.SourcePath()), + )), + )) + + env.Must(env.Exec("should prevent creating parameter field that already exist", + step.NewSteps(step.New( + step.Exec(envtest.IgniteApp, + "s", + "params", + "--yes", + "bla", + "buu:uint", + "--module", + "foo", + ), + step.Workdir(app.SourcePath()), + )), + envtest.ExecShouldError(), + )) + + env.Must(env.Exec("create an new module parameters in the foo module", + step.NewSteps(step.New( + step.Exec(envtest.IgniteApp, + "s", + "params", + "--yes", + "bol", + "buu:uint", + "plk:bool", + "--module", + "foo", + ), + step.Workdir(app.SourcePath()), + )), + )) + + env.Must(env.Exec("create an new module parameters in the mars module", + step.NewSteps(step.New( + step.Exec(envtest.IgniteApp, + "s", + "params", + "--yes", + "foo", + "bar:uint", + "baz:bool", + ), + step.Workdir(app.SourcePath()), + )), + )) + + app.EnsureSteady() +} From 452187c91fdd283f4803d08719099325b7d8e2a2 Mon Sep 17 00:00:00 2001 From: Pantani Date: Fri, 24 Nov 2023 02:42:43 +0100 Subject: [PATCH 06/35] improve the scaffolder logic --- ignite/services/scaffolder/params.go | 31 +++++++++++++--------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/ignite/services/scaffolder/params.go b/ignite/services/scaffolder/params.go index e7e0da0953..705b753873 100644 --- a/ignite/services/scaffolder/params.go +++ b/ignite/services/scaffolder/params.go @@ -47,16 +47,16 @@ func (s Scaffolder) CreateParams( return sm, fmt.Errorf("the module %v not exist", moduleName) } - if err := checkParamCreated(s.path, moduleName, params...); err != nil { - return sm, err - } - // Parse params with the associated type paramsFields, err := field.ParseFields(params, checkForbiddenTypeIndex) if err != nil { return sm, err } + if err := checkParamCreated(s.path, moduleName, paramsFields); err != nil { + return sm, err + } + opts := modulecreate.ParamsOptions{ ModuleName: moduleName, Params: paramsFields, @@ -79,7 +79,7 @@ func (s Scaffolder) CreateParams( } // checkParamCreated checks if the parameter has been already created. -func checkParamCreated(appPath, moduleName string, params ...string) (err error) { +func checkParamCreated(appPath, moduleName string, params field.Fields) (err error) { absPath, err := filepath.Abs(filepath.Join(appPath, "x", moduleName, "types")) if err != nil { return err @@ -92,11 +92,7 @@ func checkParamCreated(appPath, moduleName string, params ...string) (err error) paramsName := make(map[string]struct{}) for _, param := range params { - paramSplit := strings.Split(param, ":") - if len(paramSplit) == 0 || len(paramSplit) > 2 { - return fmt.Errorf("invalid param format: %s", param) - } - paramsName[strings.ToLower(paramSplit[0])] = struct{}{} + paramsName[param.Name.LowerCase] = struct{}{} } for _, pkg := range all { @@ -122,14 +118,15 @@ func checkParamCreated(appPath, moduleName string, params ...string) (err error) // Iterate through the fields of the struct. for _, paramField := range structType.Fields.List { for _, fieldName := range paramField.Names { - if _, ok := paramsName[strings.ToLower(fieldName.Name)]; ok { - err = fmt.Errorf( - "param field '%s' already exist for module %s", - fieldName.Name, - moduleName, - ) - return false + if _, ok := paramsName[strings.ToLower(fieldName.Name)]; !ok { + continue } + err = fmt.Errorf( + "param field '%s' already exist for module %s", + fieldName.Name, + moduleName, + ) + return false } } return true From cb73d70a3606b597c2e6989440f34cdb9f82f1cb Mon Sep 17 00:00:00 2001 From: Pantani Date: Fri, 24 Nov 2023 02:50:18 +0100 Subject: [PATCH 07/35] improve the log message --- ignite/cmd/scaffold_params.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ignite/cmd/scaffold_params.go b/ignite/cmd/scaffold_params.go index f30a7bf9bc..ac59d2998c 100644 --- a/ignite/cmd/scaffold_params.go +++ b/ignite/cmd/scaffold_params.go @@ -75,7 +75,7 @@ func scaffoldParamsHandler(cmd *cobra.Command, args []string) error { } session.Println(modificationsStr) - session.Printf("\nšŸŽ‰ New parameters added to the module `%s`:\n`%s`.\n\n", moduleName, strings.Join(params, "\n")) + session.Printf("\nšŸŽ‰ New parameters added to the module:\n- %s.\n\n", moduleName, strings.Join(params, "\n- ")) return nil } From 600e0e97769f805d392e544cfdca0bac3d223cd0 Mon Sep 17 00:00:00 2001 From: Pantani Date: Fri, 24 Nov 2023 02:52:51 +0100 Subject: [PATCH 08/35] fix the log message --- ignite/cmd/scaffold_params.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ignite/cmd/scaffold_params.go b/ignite/cmd/scaffold_params.go index ac59d2998c..87745ea388 100644 --- a/ignite/cmd/scaffold_params.go +++ b/ignite/cmd/scaffold_params.go @@ -75,7 +75,7 @@ func scaffoldParamsHandler(cmd *cobra.Command, args []string) error { } session.Println(modificationsStr) - session.Printf("\nšŸŽ‰ New parameters added to the module:\n- %s.\n\n", moduleName, strings.Join(params, "\n- ")) + session.Printf("\nšŸŽ‰ New parameters added to the module:\n\n- %s\n\n", strings.Join(params, "\n- ")) return nil } From e9a64ffa132269f7a91d963ec6c735475ce3c6ab Mon Sep 17 00:00:00 2001 From: Pantani Date: Tue, 5 Dec 2023 15:30:50 +0100 Subject: [PATCH 09/35] fix changelog --- changelog.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index b7dc25d66e..79f078ebee 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,9 @@ ## Unreleased +### Features +- [#3684](https://github.com/ignite/cli/issues/3684) Add scaffold params command + ## [`v28.0.0`](https://github.com/ignite/cli/releases/tag/v28.0.0) ### Features @@ -17,8 +20,6 @@ - [#3626](https://github.com/ignite/cli/pull/3626) Add logging levels to relayer - [#3614](https://github.com/ignite/cli/pull/3614) feat: use DefaultBaseappOptions for app.New method - [#3715](https://github.com/ignite/cli/pull/3715) Add test suite for the cli tests -- [#3756](https://github.com/ignite/cli/pull/3756) Add faucet compatibility for latest sdk chains -- [#3684](https://github.com/ignite/cli/issues/3684) Add scaffold params command ### Changes From d1a79eccebc2a1b9dc01aa3c3927107d939202d3 Mon Sep 17 00:00:00 2001 From: Pantani Date: Tue, 5 Dec 2023 16:12:08 +0100 Subject: [PATCH 10/35] fix 28 imports --- ignite/cmd/scaffold_params.go | 6 +++--- ignite/services/scaffolder/params.go | 12 ++++++------ ignite/templates/module/create/params.go | 13 +++---------- integration/params/cmd_params_test.go | 4 ++-- 4 files changed, 14 insertions(+), 21 deletions(-) diff --git a/ignite/cmd/scaffold_params.go b/ignite/cmd/scaffold_params.go index 87745ea388..ec586832f7 100644 --- a/ignite/cmd/scaffold_params.go +++ b/ignite/cmd/scaffold_params.go @@ -5,9 +5,9 @@ import ( "github.com/spf13/cobra" - "github.com/ignite/cli/ignite/pkg/cliui" - "github.com/ignite/cli/ignite/pkg/placeholder" - "github.com/ignite/cli/ignite/services/scaffolder" + "github.com/ignite/cli/v28/ignite/pkg/cliui" + "github.com/ignite/cli/v28/ignite/pkg/placeholder" + "github.com/ignite/cli/v28/ignite/services/scaffolder" ) // NewScaffoldParams returns the command to scaffold a Cosmos SDK parameters into a module. diff --git a/ignite/services/scaffolder/params.go b/ignite/services/scaffolder/params.go index 705b753873..6fdb907dcc 100644 --- a/ignite/services/scaffolder/params.go +++ b/ignite/services/scaffolder/params.go @@ -12,12 +12,12 @@ import ( "github.com/gobuffalo/genny/v2" - "github.com/ignite/cli/ignite/pkg/cache" - "github.com/ignite/cli/ignite/pkg/multiformatname" - "github.com/ignite/cli/ignite/pkg/placeholder" - "github.com/ignite/cli/ignite/pkg/xgenny" - "github.com/ignite/cli/ignite/templates/field" - modulecreate "github.com/ignite/cli/ignite/templates/module/create" + "github.com/ignite/cli/v28/ignite/pkg/cache" + "github.com/ignite/cli/v28/ignite/pkg/multiformatname" + "github.com/ignite/cli/v28/ignite/pkg/placeholder" + "github.com/ignite/cli/v28/ignite/pkg/xgenny" + "github.com/ignite/cli/v28/ignite/templates/field" + modulecreate "github.com/ignite/cli/v28/ignite/templates/module/create" ) // CreateParams creates a new params in the scaffolded module. diff --git a/ignite/templates/module/create/params.go b/ignite/templates/module/create/params.go index 4c1453692a..ae8c586afd 100644 --- a/ignite/templates/module/create/params.go +++ b/ignite/templates/module/create/params.go @@ -6,9 +6,9 @@ import ( "github.com/gobuffalo/genny/v2" - "github.com/ignite/cli/ignite/pkg/placeholder" - "github.com/ignite/cli/ignite/pkg/protoanalysis/protoutil" - "github.com/ignite/cli/ignite/templates/module" + "github.com/ignite/cli/v28/ignite/pkg/placeholder" + "github.com/ignite/cli/v28/ignite/pkg/protoanalysis/protoutil" + "github.com/ignite/cli/v28/ignite/templates/module" ) // NewModuleParam returns the generator to scaffold a new parameter inside a module. @@ -36,17 +36,10 @@ func paramsProtoModify(opts ParamsOptions) genny.RunFn { return fmt.Errorf("couldn't find message 'GenesisState' in %s: %w", path, err) } for _, paramField := range opts.Params { - yamlOption := protoutil.NewOption( - "gogoproto.moretags", - fmt.Sprintf("yaml:\\\"%s\\\"", - paramField.Name.LowerCamel), - protoutil.Custom(), - ) param := protoutil.NewField( paramField.Name.LowerCamel, paramField.DataType(), protoutil.NextUniqueID(params), - protoutil.WithFieldOptions(yamlOption), ) protoutil.Append(params, param) } diff --git a/integration/params/cmd_params_test.go b/integration/params/cmd_params_test.go index 45d087172a..92f1965fdb 100644 --- a/integration/params/cmd_params_test.go +++ b/integration/params/cmd_params_test.go @@ -5,8 +5,8 @@ package params_test import ( "testing" - "github.com/ignite/cli/ignite/pkg/cmdrunner/step" - envtest "github.com/ignite/cli/integration" + "github.com/ignite/cli/v28/ignite/pkg/cmdrunner/step" + envtest "github.com/ignite/cli/v28/integration" ) func TestCreateModuleParameters(t *testing.T) { From 758457b6f3171aae66b6469a5917d86f4e20905f Mon Sep 17 00:00:00 2001 From: Pantani Date: Tue, 5 Dec 2023 21:58:26 +0100 Subject: [PATCH 11/35] remove params module --- .../x/{{moduleName}}/types/params.go.plush | 20 +------------------ ignite/templates/module/create/params.go | 10 ---------- ignite/templates/module/placeholders.go | 1 - 3 files changed, 1 insertion(+), 30 deletions(-) diff --git a/ignite/templates/module/create/files/base/x/{{moduleName}}/types/params.go.plush b/ignite/templates/module/create/files/base/x/{{moduleName}}/types/params.go.plush index 6bccf0c06b..b100bd5a3d 100644 --- a/ignite/templates/module/create/files/base/x/{{moduleName}}/types/params.go.plush +++ b/ignite/templates/module/create/files/base/x/{{moduleName}}/types/params.go.plush @@ -2,14 +2,9 @@ package types import ( "fmt" - - paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" ) -var ( - _ = fmt.Errorf - _ paramtypes.ParamSet = (*Params)(nil) -) +var _ = fmt.Errorf <%= for (param) in params { %> var ( @@ -23,11 +18,6 @@ var ( // this line is used by starport scaffolding # types/params/vars -// ParamKeyTable the param key table for launch module. -func ParamKeyTable() paramtypes.KeyTable { - return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) -} - // NewParams creates a new Params instance. func NewParams(<%= for (param) in params { %> <%= param.Name.LowerCamel %> <%= param.DataType() %>,<% } %> @@ -47,14 +37,6 @@ func DefaultParams() Params { ) } -// ParamSetPairs get the params.ParamSet. -func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { - return paramtypes.ParamSetPairs{<%= for (param) in params { %> - paramtypes.NewParamSetPair(Key<%= param.Name.UpperCamel %>, &p.<%= param.Name.UpperCamel %>, validate<%= param.Name.UpperCamel %>),<% } %> - // this line is used by starport scaffolding # types/params/setpairs - } -} - // Validate validates the set of params. func (p Params) Validate() error {<%= for (param) in params { %> if err := validate<%= param.Name.UpperCamel %>(p.<%= param.Name.UpperCamel %>); err != nil { diff --git a/ignite/templates/module/create/params.go b/ignite/templates/module/create/params.go index ae8c586afd..f1dc212ad0 100644 --- a/ignite/templates/module/create/params.go +++ b/ignite/templates/module/create/params.go @@ -108,16 +108,6 @@ func paramsTypesModify(replacer placeholder.Replacer, opts ParamsOptions) genny. ) content = replacer.Replace(content, module.PlaceholderParamsDefault, replacementDefault) - // add new param set pair. - templateSetPairs := `paramtypes.NewParamSetPair(Key%[2]v, &p.%[2]v, validate%[2]v), -%[1]v` - replacementSetPairs := fmt.Sprintf( - templateSetPairs, - module.PlaceholderParamsSetPairs, - param.Name.UpperCamel, - ) - content = replacer.Replace(content, module.PlaceholderParamsSetPairs, replacementSetPairs) - // add param field to the validate method. templateValidate := `if err := validate%[2]v(p.%[2]v); err != nil { return err diff --git a/ignite/templates/module/placeholders.go b/ignite/templates/module/placeholders.go index 7ece674d37..6e1add0631 100644 --- a/ignite/templates/module/placeholders.go +++ b/ignite/templates/module/placeholders.go @@ -33,7 +33,6 @@ const ( PlaceholderParamsNewParam = "// this line is used by starport scaffolding # types/params/new/parameter" PlaceholderParamsNewStruct = "// this line is used by starport scaffolding # types/params/new/struct" PlaceholderParamsDefault = "// this line is used by starport scaffolding # types/params/default" - PlaceholderParamsSetPairs = "// this line is used by starport scaffolding # types/params/setpairs" PlaceholderParamsValidate = "// this line is used by starport scaffolding # types/params/validate" PlaceholderParamsValidation = "// this line is used by starport scaffolding # types/params/validation" ) From 039cd8921614610b814c1c60ed6cf304127650aa Mon Sep 17 00:00:00 2001 From: Pantani Date: Wed, 6 Dec 2023 00:27:48 +0100 Subject: [PATCH 12/35] add module configs command --- ignite/cmd/scaffold.go | 1 + ignite/cmd/scaffold_chain.go | 8 +- ignite/cmd/scaffold_configs.go | 79 ++++++++++ ignite/cmd/scaffold_module.go | 8 + ignite/cmd/scaffold_params.go | 2 +- ignite/services/scaffolder/configs.go | 141 ++++++++++++++++++ ignite/services/scaffolder/init.go | 15 +- ignite/services/scaffolder/module.go | 25 +++- ignite/templates/module/create/base.go | 1 + ignite/templates/module/create/configs.go | 47 ++++++ .../{{moduleName}}/module/module.proto.plush | 3 + .../{{moduleName}}/params.proto.plush | 2 +- ignite/templates/module/create/options.go | 9 ++ ignite/templates/module/create/params.go | 2 +- integration/params/cmd_configs_test.go | 79 ++++++++++ 15 files changed, 411 insertions(+), 11 deletions(-) create mode 100644 ignite/cmd/scaffold_configs.go create mode 100644 ignite/services/scaffolder/configs.go create mode 100644 ignite/templates/module/create/configs.go create mode 100644 integration/params/cmd_configs_test.go diff --git a/ignite/cmd/scaffold.go b/ignite/cmd/scaffold.go index f937a339d9..6503d1cf10 100644 --- a/ignite/cmd/scaffold.go +++ b/ignite/cmd/scaffold.go @@ -124,6 +124,7 @@ with an "--ibc" flag. Note that the default module is not IBC-enabled. NewScaffoldSingle(), NewScaffoldType(), NewScaffoldParams(), + NewScaffoldConfigs(), NewScaffoldMessage(), NewScaffoldQuery(), NewScaffoldPacket(), diff --git a/ignite/cmd/scaffold_chain.go b/ignite/cmd/scaffold_chain.go index 8a7cc81940..bde40f0b3e 100644 --- a/ignite/cmd/scaffold_chain.go +++ b/ignite/cmd/scaffold_chain.go @@ -80,6 +80,7 @@ about Cosmos SDK on https://docs.cosmos.network c.Flags().StringP(flagPath, "p", "", "create a project in a specific path") c.Flags().Bool(flagNoDefaultModule, false, "create a project without a default module") c.Flags().StringSlice(flagParams, []string{}, "add default module parameters") + c.Flags().StringSlice(flagModuleConfigs, []string{}, "add module configs") c.Flags().Bool(flagSkipGit, false, "skip Git repository initialization") return c @@ -101,7 +102,11 @@ func scaffoldChainHandler(cmd *cobra.Command, args []string) error { if err != nil { return err } - if noDefaultModule && len(params) > 0 { + moduleConfigs, err := cmd.Flags().GetStringSlice(flagModuleConfigs) + if err != nil { + return err + } + if noDefaultModule && (len(params) > 0 || len(moduleConfigs) > 0) { return errors.New("params flag is only supported if the default module is enabled") } @@ -120,6 +125,7 @@ func scaffoldChainHandler(cmd *cobra.Command, args []string) error { noDefaultModule, skipGit, params, + moduleConfigs, ) if err != nil { return err diff --git a/ignite/cmd/scaffold_configs.go b/ignite/cmd/scaffold_configs.go new file mode 100644 index 0000000000..d52cebd0a7 --- /dev/null +++ b/ignite/cmd/scaffold_configs.go @@ -0,0 +1,79 @@ +package ignitecmd + +import ( + "strings" + + "github.com/spf13/cobra" + + "github.com/ignite/cli/v28/ignite/pkg/cliui" + "github.com/ignite/cli/v28/ignite/pkg/placeholder" + "github.com/ignite/cli/v28/ignite/services/scaffolder" +) + +// NewScaffoldConfigs returns the command to scaffold a Cosmos SDK configs into a module. +func NewScaffoldConfigs() *cobra.Command { + c := &cobra.Command{ + Use: "configs [configs]...", + Short: "Configs for a custom Cosmos SDK module", + Long: `Scaffold a new config for a Cosmos SDK module. + +A Cosmos SDK module can have configurations. An example of a config is "address prefix" of the +"auth" module. A config can be scaffolded into a module using the "--module-configs" into +the scaffold module command or using the "scaffold configs" command. By default +configs are of type "string", but you can specify a type for each config. For example: + + ignite scaffold configs foo baz:uint bar:bool + +Refer to Cosmos SDK documentation to learn more about modules, dependencies and +configs. +`, + Args: cobra.MinimumNArgs(1), + PreRunE: migrationPreRunHandler, + RunE: scaffoldConfigsHandler, + } + + flagSetPath(c) + flagSetClearCache(c) + + c.Flags().AddFlagSet(flagSetYes()) + + c.Flags().String(flagModule, "", "module to add the query into. Default: app's main module") + + return c +} + +func scaffoldConfigsHandler(cmd *cobra.Command, args []string) error { + var ( + configs = args[0:] + appPath = flagGetPath(cmd) + moduleName = flagGetModule(cmd) + ) + + session := cliui.New(cliui.StartSpinnerWithText(statusScaffolding)) + defer session.End() + + cacheStorage, err := newCache(cmd) + if err != nil { + return err + } + + sc, err := scaffolder.New(appPath) + if err != nil { + return err + } + + sm, err := sc.CreateConfigs(cmd.Context(), cacheStorage, placeholder.New(), moduleName, configs...) + if err != nil { + return err + } + + modificationsStr, err := sourceModificationToString(sm) + if err != nil { + return err + } + + session.Println(modificationsStr) + session.Printf("\nšŸŽ‰ New configs added to the module:\n\n- %s\n\n", strings.Join(configs, "\n- ")) + + return nil +} diff --git a/ignite/cmd/scaffold_module.go b/ignite/cmd/scaffold_module.go index e953c9ab47..4691622834 100644 --- a/ignite/cmd/scaffold_module.go +++ b/ignite/cmd/scaffold_module.go @@ -25,6 +25,7 @@ const ( flagDep = "dep" flagIBC = "ibc" flagParams = "params" + flagModuleConfigs = "module-configs" flagIBCOrdering = "ordering" flagRequireRegistration = "require-registration" @@ -115,6 +116,7 @@ params. c.Flags().String(flagIBCOrdering, "none", "channel ordering of the IBC module [none|ordered|unordered]") c.Flags().Bool(flagRequireRegistration, false, "fail if module can't be registered") c.Flags().StringSlice(flagParams, []string{}, "add module parameters") + c.Flags().StringSlice(flagModuleConfigs, []string{}, "add module configs") return c } @@ -147,6 +149,11 @@ func scaffoldModuleHandler(cmd *cobra.Command, args []string) error { return err } + moduleConfigs, err := cmd.Flags().GetStringSlice(flagModuleConfigs) + if err != nil { + return err + } + cacheStorage, err := newCache(cmd) if err != nil { return err @@ -154,6 +161,7 @@ func scaffoldModuleHandler(cmd *cobra.Command, args []string) error { options := []scaffolder.ModuleCreationOption{ scaffolder.WithParams(params), + scaffolder.WithModuleConfigs(moduleConfigs), } // Check if the module must be an IBC module diff --git a/ignite/cmd/scaffold_params.go b/ignite/cmd/scaffold_params.go index ec586832f7..57261ca58d 100644 --- a/ignite/cmd/scaffold_params.go +++ b/ignite/cmd/scaffold_params.go @@ -21,7 +21,7 @@ A Cosmos SDK module can have parameters (or "params"). Params are values that can be set at the genesis of the blockchain and can be modified while the blockchain is running. An example of a param is "Inflation rate change" of the "mint" module. A params can be scaffolded into a module using the "--params" into -the scaffold module command or using the scaffold params command. By default +the scaffold module command or using the "scaffold params" command. By default params are of type "string", but you can specify a type for each param. For example: ignite scaffold params foo baz:uint bar:bool diff --git a/ignite/services/scaffolder/configs.go b/ignite/services/scaffolder/configs.go new file mode 100644 index 0000000000..a95fc349a9 --- /dev/null +++ b/ignite/services/scaffolder/configs.go @@ -0,0 +1,141 @@ +package scaffolder + +import ( + "context" + "fmt" + "go/ast" + "go/parser" + "go/token" + "os" + "path/filepath" + "strings" + + "github.com/gobuffalo/genny/v2" + + "github.com/ignite/cli/v28/ignite/pkg/cache" + "github.com/ignite/cli/v28/ignite/pkg/multiformatname" + "github.com/ignite/cli/v28/ignite/pkg/placeholder" + "github.com/ignite/cli/v28/ignite/pkg/xgenny" + "github.com/ignite/cli/v28/ignite/templates/field" + modulecreate "github.com/ignite/cli/v28/ignite/templates/module/create" +) + +// CreateConfigs creates a new configs in the scaffolded module. +func (s Scaffolder) CreateConfigs( + ctx context.Context, + cacheStorage cache.Storage, + tracer *placeholder.Tracer, + moduleName string, + configs ...string, +) (sm xgenny.SourceModification, err error) { + appName := s.modpath.Package + // If no module is provided, we add the type to the app's module + if moduleName == "" { + moduleName = s.modpath.Package + } + mfName, err := multiformatname.NewName(moduleName, multiformatname.NoNumber) + if err != nil { + return sm, err + } + moduleName = mfName.LowerCase + + // Check if the module already exist + ok, err := moduleExists(s.path, moduleName) + if err != nil { + return sm, err + } + if !ok { + return sm, fmt.Errorf("the module %v not exist", moduleName) + } + + // Parse config with the associated type + configsFields, err := field.ParseFields(configs, checkForbiddenTypeIndex) + if err != nil { + return sm, err + } + + if err := checkConfigCreated(s.path, appName, moduleName, configsFields); err != nil { + return sm, err + } + + opts := modulecreate.ConfigsOptions{ + ModuleName: moduleName, + Configs: configsFields, + AppName: s.modpath.Package, + AppPath: s.path, + } + + g, err := modulecreate.NewModuleConfigs(opts) + if err != nil { + return sm, err + } + gens := []*genny.Generator{g} + + sm, err = xgenny.RunWithValidation(tracer, gens...) + if err != nil { + return sm, err + } + + return sm, finish(ctx, cacheStorage, opts.AppPath, s.modpath.RawPath) +} + +// checkConfigCreated checks if the config has been already created. +func checkConfigCreated(appPath, appName, moduleName string, configs field.Fields) (err error) { + absPath, err := filepath.Abs(filepath.Join(appPath, "api", appName, moduleName, "module")) + if err != nil { + return err + } + fileSet := token.NewFileSet() + all, err := parser.ParseDir(fileSet, absPath, func(os.FileInfo) bool { return true }, parser.ParseComments) + if err != nil { + return err + } + + configsName := make(map[string]struct{}) + for _, config := range configs { + configsName[config.Name.LowerCase] = struct{}{} + } + + for _, pkg := range all { + for _, f := range pkg.Files { + ast.Inspect(f, func(x ast.Node) bool { + typeSpec, ok := x.(*ast.TypeSpec) + if !ok { + return true + } + + if _, ok := typeSpec.Type.(*ast.StructType); !ok || + typeSpec.Name.Name != "Module" || + typeSpec.Type == nil { + return true + } + + // Check if the struct has fields. + structType, ok := typeSpec.Type.(*ast.StructType) + if !ok { + return true + } + + // Iterate through the fields of the struct. + for _, configField := range structType.Fields.List { + for _, fieldName := range configField.Names { + if _, ok := configsName[strings.ToLower(fieldName.Name)]; !ok { + continue + } + err = fmt.Errorf( + "config field '%s' already exist for module %s", + fieldName.Name, + moduleName, + ) + return false + } + } + return true + }) + if err != nil { + return err + } + } + } + return err +} diff --git a/ignite/services/scaffolder/init.go b/ignite/services/scaffolder/init.go index f861ebf31f..2905411252 100644 --- a/ignite/services/scaffolder/init.go +++ b/ignite/services/scaffolder/init.go @@ -26,7 +26,8 @@ func Init( tracer *placeholder.Tracer, root, name, addressPrefix string, noDefaultModule, skipGit bool, - params []string, + params, + moduleConfigs []string, ) (path string, err error) { pathInfo, err := gomodulepath.Parse(name) if err != nil { @@ -46,7 +47,7 @@ func Init( path = filepath.Join(root, appFolder) // create the project - err = generate(ctx, tracer, pathInfo, addressPrefix, path, noDefaultModule, params) + err = generate(ctx, tracer, pathInfo, addressPrefix, path, noDefaultModule, params, moduleConfigs) if err != nil { return "", err } @@ -74,7 +75,8 @@ func generate( addressPrefix, absRoot string, noDefaultModule bool, - params []string, + params, + moduleConfigs []string, ) error { // Parse params with the associated type paramsFields, err := field.ParseFields(params, checkForbiddenTypeIndex) @@ -82,6 +84,12 @@ func generate( return err } + // Parse configs with the associated type + configsFields, err := field.ParseFields(moduleConfigs, checkForbiddenTypeIndex) + if err != nil { + return err + } + githubPath := gomodulepath.ExtractAppPath(pathInfo.RawPath) if !strings.Contains(githubPath, "/") { // A username must be added when the app module path has a single element @@ -128,6 +136,7 @@ func generate( AppName: pathInfo.Package, AppPath: absRoot, Params: paramsFields, + Configs: configsFields, IsIBC: false, } // Check if the module name is valid diff --git a/ignite/services/scaffolder/module.go b/ignite/services/scaffolder/module.go index 92fb4b8435..ad5f95653c 100644 --- a/ignite/services/scaffolder/module.go +++ b/ignite/services/scaffolder/module.go @@ -106,16 +106,19 @@ var ( // moduleCreationOptions holds options for creating a new module. type moduleCreationOptions struct { - // ibc true if the module is an ibc module + // ibc true if the module is an ibc module. ibc bool - // params list of parameters + // params list of parameters. params []string - // ibcChannelOrdering ibc channel ordering + // moduleConfigs list of module configs. + moduleConfigs []string + + // ibcChannelOrdering ibc channel ordering. ibcChannelOrdering string - // dependencies list of module dependencies + // dependencies list of module dependencies. dependencies []modulecreate.Dependency } @@ -136,6 +139,13 @@ func WithParams(params []string) ModuleCreationOption { } } +// WithModuleConfigs scaffolds a module with module configs. +func WithModuleConfigs(moduleConfigs []string) ModuleCreationOption { + return func(m *moduleCreationOptions) { + m.moduleConfigs = moduleConfigs + } +} + // WithIBCChannelOrdering configures channel ordering of the IBC module. func WithIBCChannelOrdering(ordering string) ModuleCreationOption { return func(m *moduleCreationOptions) { @@ -197,6 +207,12 @@ func (s Scaffolder) CreateModule( return sm, err } + // Parse configs with the associated type + configs, err := field.ParseFields(creationOpts.moduleConfigs, checkForbiddenTypeIndex) + if err != nil { + return sm, err + } + // Check dependencies if err := checkDependencies(creationOpts.dependencies, s.path); err != nil { return sm, err @@ -206,6 +222,7 @@ func (s Scaffolder) CreateModule( ModuleName: moduleName, ModulePath: s.modpath.RawPath, Params: params, + Configs: configs, AppName: s.modpath.Package, AppPath: s.path, IsIBC: creationOpts.ibc, diff --git a/ignite/templates/module/create/base.go b/ignite/templates/module/create/base.go index ba19e4b9da..0af3c1de82 100644 --- a/ignite/templates/module/create/base.go +++ b/ignite/templates/module/create/base.go @@ -48,6 +48,7 @@ func NewGenerator(opts *CreateOptions) (*genny.Generator, error) { ctx.Set("appName", opts.AppName) ctx.Set("dependencies", opts.Dependencies) ctx.Set("params", opts.Params) + ctx.Set("configs", opts.Configs) ctx.Set("isIBC", opts.IsIBC) ctx.Set("apiPath", fmt.Sprintf("/%s/%s", appModulePath, opts.ModuleName)) ctx.Set("protoPkgName", module.ProtoPackageName(appModulePath, opts.ModuleName)) diff --git a/ignite/templates/module/create/configs.go b/ignite/templates/module/create/configs.go new file mode 100644 index 0000000000..0a51376725 --- /dev/null +++ b/ignite/templates/module/create/configs.go @@ -0,0 +1,47 @@ +package modulecreate + +import ( + "fmt" + "path/filepath" + + "github.com/gobuffalo/genny/v2" + + "github.com/ignite/cli/v28/ignite/pkg/protoanalysis/protoutil" +) + +// NewModuleConfigs returns the generator to scaffold a new configs inside a module. +func NewModuleConfigs(opts ConfigsOptions) (*genny.Generator, error) { + g := genny.New() + g.RunFn(configsProtoModify(opts)) + return g, nil +} + +func configsProtoModify(opts ConfigsOptions) genny.RunFn { + return func(r *genny.Runner) error { + path := filepath.Join(opts.AppPath, "proto", opts.AppName, opts.ModuleName, "module/module.proto") + f, err := r.Disk.Find(path) + if err != nil { + return err + } + protoFile, err := protoutil.ParseProtoFile(f) + if err != nil { + return err + } + + params, err := protoutil.GetMessageByName(protoFile, "Module") + if err != nil { + return fmt.Errorf("couldn't find message 'Module' in %s: %w", path, err) + } + for _, paramField := range opts.Configs { + param := protoutil.NewField( + paramField.Name.LowerCamel, + paramField.DataType(), + protoutil.NextUniqueID(params), + ) + protoutil.Append(params, param) + } + + newFile := genny.NewFileS(path, protoutil.Print(protoFile)) + return r.File(newFile) + } +} diff --git a/ignite/templates/module/create/files/base/proto/{{appName}}/{{moduleName}}/module/module.proto.plush b/ignite/templates/module/create/files/base/proto/{{appName}}/{{moduleName}}/module/module.proto.plush index 4a5ffaa9c9..25ebf95e9a 100644 --- a/ignite/templates/module/create/files/base/proto/{{appName}}/{{moduleName}}/module/module.proto.plush +++ b/ignite/templates/module/create/files/base/proto/{{appName}}/{{moduleName}}/module/module.proto.plush @@ -11,4 +11,7 @@ message Module { // authority defines the custom module authority. If not set, defaults to the governance module. string authority = 1; + + <%= for (i, config) in configs { %> + <%= config.ProtoType(i+2) %>;<% } %> } \ No newline at end of file diff --git a/ignite/templates/module/create/files/base/proto/{{appName}}/{{moduleName}}/params.proto.plush b/ignite/templates/module/create/files/base/proto/{{appName}}/{{moduleName}}/params.proto.plush index 05eb8fdc68..34d5e7498d 100644 --- a/ignite/templates/module/create/files/base/proto/{{appName}}/{{moduleName}}/params.proto.plush +++ b/ignite/templates/module/create/files/base/proto/{{appName}}/{{moduleName}}/params.proto.plush @@ -11,5 +11,5 @@ message Params { option (amino.name) = "<%= appName %>/x/<%= moduleName %>/Params"; option (gogoproto.equal) = true; <%= for (i, param) in params { %> - <%= param.ProtoType(i+1) %> [(gogoproto.moretags) = "yaml:\"<%= param.Name.Snake %>\""];<% } %> + <%= param.ProtoType(i+1) %>;<% } %> } \ No newline at end of file diff --git a/ignite/templates/module/create/options.go b/ignite/templates/module/create/options.go index c3b3cc209b..58d0d40347 100644 --- a/ignite/templates/module/create/options.go +++ b/ignite/templates/module/create/options.go @@ -9,6 +9,14 @@ import ( ) type ( + // ConfigsOptions represents the options to scaffold a Cosmos SDK module configs. + ConfigsOptions struct { + ModuleName string + AppName string + AppPath string + Configs field.Fields + } + // ParamsOptions represents the options to scaffold a Cosmos SDK module parameters. ParamsOptions struct { ModuleName string @@ -24,6 +32,7 @@ type ( AppName string AppPath string Params field.Fields + Configs field.Fields // True if the module should implement the IBC module interface IsIBC bool diff --git a/ignite/templates/module/create/params.go b/ignite/templates/module/create/params.go index f1dc212ad0..3d40c1f084 100644 --- a/ignite/templates/module/create/params.go +++ b/ignite/templates/module/create/params.go @@ -33,7 +33,7 @@ func paramsProtoModify(opts ParamsOptions) genny.RunFn { params, err := protoutil.GetMessageByName(protoFile, "Params") if err != nil { - return fmt.Errorf("couldn't find message 'GenesisState' in %s: %w", path, err) + return fmt.Errorf("couldn't find message 'Params' in %s: %w", path, err) } for _, paramField := range opts.Params { param := protoutil.NewField( diff --git a/integration/params/cmd_configs_test.go b/integration/params/cmd_configs_test.go new file mode 100644 index 0000000000..0a850eabd5 --- /dev/null +++ b/integration/params/cmd_configs_test.go @@ -0,0 +1,79 @@ +//go:build !relayer + +package params_test + +import ( + "testing" + + "github.com/ignite/cli/v28/ignite/pkg/cmdrunner/step" + envtest "github.com/ignite/cli/v28/integration" +) + +func TestCreateModuleConfigs(t *testing.T) { + var ( + env = envtest.New(t) + app = env.Scaffold("github.com/test/mars") + ) + + env.Must(env.Exec("create an module with configs", + step.NewSteps(step.New( + step.Exec(envtest.IgniteApp, + "s", + "module", + "--yes", + "foo", + "--module-configs", + "bla,baz:uint,bar:bool", + "--require-registration"), + step.Workdir(app.SourcePath()), + )), + )) + + env.Must(env.Exec("should prevent creating configs field that already exist", + step.NewSteps(step.New( + step.Exec(envtest.IgniteApp, + "s", + "configs", + "--yes", + "bla", + "buu:uint", + "--module", + "foo", + ), + step.Workdir(app.SourcePath()), + )), + envtest.ExecShouldError(), + )) + + env.Must(env.Exec("create an new module configs in the foo module", + step.NewSteps(step.New( + step.Exec(envtest.IgniteApp, + "s", + "configs", + "--yes", + "bol", + "buu:uint", + "plk:bool", + "--module", + "foo", + ), + step.Workdir(app.SourcePath()), + )), + )) + + env.Must(env.Exec("create an new module configs in the mars module", + step.NewSteps(step.New( + step.Exec(envtest.IgniteApp, + "s", + "configs", + "--yes", + "foo", + "bar:uint", + "baz:bool", + ), + step.Workdir(app.SourcePath()), + )), + )) + + app.EnsureSteady() +} From a6a3f938f884cfd07a7758e913e24365f327c7b7 Mon Sep 17 00:00:00 2001 From: Pantani Date: Wed, 6 Dec 2023 00:35:39 +0100 Subject: [PATCH 13/35] rollbak empty line for proto --- .../base/proto/{{appName}}/{{moduleName}}/params.proto.plush | 1 + 1 file changed, 1 insertion(+) diff --git a/ignite/templates/module/create/files/base/proto/{{appName}}/{{moduleName}}/params.proto.plush b/ignite/templates/module/create/files/base/proto/{{appName}}/{{moduleName}}/params.proto.plush index 34d5e7498d..df82363d16 100644 --- a/ignite/templates/module/create/files/base/proto/{{appName}}/{{moduleName}}/params.proto.plush +++ b/ignite/templates/module/create/files/base/proto/{{appName}}/{{moduleName}}/params.proto.plush @@ -10,6 +10,7 @@ option go_package = "<%= modulePath %>/x/<%= moduleName %>/types"; message Params { option (amino.name) = "<%= appName %>/x/<%= moduleName %>/Params"; option (gogoproto.equal) = true; + <%= for (i, param) in params { %> <%= param.ProtoType(i+1) %>;<% } %> } \ No newline at end of file From 8564b7d357ac92726b1e657e695d4e09aea93716 Mon Sep 17 00:00:00 2001 From: Pantani Date: Wed, 6 Dec 2023 02:34:35 +0100 Subject: [PATCH 14/35] improve code readbility and add unit tests --- ignite/pkg/goanalysis/goanalysis.go | 56 +++++++++ ignite/pkg/goanalysis/goanalysis_test.go | 118 +++++++++++++++++-- ignite/pkg/goanalysis/testdata/fieldexist.go | 13 ++ ignite/services/scaffolder/configs.go | 76 +++--------- ignite/services/scaffolder/params.go | 76 +++--------- 5 files changed, 204 insertions(+), 135 deletions(-) create mode 100644 ignite/pkg/goanalysis/testdata/fieldexist.go diff --git a/ignite/pkg/goanalysis/goanalysis.go b/ignite/pkg/goanalysis/goanalysis.go index 96115824ce..7dc391585a 100644 --- a/ignite/pkg/goanalysis/goanalysis.go +++ b/ignite/pkg/goanalysis/goanalysis.go @@ -279,3 +279,59 @@ func createUnderscoreImport(imp string) *ast.ImportSpec { }, } } + +// HasStructFieldsInPkg finds the struct into a package folder and checks +// if the field exists inside the struct. +func HasStructFieldsInPkg(pkgPath, structName string, fields []string) (bool, error) { + absPath, err := filepath.Abs(pkgPath) + if err != nil { + return false, err + } + fileSet := token.NewFileSet() + all, err := parser.ParseDir(fileSet, absPath, func(os.FileInfo) bool { return true }, parser.ParseComments) + if err != nil { + return false, err + } + + fieldsNames := make(map[string]struct{}) + for _, field := range fields { + fieldsNames[strings.ToLower(field)] = struct{}{} + } + + exist := false + for _, pkg := range all { + for _, f := range pkg.Files { + ast.Inspect(f, func(x ast.Node) bool { + typeSpec, ok := x.(*ast.TypeSpec) + if !ok { + return true + } + + if _, ok := typeSpec.Type.(*ast.StructType); !ok || + typeSpec.Name.Name != structName || + typeSpec.Type == nil { + return true + } + + // Check if the struct has fields. + structType, ok := typeSpec.Type.(*ast.StructType) + if !ok { + return true + } + + // Iterate through the fields of the struct. + for _, field := range structType.Fields.List { + for _, fieldName := range field.Names { + if _, ok := fieldsNames[strings.ToLower(fieldName.Name)]; !ok { + continue + } + exist = true + return false + } + } + return true + }) + } + } + return exist, nil +} diff --git a/ignite/pkg/goanalysis/goanalysis_test.go b/ignite/pkg/goanalysis/goanalysis_test.go index c562080eb5..a49846df7c 100644 --- a/ignite/pkg/goanalysis/goanalysis_test.go +++ b/ignite/pkg/goanalysis/goanalysis_test.go @@ -144,84 +144,84 @@ func createMainFiles(tmpDir string, mainFiles []string) (pathsWithMain []string, func TestFuncVarExists(t *testing.T) { tests := []struct { name string - testfile string + testFile string goImport string methodSignature string want bool }{ { name: "test a declaration inside a method success", - testfile: "testdata/varexist", + testFile: "testdata/varexist", methodSignature: "Background", goImport: "context", want: true, }, { name: "test global declaration success", - testfile: "testdata/varexist", + testFile: "testdata/varexist", methodSignature: "Join", goImport: "path/filepath", want: true, }, { name: "test a declaration inside an if and inside a method success", - testfile: "testdata/varexist", + testFile: "testdata/varexist", methodSignature: "SplitList", goImport: "path/filepath", want: true, }, { name: "test global variable success assign", - testfile: "testdata/varexist", + testFile: "testdata/varexist", methodSignature: "New", goImport: "errors", want: true, }, { name: "test invalid import", - testfile: "testdata/varexist", + testFile: "testdata/varexist", methodSignature: "Join", goImport: "errors", want: false, }, { name: "test invalid case sensitive assign", - testfile: "testdata/varexist", + testFile: "testdata/varexist", methodSignature: "join", goImport: "context", want: false, }, { name: "test invalid struct assign", - testfile: "testdata/varexist", + testFile: "testdata/varexist", methodSignature: "fooStruct", goImport: "context", want: false, }, { name: "test invalid method signature", - testfile: "testdata/varexist", + testFile: "testdata/varexist", methodSignature: "fooMethod", goImport: "context", want: false, }, { name: "test not found name", - testfile: "testdata/varexist", + testFile: "testdata/varexist", methodSignature: "Invalid", goImport: "context", want: false, }, { name: "test invalid assign with wrong", - testfile: "testdata/varexist", + testFile: "testdata/varexist", methodSignature: "invalid.New", goImport: "context", want: false, }, { name: "test invalid assign with wrong", - testfile: "testdata/varexist", + testFile: "testdata/varexist", methodSignature: "SplitList", goImport: "path/filepath", want: true, @@ -229,7 +229,7 @@ func TestFuncVarExists(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - appPkg, _, err := xast.ParseFile(tt.testfile) + appPkg, _, err := xast.ParseFile(tt.testFile) require.NoError(t, err) got := goanalysis.FuncVarExists(appPkg, tt.goImport, tt.methodSignature) @@ -502,3 +502,95 @@ func TestUpdateInitImports(t *testing.T) { }) } } + +func TestHasStructFieldsInPkg(t *testing.T) { + tests := []struct { + name string + path string + structName string + fields []string + err error + want bool + }{ + { + name: "test a value with an empty struct", + path: "testdata", + structName: "emptyStruct", + fields: []string{"name"}, + want: false, + }, + { + name: "test no value with an empty struct", + path: "testdata", + structName: "emptyStruct", + fields: []string{""}, + want: false, + }, + { + name: "test a valid field into single field struct", + path: "testdata", + structName: "fooStruct", + fields: []string{"name"}, + want: true, + }, + { + name: "test a not valid field into single field struct", + path: "testdata", + structName: "fooStruct", + fields: []string{"baz"}, + want: false, + }, + { + name: "test a not valid field into struct", + path: "testdata", + structName: "bazStruct", + fields: []string{"baz"}, + want: false, + }, + { + name: "test a valid field into struct", + path: "testdata", + structName: "bazStruct", + fields: []string{"name"}, + want: true, + }, + { + name: "test two valid fields into struct", + path: "testdata", + structName: "bazStruct", + fields: []string{"name", "title"}, + want: true, + }, + { + name: "test a valid and a not valid fields into struct", + path: "testdata", + structName: "bazStruct", + fields: []string{"foo", "title"}, + want: true, + }, + { + name: "test three not valid fields into struct", + path: "testdata", + structName: "bazStruct", + fields: []string{"foo", "baz", "bla"}, + want: false, + }, + { + name: "invalid path", + path: "invalid_path", + err: os.ErrNotExist, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := goanalysis.HasStructFieldsInPkg(tt.path, tt.structName, tt.fields) + if tt.err != nil { + require.Error(t, err) + require.ErrorIs(t, err, tt.err) + return + } + require.NoError(t, err) + require.Equal(t, tt.want, got) + }) + } +} diff --git a/ignite/pkg/goanalysis/testdata/fieldexist.go b/ignite/pkg/goanalysis/testdata/fieldexist.go new file mode 100644 index 0000000000..34d9e6c6bc --- /dev/null +++ b/ignite/pkg/goanalysis/testdata/fieldexist.go @@ -0,0 +1,13 @@ +package goanalysis + +type ( + emptyStruct struct{} + fooStruct struct { + name string + } + bazStruct struct { + name string + title string + description string + } +) diff --git a/ignite/services/scaffolder/configs.go b/ignite/services/scaffolder/configs.go index a95fc349a9..e533e4eff1 100644 --- a/ignite/services/scaffolder/configs.go +++ b/ignite/services/scaffolder/configs.go @@ -3,16 +3,13 @@ package scaffolder import ( "context" "fmt" - "go/ast" - "go/parser" - "go/token" - "os" "path/filepath" "strings" "github.com/gobuffalo/genny/v2" "github.com/ignite/cli/v28/ignite/pkg/cache" + "github.com/ignite/cli/v28/ignite/pkg/goanalysis" "github.com/ignite/cli/v28/ignite/pkg/multiformatname" "github.com/ignite/cli/v28/ignite/pkg/placeholder" "github.com/ignite/cli/v28/ignite/pkg/xgenny" @@ -48,13 +45,13 @@ func (s Scaffolder) CreateConfigs( return sm, fmt.Errorf("the module %v not exist", moduleName) } - // Parse config with the associated type - configsFields, err := field.ParseFields(configs, checkForbiddenTypeIndex) - if err != nil { + if err := checkConfigCreated(s.path, appName, moduleName, configs); err != nil { return sm, err } - if err := checkConfigCreated(s.path, appName, moduleName, configsFields); err != nil { + // Parse config with the associated type + configsFields, err := field.ParseFields(configs, checkForbiddenTypeIndex) + if err != nil { return sm, err } @@ -80,62 +77,19 @@ func (s Scaffolder) CreateConfigs( } // checkConfigCreated checks if the config has been already created. -func checkConfigCreated(appPath, appName, moduleName string, configs field.Fields) (err error) { - absPath, err := filepath.Abs(filepath.Join(appPath, "api", appName, moduleName, "module")) - if err != nil { - return err - } - fileSet := token.NewFileSet() - all, err := parser.ParseDir(fileSet, absPath, func(os.FileInfo) bool { return true }, parser.ParseComments) +func checkConfigCreated(appPath, appName, moduleName string, configs []string) (err error) { + path := filepath.Join(appPath, "api", appName, moduleName, "module") + ok, err := goanalysis.HasStructFieldsInPkg(path, "Module", configs) if err != nil { return err } - configsName := make(map[string]struct{}) - for _, config := range configs { - configsName[config.Name.LowerCase] = struct{}{} - } - - for _, pkg := range all { - for _, f := range pkg.Files { - ast.Inspect(f, func(x ast.Node) bool { - typeSpec, ok := x.(*ast.TypeSpec) - if !ok { - return true - } - - if _, ok := typeSpec.Type.(*ast.StructType); !ok || - typeSpec.Name.Name != "Module" || - typeSpec.Type == nil { - return true - } - - // Check if the struct has fields. - structType, ok := typeSpec.Type.(*ast.StructType) - if !ok { - return true - } - - // Iterate through the fields of the struct. - for _, configField := range structType.Fields.List { - for _, fieldName := range configField.Names { - if _, ok := configsName[strings.ToLower(fieldName.Name)]; !ok { - continue - } - err = fmt.Errorf( - "config field '%s' already exist for module %s", - fieldName.Name, - moduleName, - ) - return false - } - } - return true - }) - if err != nil { - return err - } - } + if ok { + return fmt.Errorf( + "duplicated configs (%s) module %s", + strings.Join(configs, " "), + moduleName, + ) } - return err + return nil } diff --git a/ignite/services/scaffolder/params.go b/ignite/services/scaffolder/params.go index 6fdb907dcc..19a503ede4 100644 --- a/ignite/services/scaffolder/params.go +++ b/ignite/services/scaffolder/params.go @@ -3,16 +3,13 @@ package scaffolder import ( "context" "fmt" - "go/ast" - "go/parser" - "go/token" - "os" "path/filepath" "strings" "github.com/gobuffalo/genny/v2" "github.com/ignite/cli/v28/ignite/pkg/cache" + "github.com/ignite/cli/v28/ignite/pkg/goanalysis" "github.com/ignite/cli/v28/ignite/pkg/multiformatname" "github.com/ignite/cli/v28/ignite/pkg/placeholder" "github.com/ignite/cli/v28/ignite/pkg/xgenny" @@ -47,13 +44,13 @@ func (s Scaffolder) CreateParams( return sm, fmt.Errorf("the module %v not exist", moduleName) } - // Parse params with the associated type - paramsFields, err := field.ParseFields(params, checkForbiddenTypeIndex) - if err != nil { + if err := checkParamCreated(s.path, moduleName, params); err != nil { return sm, err } - if err := checkParamCreated(s.path, moduleName, paramsFields); err != nil { + // Parse params with the associated type + paramsFields, err := field.ParseFields(params, checkForbiddenTypeIndex) + if err != nil { return sm, err } @@ -79,62 +76,19 @@ func (s Scaffolder) CreateParams( } // checkParamCreated checks if the parameter has been already created. -func checkParamCreated(appPath, moduleName string, params field.Fields) (err error) { - absPath, err := filepath.Abs(filepath.Join(appPath, "x", moduleName, "types")) - if err != nil { - return err - } - fileSet := token.NewFileSet() - all, err := parser.ParseDir(fileSet, absPath, func(os.FileInfo) bool { return true }, parser.ParseComments) +func checkParamCreated(appPath, moduleName string, params []string) error { + path := filepath.Join(appPath, "x", moduleName, "types") + ok, err := goanalysis.HasStructFieldsInPkg(path, "Params", params) if err != nil { return err } - paramsName := make(map[string]struct{}) - for _, param := range params { - paramsName[param.Name.LowerCase] = struct{}{} - } - - for _, pkg := range all { - for _, f := range pkg.Files { - ast.Inspect(f, func(x ast.Node) bool { - typeSpec, ok := x.(*ast.TypeSpec) - if !ok { - return true - } - - if _, ok := typeSpec.Type.(*ast.StructType); !ok || - typeSpec.Name.Name != "Params" || - typeSpec.Type == nil { - return true - } - - // Check if the struct has fields. - structType, ok := typeSpec.Type.(*ast.StructType) - if !ok { - return true - } - - // Iterate through the fields of the struct. - for _, paramField := range structType.Fields.List { - for _, fieldName := range paramField.Names { - if _, ok := paramsName[strings.ToLower(fieldName.Name)]; !ok { - continue - } - err = fmt.Errorf( - "param field '%s' already exist for module %s", - fieldName.Name, - moduleName, - ) - return false - } - } - return true - }) - if err != nil { - return err - } - } + if ok { + return fmt.Errorf( + "duplicated params (%s) module %s", + strings.Join(params, " "), + moduleName, + ) } - return err + return nil } From ba0a50eeae6ae7c61e53789d6f9277e3e51295a3 Mon Sep 17 00:00:00 2001 From: Pantani Date: Tue, 12 Dec 2023 18:37:15 -0300 Subject: [PATCH 15/35] fix changelog --- changelog.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/changelog.md b/changelog.md index 43dc7a25a0..a83e4fcac7 100644 --- a/changelog.md +++ b/changelog.md @@ -9,6 +9,7 @@ ### Changes - [#3838](https://github.com/ignite/cli/pull/3838) Scaffold chain with Cosmos SDK `v0.50.2`, and bump confix and x/upgrade to latest +- [#3770](https://github.com/ignite/cli/pull/3770) Add `scaffold configs` and `scaffold params` commands ### Fixes @@ -16,11 +17,6 @@ - [#3831](https://github.com/ignite/cli/pull/3831) Correct ignite app gRPC server stop memory issue - [#3825](https://github.com/ignite/cli/pull/3825) Fix a minor Keplr type-checking bug in TS client - [#3836](https://github.com/ignite/cli/pull/3836) Add missing IBC commands for scaffolded chain - -### Features - -- [#3830](https://github.com/ignite/cli/pull/3830) Remove gRPC info from Ignite Apps errors -- [#3684](https://github.com/ignite/cli/issues/3684) Add scaffold params command - [#3833](https://github.com/ignite/cli/pull/3833) Improve Cosmos SDK detection to support SDK forks ## [`v28.0.0`](https://github.com/ignite/cli/releases/tag/v28.0.0) From cef9405670269864720a4d69d4332d2501820ae1 Mon Sep 17 00:00:00 2001 From: Pantani Date: Thu, 21 Dec 2023 14:55:32 -0300 Subject: [PATCH 16/35] use cli error package --- ignite/services/scaffolder/configs.go | 6 +++--- ignite/services/scaffolder/params.go | 6 +++--- ignite/templates/module/create/configs.go | 4 ++-- ignite/templates/module/create/params.go | 3 ++- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/ignite/services/scaffolder/configs.go b/ignite/services/scaffolder/configs.go index e533e4eff1..348d54bfd9 100644 --- a/ignite/services/scaffolder/configs.go +++ b/ignite/services/scaffolder/configs.go @@ -2,13 +2,13 @@ package scaffolder import ( "context" - "fmt" "path/filepath" "strings" "github.com/gobuffalo/genny/v2" "github.com/ignite/cli/v28/ignite/pkg/cache" + "github.com/ignite/cli/v28/ignite/pkg/errors" "github.com/ignite/cli/v28/ignite/pkg/goanalysis" "github.com/ignite/cli/v28/ignite/pkg/multiformatname" "github.com/ignite/cli/v28/ignite/pkg/placeholder" @@ -42,7 +42,7 @@ func (s Scaffolder) CreateConfigs( return sm, err } if !ok { - return sm, fmt.Errorf("the module %v not exist", moduleName) + return sm, errors.Errorf("the module %v not exist", moduleName) } if err := checkConfigCreated(s.path, appName, moduleName, configs); err != nil { @@ -85,7 +85,7 @@ func checkConfigCreated(appPath, appName, moduleName string, configs []string) ( } if ok { - return fmt.Errorf( + return errors.Errorf( "duplicated configs (%s) module %s", strings.Join(configs, " "), moduleName, diff --git a/ignite/services/scaffolder/params.go b/ignite/services/scaffolder/params.go index 19a503ede4..e1cd98ccd5 100644 --- a/ignite/services/scaffolder/params.go +++ b/ignite/services/scaffolder/params.go @@ -2,13 +2,13 @@ package scaffolder import ( "context" - "fmt" "path/filepath" "strings" "github.com/gobuffalo/genny/v2" "github.com/ignite/cli/v28/ignite/pkg/cache" + "github.com/ignite/cli/v28/ignite/pkg/errors" "github.com/ignite/cli/v28/ignite/pkg/goanalysis" "github.com/ignite/cli/v28/ignite/pkg/multiformatname" "github.com/ignite/cli/v28/ignite/pkg/placeholder" @@ -41,7 +41,7 @@ func (s Scaffolder) CreateParams( return sm, err } if !ok { - return sm, fmt.Errorf("the module %v not exist", moduleName) + return sm, errors.Errorf("the module %v not exist", moduleName) } if err := checkParamCreated(s.path, moduleName, params); err != nil { @@ -84,7 +84,7 @@ func checkParamCreated(appPath, moduleName string, params []string) error { } if ok { - return fmt.Errorf( + return errors.Errorf( "duplicated params (%s) module %s", strings.Join(params, " "), moduleName, diff --git a/ignite/templates/module/create/configs.go b/ignite/templates/module/create/configs.go index 0a51376725..db3ece0934 100644 --- a/ignite/templates/module/create/configs.go +++ b/ignite/templates/module/create/configs.go @@ -1,11 +1,11 @@ package modulecreate import ( - "fmt" "path/filepath" "github.com/gobuffalo/genny/v2" + "github.com/ignite/cli/v28/ignite/pkg/errors" "github.com/ignite/cli/v28/ignite/pkg/protoanalysis/protoutil" ) @@ -30,7 +30,7 @@ func configsProtoModify(opts ConfigsOptions) genny.RunFn { params, err := protoutil.GetMessageByName(protoFile, "Module") if err != nil { - return fmt.Errorf("couldn't find message 'Module' in %s: %w", path, err) + return errors.Errorf("couldn't find message 'Module' in %s: %w", path, err) } for _, paramField := range opts.Configs { param := protoutil.NewField( diff --git a/ignite/templates/module/create/params.go b/ignite/templates/module/create/params.go index 3d40c1f084..41e885d4e6 100644 --- a/ignite/templates/module/create/params.go +++ b/ignite/templates/module/create/params.go @@ -6,6 +6,7 @@ import ( "github.com/gobuffalo/genny/v2" + "github.com/ignite/cli/v28/ignite/pkg/errors" "github.com/ignite/cli/v28/ignite/pkg/placeholder" "github.com/ignite/cli/v28/ignite/pkg/protoanalysis/protoutil" "github.com/ignite/cli/v28/ignite/templates/module" @@ -33,7 +34,7 @@ func paramsProtoModify(opts ParamsOptions) genny.RunFn { params, err := protoutil.GetMessageByName(protoFile, "Params") if err != nil { - return fmt.Errorf("couldn't find message 'Params' in %s: %w", path, err) + return errors.Errorf("couldn't find message 'Params' in %s: %w", path, err) } for _, paramField := range opts.Params { param := protoutil.NewField( From 839867ab08daaee8f704c41e0edf0066318b8eb2 Mon Sep 17 00:00:00 2001 From: Danilo Pantani Date: Mon, 15 Jan 2024 09:57:41 -0300 Subject: [PATCH 17/35] Update ignite/cmd/scaffold_configs.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JerĆ³nimo Albi --- ignite/cmd/scaffold_configs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ignite/cmd/scaffold_configs.go b/ignite/cmd/scaffold_configs.go index d52cebd0a7..9a7bf29d1d 100644 --- a/ignite/cmd/scaffold_configs.go +++ b/ignite/cmd/scaffold_configs.go @@ -37,7 +37,7 @@ configs. c.Flags().AddFlagSet(flagSetYes()) - c.Flags().String(flagModule, "", "module to add the query into. Default: app's main module") + c.Flags().String(flagModule, "", "module to add the query into (default: app's main module)") return c } From 557eed3a3492bc7c19e6f4e7f1d6357ac6c7f190 Mon Sep 17 00:00:00 2001 From: Pantani Date: Tue, 16 Jan 2024 10:57:13 -0300 Subject: [PATCH 18/35] apply pr suggestions --- ignite/cmd/scaffold_chain.go | 8 ++++++-- ignite/pkg/goanalysis/goanalysis.go | 8 ++++---- ignite/pkg/goanalysis/goanalysis_test.go | 2 +- ignite/services/scaffolder/configs.go | 2 +- ignite/services/scaffolder/params.go | 2 +- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/ignite/cmd/scaffold_chain.go b/ignite/cmd/scaffold_chain.go index e8cf84f456..5426cdadac 100644 --- a/ignite/cmd/scaffold_chain.go +++ b/ignite/cmd/scaffold_chain.go @@ -108,8 +108,12 @@ func scaffoldChainHandler(cmd *cobra.Command, args []string) error { if err != nil { return err } - if noDefaultModule && (len(params) > 0 || len(moduleConfigs) > 0) { - return errors.New("params flag is only supported if the default module is enabled") + if noDefaultModule { + if len(params) > 0 { + return errors.New("params flag is only supported if the default module is enabled") + } else if len(moduleConfigs) > 0 { + return errors.New("module configs flag is only supported if the default module is enabled") + } } cacheStorage, err := newCache(cmd) diff --git a/ignite/pkg/goanalysis/goanalysis.go b/ignite/pkg/goanalysis/goanalysis.go index 481763a4ec..fe618015ee 100644 --- a/ignite/pkg/goanalysis/goanalysis.go +++ b/ignite/pkg/goanalysis/goanalysis.go @@ -280,15 +280,15 @@ func createUnderscoreImport(imp string) *ast.ImportSpec { } } -// HasStructFieldsInPkg finds the struct into a package folder and checks -// if the field exists inside the struct. -func HasStructFieldsInPkg(pkgPath, structName string, fields []string) (bool, error) { +// HasAnyStructFieldsInPkg finds the struct within a package folder and checks +// if any of the fields are defined in the struct. +func HasAnyStructFieldsInPkg(pkgPath, structName string, fields []string) (bool, error) { absPath, err := filepath.Abs(pkgPath) if err != nil { return false, err } fileSet := token.NewFileSet() - all, err := parser.ParseDir(fileSet, absPath, func(os.FileInfo) bool { return true }, parser.ParseComments) + all, err := parser.ParseDir(fileSet, absPath, nil, parser.ParseComments) if err != nil { return false, err } diff --git a/ignite/pkg/goanalysis/goanalysis_test.go b/ignite/pkg/goanalysis/goanalysis_test.go index 7572714563..620514cd7d 100644 --- a/ignite/pkg/goanalysis/goanalysis_test.go +++ b/ignite/pkg/goanalysis/goanalysis_test.go @@ -584,7 +584,7 @@ func TestHasStructFieldsInPkg(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := goanalysis.HasStructFieldsInPkg(tt.path, tt.structName, tt.fields) + got, err := goanalysis.HasAnyStructFieldsInPkg(tt.path, tt.structName, tt.fields) if tt.err != nil { require.Error(t, err) require.ErrorIs(t, err, tt.err) diff --git a/ignite/services/scaffolder/configs.go b/ignite/services/scaffolder/configs.go index 348d54bfd9..8e347765e9 100644 --- a/ignite/services/scaffolder/configs.go +++ b/ignite/services/scaffolder/configs.go @@ -79,7 +79,7 @@ func (s Scaffolder) CreateConfigs( // checkConfigCreated checks if the config has been already created. func checkConfigCreated(appPath, appName, moduleName string, configs []string) (err error) { path := filepath.Join(appPath, "api", appName, moduleName, "module") - ok, err := goanalysis.HasStructFieldsInPkg(path, "Module", configs) + ok, err := goanalysis.HasAnyStructFieldsInPkg(path, "Module", configs) if err != nil { return err } diff --git a/ignite/services/scaffolder/params.go b/ignite/services/scaffolder/params.go index e1cd98ccd5..ddbafe3e62 100644 --- a/ignite/services/scaffolder/params.go +++ b/ignite/services/scaffolder/params.go @@ -78,7 +78,7 @@ func (s Scaffolder) CreateParams( // checkParamCreated checks if the parameter has been already created. func checkParamCreated(appPath, moduleName string, params []string) error { path := filepath.Join(appPath, "x", moduleName, "types") - ok, err := goanalysis.HasStructFieldsInPkg(path, "Params", params) + ok, err := goanalysis.HasAnyStructFieldsInPkg(path, "Params", params) if err != nil { return err } From 52f7239ec8004e83377de11f3d30cd63750a3a23 Mon Sep 17 00:00:00 2001 From: Pantani Date: Tue, 16 Jan 2024 12:55:12 -0300 Subject: [PATCH 19/35] fix some typos --- ignite/templates/field/field.go | 2 +- integration/params/cmd_configs_test.go | 6 +++--- integration/params/cmd_params_test.go | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ignite/templates/field/field.go b/ignite/templates/field/field.go index 13710a8daf..f33a8ea1d1 100644 --- a/ignite/templates/field/field.go +++ b/ignite/templates/field/field.go @@ -159,7 +159,7 @@ func (f Field) ProtoImports() []string { // Value returns the field assign value. func (f Field) Value() string { if f.DataType() == "string" { - return fmt.Sprintf("\"%s\"", f.Name.Snake) + return fmt.Sprintf(`"%s"`, f.Name.Snake) } return f.ValueIndex() } diff --git a/integration/params/cmd_configs_test.go b/integration/params/cmd_configs_test.go index 0a850eabd5..2916467f5b 100644 --- a/integration/params/cmd_configs_test.go +++ b/integration/params/cmd_configs_test.go @@ -15,7 +15,7 @@ func TestCreateModuleConfigs(t *testing.T) { app = env.Scaffold("github.com/test/mars") ) - env.Must(env.Exec("create an module with configs", + env.Must(env.Exec("create a new module with configs", step.NewSteps(step.New( step.Exec(envtest.IgniteApp, "s", @@ -45,7 +45,7 @@ func TestCreateModuleConfigs(t *testing.T) { envtest.ExecShouldError(), )) - env.Must(env.Exec("create an new module configs in the foo module", + env.Must(env.Exec("create a new module configs in the foo module", step.NewSteps(step.New( step.Exec(envtest.IgniteApp, "s", @@ -61,7 +61,7 @@ func TestCreateModuleConfigs(t *testing.T) { )), )) - env.Must(env.Exec("create an new module configs in the mars module", + env.Must(env.Exec("create a new module configs in the mars module", step.NewSteps(step.New( step.Exec(envtest.IgniteApp, "s", diff --git a/integration/params/cmd_params_test.go b/integration/params/cmd_params_test.go index 92f1965fdb..65e47ebf24 100644 --- a/integration/params/cmd_params_test.go +++ b/integration/params/cmd_params_test.go @@ -15,7 +15,7 @@ func TestCreateModuleParameters(t *testing.T) { app = env.Scaffold("github.com/test/mars") ) - env.Must(env.Exec("create an module with parameter", + env.Must(env.Exec("create a new module with parameter", step.NewSteps(step.New( step.Exec(envtest.IgniteApp, "s", @@ -45,7 +45,7 @@ func TestCreateModuleParameters(t *testing.T) { envtest.ExecShouldError(), )) - env.Must(env.Exec("create an new module parameters in the foo module", + env.Must(env.Exec("create a new module parameters in the foo module", step.NewSteps(step.New( step.Exec(envtest.IgniteApp, "s", @@ -61,7 +61,7 @@ func TestCreateModuleParameters(t *testing.T) { )), )) - env.Must(env.Exec("create an new module parameters in the mars module", + env.Must(env.Exec("create a new module parameters in the mars module", step.NewSteps(step.New( step.Exec(envtest.IgniteApp, "s", From d650546aa46b89d61ee72714aa8ee455a6d4e794 Mon Sep 17 00:00:00 2001 From: Pantani Date: Tue, 16 Jan 2024 14:59:38 -0300 Subject: [PATCH 20/35] remove unused vars and casting --- changelog.md | 5 ++++- .../x/{{moduleName}}/types/params.go.plush | 21 ++----------------- ignite/templates/module/create/params.go | 20 ++++-------------- 3 files changed, 10 insertions(+), 36 deletions(-) diff --git a/changelog.md b/changelog.md index bebe1f0dad..69ddce3e34 100644 --- a/changelog.md +++ b/changelog.md @@ -6,6 +6,10 @@ - [#3835](https://github.com/ignite/cli/pull/3835) Add `--minimal` flag to `scaffold chain` to scaffold a chain with the least amount of sdk modules +### Changes + +- [#3770](https://github.com/ignite/cli/pull/3770) Add `scaffold configs` and `scaffold params` commands + ## [`v28.1.1`](https://github.com/ignite/cli/releases/tag/v28.1.1) ### Fixes @@ -32,7 +36,6 @@ - [#3822](https://github.com/ignite/cli/pull/3822) Improve default scaffolded AutoCLI config - [#3838](https://github.com/ignite/cli/pull/3838) Scaffold chain with Cosmos SDK `v0.50.2`, and bump confix and x/upgrade to latest -- [#3770](https://github.com/ignite/cli/pull/3770) Add `scaffold configs` and `scaffold params` commands - [#3829](https://github.com/ignite/cli/pull/3829) Support version prefix for cached values - [#3723](https://github.com/ignite/cli/pull/3723) Create a wrapper for errors diff --git a/ignite/templates/module/create/files/base/x/{{moduleName}}/types/params.go.plush b/ignite/templates/module/create/files/base/x/{{moduleName}}/types/params.go.plush index b100bd5a3d..297f9560f1 100644 --- a/ignite/templates/module/create/files/base/x/{{moduleName}}/types/params.go.plush +++ b/ignite/templates/module/create/files/base/x/{{moduleName}}/types/params.go.plush @@ -1,19 +1,9 @@ package types -import ( - "fmt" -) - -var _ = fmt.Errorf - <%= for (param) in params { %> -var ( - // Key<%= param.Name.UpperCamel %> represents the <%= param.Name.UpperCamel %> parameter. - Key<%= param.Name.UpperCamel %> = []byte("<%= param.Name.UpperCamel %>") // Default<%= param.Name.UpperCamel %> represents the <%= param.Name.UpperCamel %> default value. // TODO: Determine the default value. - Default<%= param.Name.UpperCamel %> <%= param.DataType() %> = <%= if (param.DataType() == "string") { %>"<%= param.Name.Snake %>"<% } else { %><%= param.ValueIndex() %><% } %> -) + var Default<%= param.Name.UpperCamel %> <%= param.DataType() %> = <%= if (param.DataType() == "string") { %>"<%= param.Name.Snake %>"<% } else { %><%= param.ValueIndex() %><% } %> <% } %> // this line is used by starport scaffolding # types/params/vars @@ -51,15 +41,8 @@ func (p Params) Validate() error {<%= for (param) in params { %> <%= for (param) in params { %> // validate<%= param.Name.UpperCamel %> validates the <%= param.Name.UpperCamel %> parameter. -func validate<%= param.Name.UpperCamel %>(v interface{}) error { - <%= param.Name.LowerCamel %>, ok := v.(<%= param.DataType() %>) - if !ok { - return fmt.Errorf("invalid parameter type: %T", v) - } - +func validate<%= param.Name.UpperCamel %>(v <%= param.DataType() %>) error { // TODO implement validation - _ = <%= param.Name.LowerCamel %> - return nil } <% } %> diff --git a/ignite/templates/module/create/params.go b/ignite/templates/module/create/params.go index 41e885d4e6..b31f95eeae 100644 --- a/ignite/templates/module/create/params.go +++ b/ignite/templates/module/create/params.go @@ -61,15 +61,11 @@ func paramsTypesModify(replacer placeholder.Replacer, opts ParamsOptions) genny. content := f.String() for _, param := range opts.Params { // param key and default value. - templateVars := `var ( - // Key%[2]v represents the %[2]v parameter. - Key%[2]v = []byte("%[2]v") + templateVars := ` // Default%[2]v represents the %[2]v default value. // TODO: Determine the default value. - Default%[2]v %[3]v = %[4]v -) - -%[1]v` + var Default%[2]v %[3]v = %[4]v + %[1]v` replacementVars := fmt.Sprintf( templateVars, module.PlaceholderParamsVars, @@ -123,15 +119,8 @@ func paramsTypesModify(replacer placeholder.Replacer, opts ParamsOptions) genny. // add param field to the validate method. templateValidation := `// validate%[2]v validates the %[2]v parameter. -func validate%[2]v(v interface{}) error { - %[3]v, ok := v.(%[4]v) - if !ok { - return fmt.Errorf("invalid parameter type: %%T", v) - } - +func validate%[2]v(v %[3]v) error { // TODO implement validation - _ = %[3]v - return nil } @@ -140,7 +129,6 @@ func validate%[2]v(v interface{}) error { templateValidation, module.PlaceholderParamsValidation, param.Name.UpperCamel, - param.Name.LowerCamel, param.DataType(), ) content = replacer.Replace(content, module.PlaceholderParamsValidation, replacementValidation) From d9030ecafbd4292f876a522a190125615ab63e80 Mon Sep 17 00:00:00 2001 From: Pantani Date: Sat, 17 Feb 2024 05:10:11 -0300 Subject: [PATCH 21/35] add replacer pkg for goanalysis --- ignite/pkg/goanalysis/replacer/function.go | 249 +++++++ ignite/pkg/goanalysis/replacer/global.go | 140 ++++ ignite/pkg/goanalysis/replacer/global_test.go | 198 ++++++ ignite/pkg/goanalysis/replacer/replacer.go | 286 ++++++++ .../pkg/goanalysis/replacer/replacer_test.go | 660 ++++++++++++++++++ 5 files changed, 1533 insertions(+) create mode 100644 ignite/pkg/goanalysis/replacer/function.go create mode 100644 ignite/pkg/goanalysis/replacer/global.go create mode 100644 ignite/pkg/goanalysis/replacer/global_test.go create mode 100644 ignite/pkg/goanalysis/replacer/replacer.go create mode 100644 ignite/pkg/goanalysis/replacer/replacer_test.go diff --git a/ignite/pkg/goanalysis/replacer/function.go b/ignite/pkg/goanalysis/replacer/function.go new file mode 100644 index 0000000000..35a8749d85 --- /dev/null +++ b/ignite/pkg/goanalysis/replacer/function.go @@ -0,0 +1,249 @@ +package replacer + +import ( + "bytes" + "fmt" + "go/ast" + "go/format" + "go/parser" + "go/token" + "strings" + + "github.com/ignite/cli/v28/ignite/pkg/errors" +) + +type ( + // functionOpts represent the options for functions. + functionOpts struct { + newParams []param + body string + newLines []line + insideCall []call + appendCode []string + returnVars []string + } + + // FunctionOptions configures code generation. + FunctionOptions func(*functionOpts) + + call struct { + name, code string + } + param struct { + name string + varType string + newLine bool + } + line struct { + code string + number uint64 + } +) + +// AppendParams add a new param value. +func AppendParams(name, varType string, newLine bool) FunctionOptions { + return func(c *functionOpts) { + c.newParams = append(c.newParams, param{ + name: name, + varType: varType, + newLine: newLine, + }) + } +} + +// ReplaceBody replace all body of the function, the method will replace first and apply the other options after. +func ReplaceBody(body string) FunctionOptions { + return func(c *functionOpts) { + c.body = body + } +} + +// AppendCode append code before the end or the return, if exists, of a function in Go source code content. +func AppendCode(code string) FunctionOptions { + return func(c *functionOpts) { + c.appendCode = append(c.appendCode, code) + } +} + +// AppendAtLine append a new code at line. +func AppendAtLine(code string, lineNumber uint64) FunctionOptions { + return func(c *functionOpts) { + c.newLines = append(c.newLines, line{ + code: code, + number: lineNumber, + }) + } +} + +// InsideCall add code inside another function call. For instances, the method have a parameter a +// call 'New(param1, param2)' and we want to add the param3 the result will be 'New(param1, param2, param3)'. +// Or if we have a struct call Params{Param1: param1} and we want to add the param2 the result will +// be Params{Param1: param1, Param2: param2}. +func InsideCall(callName, code string) FunctionOptions { + return func(c *functionOpts) { + c.insideCall = append(c.insideCall, call{ + name: callName, + code: code, + }) + } +} + +// NewReturn replaces return statements in a Go function with a new return statement. +func NewReturn(returnVars ...string) FunctionOptions { + return func(c *functionOpts) { + c.returnVars = append(c.returnVars, returnVars...) + } +} + +func newFunctionOptions() functionOpts { + return functionOpts{ + newParams: make([]param, 0), + body: "", + newLines: make([]line, 0), + appendCode: make([]string, 0), + returnVars: make([]string, 0), + } +} + +func ModifyFunction(fileContent, functionName string, functions ...FunctionOptions) (modifiedContent string, err error) { + // Apply function options. + opts := newFunctionOptions() + for _, o := range functions { + o(&opts) + } + + fileSet := token.NewFileSet() + + // Parse the Go source code content. + f, err := parser.ParseFile(fileSet, "", fileContent, parser.ParseComments) + if err != nil { + return "", err + } + + // Parse the content of the new function into an ast. + var newFunctionBody *ast.BlockStmt + if opts.body != "" { + newFuncContent := fmt.Sprintf("package p; func _() { %s }", strings.TrimSpace(opts.body)) + newContent, err := parser.ParseFile(fileSet, "", newFuncContent, parser.ParseComments) + if err != nil { + return "", err + } + newFunctionBody = newContent.Decls[0].(*ast.FuncDecl).Body + } + + // Parse the content of the append code an ast. + appendCode := make([]ast.Stmt, 0) + for _, codeToInsert := range opts.appendCode { + insertionExpr, err := parser.ParseExpr(codeToInsert) + if err != nil { + return "", err + } + appendCode = append(appendCode, &ast.ExprStmt{X: insertionExpr}) + } + + // Parse the content of the return vars into an ast. + returnStmts := make([]ast.Expr, 0) + for _, returnVar := range opts.returnVars { + // Parse the new return var to expression. + newRetExpr, err := parser.ParseExpr(returnVar) + if err != nil { + return "", err + } + returnStmts = append(returnStmts, newRetExpr) + } + + // Parse the Go code to insert. + var ( + found bool + errInspect error + ) + ast.Inspect(f, func(n ast.Node) bool { + if funcDecl, ok := n.(*ast.FuncDecl); ok { + // Check if the function has the code you want to replace. + if funcDecl.Name.Name == functionName { + for _, param := range opts.newParams { + funcDecl.Type.Params.List = append(funcDecl.Type.Params.List, &ast.Field{ + Names: []*ast.Ident{ast.NewIdent(param.name)}, + Type: &ast.Ident{Name: param.varType}, + }) + } + + if newFunctionBody != nil { + funcDecl.Body = newFunctionBody + } + + // Add the new code at line. + for _, newLine := range opts.newLines { + // Check if the function body has enough lines. + if newLine.number <= uint64(len(funcDecl.Body.List)) { + // Parse the Go code to insert. + insertionExpr, err := parser.ParseExpr(newLine.code) + if err != nil { + errInspect = err + return false + } + // Insert code at the specified line number. + funcDecl.Body.List = append(funcDecl.Body.List[:newLine.number-1], append([]ast.Stmt{&ast.ExprStmt{X: insertionExpr}}, funcDecl.Body.List[newLine.number-1:]...)...) + } + } + + // Check if there is a return statement in the function. + if len(funcDecl.Body.List) > 0 { + // Replace the return statements. + for _, stmt := range funcDecl.Body.List { + if retStmt, ok := stmt.(*ast.ReturnStmt); ok && len(returnStmts) > 0 { + // Remove existing return statements. + retStmt.Results = nil + // Add the new return statement. + retStmt.Results = append(retStmt.Results, returnStmts...) + } + } + + lastStmt := funcDecl.Body.List[len(funcDecl.Body.List)-1] + switch lastStmt.(type) { + case *ast.ReturnStmt: + // If there is a return, insert before it. + appendCode = append(appendCode, lastStmt) + funcDecl.Body.List = append(funcDecl.Body.List[:len(funcDecl.Body.List)-1], appendCode...) + default: + // If there is no return, insert at the end of the function body. + funcDecl.Body.List = append(funcDecl.Body.List, appendCode...) + } + } else { + // If there are no statements in the function body, insert at the end of the function body. + funcDecl.Body.List = append(funcDecl.Body.List, appendCode...) + } + + found = true + return false + } + } + return true + }) + if errInspect != nil { + return "", errInspect + } + if !found { + return "", errors.Errorf("function %s not found in file content", functionName) + } + + if len(opts.insideCall) > 0 { + // Add code inside another function call. + // Implement this logic based on your requirements. + // For simplicity, we'll just add a comment with the inserted code. + for _, call := range opts.insideCall { + targetFunc.Body.List = append(targetFunc.Body.List, &ast.Comment{ + Text: fmt.Sprintf("// Added code inside call '%s': %s", call.name, call.code), + }) + } + } + + // Format the modified AST. + var buf bytes.Buffer + if err := format.Node(&buf, fileSet, f); err != nil { + return "", err + } + + // Return the modified content. + return buf.String(), nil +} diff --git a/ignite/pkg/goanalysis/replacer/global.go b/ignite/pkg/goanalysis/replacer/global.go new file mode 100644 index 0000000000..ff209ec040 --- /dev/null +++ b/ignite/pkg/goanalysis/replacer/global.go @@ -0,0 +1,140 @@ +package replacer + +import ( + "bytes" + "go/ast" + "go/format" + "go/parser" + "go/token" + + "github.com/ignite/cli/v28/ignite/pkg/errors" +) + +type ( + // globalOpts represent the options for globals. + globalOpts struct { + globals []global + } + + // GlobalOptions configures code generation. + GlobalOptions func(*globalOpts) + + global struct { + name, varType, value string + } + + // GlobalType represents the global type. + GlobalType uint8 +) + +const ( + GlobalTypeVar GlobalType = iota + GlobalTypeConst +) + +// WithGlobal add a new global. +func WithGlobal(name, varType, value string) GlobalOptions { + return func(c *globalOpts) { + c.globals = append(c.globals, global{ + name: name, + varType: varType, + value: value, + }) + } +} + +func newGlobalOptions() globalOpts { + return globalOpts{ + globals: make([]global, 0), + } +} + +// InsertGlobal inserts global variables or constants into the provided Go source code content after the import section. +// The function parses the provided content, locates the import section, and inserts the global declarations immediately after it. +// The type of globals (variables or constants) is specified by the globalType parameter. +// Each global declaration is defined by calling WithGlobal function with appropriate arguments. +// The function returns the modified content with the inserted global declarations. +func InsertGlobal(fileContent string, globalType GlobalType, globals ...GlobalOptions) (modifiedContent string, err error) { + // apply global options. + opts := newGlobalOptions() + for _, o := range globals { + o(&opts) + } + + fileSet := token.NewFileSet() + + // Parse the Go source code content. + f, err := parser.ParseFile(fileSet, "", fileContent, parser.ParseComments) + if err != nil { + return "", err + } + + // Find the index of the import declaration or package declaration if no imports. + var insertIndex int + for i, decl := range f.Decls { + if genDecl, ok := decl.(*ast.GenDecl); ok && genDecl.Tok == token.IMPORT { + insertIndex = i + 1 + break + } else if funcDecl, ok := decl.(*ast.FuncDecl); ok { + insertIndex = i + if funcDecl.Doc == nil { + insertIndex++ + } + break + } + } + + // Determine the declaration type based on GlobalType. + var declType string + if globalType == GlobalTypeVar { + declType = "var" + } else if globalType == GlobalTypeConst { + declType = "const" + } else { + return "", errors.Errorf("unsupported global type: %d", globalType) + } + + // Create global variable/constant declarations. + for _, global := range opts.globals { + // Create an identifier for the global. + ident := &ast.Ident{Name: global.name} + + // Create a value expression if provided. + var valueExpr ast.Expr + if global.value != "" { + valueExpr, err = parser.ParseExpr(global.value) + if err != nil { + return "", err + } + } + + // Create a declaration based on the global type. + var spec ast.Spec + if declType == "var" { + spec = &ast.ValueSpec{ + Names: []*ast.Ident{ident}, + Type: ast.NewIdent(global.varType), + Values: []ast.Expr{valueExpr}, + } + } else if declType == "const" { + spec = &ast.ValueSpec{ + Names: []*ast.Ident{ident}, + Type: ast.NewIdent(global.varType), + Values: []ast.Expr{valueExpr}, + } + } + + // Insert the declaration after the import section or package declaration if no imports. + f.Decls = append(f.Decls[:insertIndex], append([]ast.Decl{&ast.GenDecl{Tok: token.Lookup(declType), Specs: []ast.Spec{spec}}}, f.Decls[insertIndex:]...)...) + insertIndex++ + } + + // Format the modified AST. + var buf bytes.Buffer + if err := format.Node(&buf, fileSet, f); err != nil { + return "", err + } + + // Return the modified content. + return buf.String(), nil +} diff --git a/ignite/pkg/goanalysis/replacer/global_test.go b/ignite/pkg/goanalysis/replacer/global_test.go new file mode 100644 index 0000000000..9159642192 --- /dev/null +++ b/ignite/pkg/goanalysis/replacer/global_test.go @@ -0,0 +1,198 @@ +package replacer + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ignite/cli/v28/ignite/pkg/errors" +) + +func TestInsertGlobal(t *testing.T) { + type args struct { + fileContent string + globalType GlobalType + globals []GlobalOptions + } + tests := []struct { + name string + args args + want string + err error + }{ + { + name: "Insert global int var", + args: args{ + fileContent: `package main + +import ( + "fmt" +) + +`, + globalType: GlobalTypeVar, + globals: []GlobalOptions{ + WithGlobal("myIntVar", "int", "42"), + }, + }, + want: `package main + +import ( + "fmt" +) + +var myIntVar int = 42 +`, + }, + { + name: "Insert global int const", + args: args{ + fileContent: `package main + +import ( + "fmt" +) + +`, + globalType: GlobalTypeConst, + globals: []GlobalOptions{ + WithGlobal("myIntConst", "int", "42"), + }, + }, + want: `package main + +import ( + "fmt" +) + +const myIntConst int = 42 +`, + }, + { + name: "Insert string const", + args: args{ + fileContent: `package main + +import ( + "fmt" +) + +`, + globalType: GlobalTypeConst, + globals: []GlobalOptions{ + WithGlobal("myStringConst", "string", `"hello"`), + }, + }, + want: `package main + +import ( + "fmt" +) + +const myStringConst string = "hello" +`, + }, + { + name: "Insert multiples consts", + args: args{ + fileContent: `package main + +import ( + "fmt" +) + +`, + globalType: GlobalTypeConst, + globals: []GlobalOptions{ + WithGlobal("myStringConst", "string", `"hello"`), + WithGlobal("myBoolConst", "bool", "true"), + WithGlobal("myUintConst", "uint64", "40"), + }, + }, + want: `package main + +import ( + "fmt" +) + +const myStringConst string = "hello" +const myBoolConst bool = true +const myUintConst uint64 = 40 +`, + }, + { + name: "Insert global int var with not imports", + args: args{ + fileContent: `package main +`, + globalType: GlobalTypeVar, + globals: []GlobalOptions{ + WithGlobal("myIntVar", "int", "42"), + }, + }, + want: `package main + +var myIntVar int = 42 +`, + }, + { + name: "Insert global int var int an empty file", + args: args{ + fileContent: ``, + globalType: GlobalTypeVar, + globals: []GlobalOptions{ + WithGlobal("myIntVar", "int", "42"), + }, + }, + err: errors.New("1:1: expected 'package', found 'EOF'"), + }, + { + name: "Insert a custom var", + args: args{ + fileContent: `package main`, + globalType: GlobalTypeVar, + globals: []GlobalOptions{ + WithGlobal("fooVar", "foo", "42"), + }, + }, + want: `package main + +var fooVar foo = 42 +`, + }, + { + name: "Insert an invalid var", + args: args{ + fileContent: `package main`, + globalType: GlobalTypeVar, + globals: []GlobalOptions{ + WithGlobal("myInvalidVar", "invalid", "AEF#3fa."), + }, + }, + err: errors.New("1:4: illegal character U+0023 '#'"), + }, + { + name: "Insert an invalid type", + args: args{ + fileContent: `package main`, + globalType: 102, + globals: []GlobalOptions{ + WithGlobal("myInvalidVar", "invalid", "AEF#3fa."), + }, + }, + err: errors.New("unsupported global type: 102"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := InsertGlobal(tt.args.fileContent, tt.args.globalType, tt.args.globals...) + if tt.err != nil { + require.Error(t, err) + require.Equal(t, tt.err.Error(), err.Error()) + return + } + require.NoError(t, err) + require.Equal(t, tt.want, got) + }) + } +} diff --git a/ignite/pkg/goanalysis/replacer/replacer.go b/ignite/pkg/goanalysis/replacer/replacer.go new file mode 100644 index 0000000000..ead50d898e --- /dev/null +++ b/ignite/pkg/goanalysis/replacer/replacer.go @@ -0,0 +1,286 @@ +package replacer + +import ( + "bytes" + "fmt" + "go/ast" + "go/format" + "go/parser" + "go/token" + "strings" + + "github.com/ignite/cli/v28/ignite/pkg/errors" +) + +// AppendFunction appends a new function to the end of the Go source code content. +func AppendFunction(fileContent string, function string) (modifiedContent string, err error) { + fileSet := token.NewFileSet() + + // Parse the function body as a separate file. + funcFile, err := parser.ParseFile(fileSet, "", "package main\n"+function, parser.AllErrors) + if err != nil { + return "", err + } + + // Extract the first declaration, assuming it's a function declaration. + var funcDecl *ast.FuncDecl + for _, decl := range funcFile.Decls { + if fDecl, ok := decl.(*ast.FuncDecl); ok { + funcDecl = fDecl + break + } + } + if funcDecl == nil { + return "", errors.Errorf("no function declaration found in the provided function body") + } + + // Parse the Go source code content. + f, err := parser.ParseFile(fileSet, "", fileContent, parser.ParseComments) + if err != nil { + return "", err + } + + // Append the function declaration to the file's declarations. + f.Decls = append(f.Decls, funcDecl) + + // Format the modified AST. + var buf bytes.Buffer + if err := format.Node(&buf, fileSet, f); err != nil { + return "", err + } + + return buf.String(), nil +} + +// AppendImports appends import statements to the existing import block in Go source code content. +func AppendImports(fileContent string, importStatements ...string) (modifiedContent string, err error) { + fileSet := token.NewFileSet() + + // Parse the Go source code content. + f, err := parser.ParseFile(fileSet, "", fileContent, parser.ParseComments) + if err != nil { + return "", err + } + + // Find the existing import declaration. + var importDecl *ast.GenDecl + for _, decl := range f.Decls { + genDecl, ok := decl.(*ast.GenDecl) + if !ok || genDecl.Tok != token.IMPORT || len(genDecl.Specs) == 0 { + continue + } + importDecl = genDecl + break + } + + if importDecl == nil { + // If no existing import declaration found, create a new one. + importDecl = &ast.GenDecl{ + Tok: token.IMPORT, + Specs: make([]ast.Spec, 0), + } + f.Decls = append([]ast.Decl{importDecl}, f.Decls...) + } + + // Check existing imports to avoid duplicates. + existImports := make(map[string]struct{}) + for _, spec := range importDecl.Specs { + importSpec, ok := spec.(*ast.ImportSpec) + if !ok { + continue + } + existImports[importSpec.Path.Value] = struct{}{} + } + + // Add new import statements. + for _, importStatement := range importStatements { + impSplit := strings.Split(importStatement, " ") + var ( + importRepo = impSplit[len(impSplit)-1] + importname = "" + ) + if len(impSplit) > 1 { + importname = impSplit[0] + } + + // Check if the import already exists. + if _, ok := existImports[`"`+importRepo+`"`]; ok { + continue + } + // Create a new import spec. + spec := &ast.ImportSpec{ + Name: &ast.Ident{ + Name: importname, + }, + Path: &ast.BasicLit{ + Kind: token.STRING, + Value: `"` + importRepo + `"`, + }, + } + importDecl.Specs = append(importDecl.Specs, spec) + } + + // Format the modified AST. + var buf bytes.Buffer + if err := format.Node(&buf, fileSet, f); err != nil { + return "", err + } + + return buf.String(), nil +} + +// AppendCodeToFunction inserts code before the end or the return, if exists, of a function in Go source code content. +func AppendCodeToFunction(fileContent, functionName, codeToInsert string) (modifiedContent string, err error) { + fileSet := token.NewFileSet() + + // Parse the Go source code content. + f, err := parser.ParseFile(fileSet, "", fileContent, parser.ParseComments) + if err != nil { + return "", err + } + + // Parse the Go code to insert. + insertionExpr, err := parser.ParseExpr(codeToInsert) + if err != nil { + return "", err + } + + found := false + ast.Inspect(f, func(n ast.Node) bool { + if funcDecl, ok := n.(*ast.FuncDecl); ok { + // Check if the function has the code you want to replace. + if funcDecl.Name.Name == functionName { + // Check if there is a return statement in the function. + if len(funcDecl.Body.List) > 0 { + lastStmt := funcDecl.Body.List[len(funcDecl.Body.List)-1] + switch lastStmt.(type) { + case *ast.ReturnStmt: + // If there is a return, insert before it. + funcDecl.Body.List = append(funcDecl.Body.List[:len(funcDecl.Body.List)-1], &ast.ExprStmt{X: insertionExpr}, lastStmt) + default: + // If there is no return, insert at the end of the function body. + funcDecl.Body.List = append(funcDecl.Body.List, &ast.ExprStmt{X: insertionExpr}) + } + } else { + // If there are no statements in the function body, insert at the end of the function body. + funcDecl.Body.List = append(funcDecl.Body.List, &ast.ExprStmt{X: insertionExpr}) + } + found = true + return false + } + } + return true + }) + + if !found { + return "", errors.Errorf("function %s not found", functionName) + } + + // Write the modified AST to a buffer. + var buf bytes.Buffer + if err := format.Node(&buf, fileSet, f); err != nil { + return "", err + } + + return buf.String(), nil +} + +// ReplaceReturnVars replaces return statements in a Go function with a new return statement. +func ReplaceReturnVars(fileContent, functionName string, returnVars ...string) (string, error) { + fileSet := token.NewFileSet() + + // Parse the Go source code content. + f, err := parser.ParseFile(fileSet, "", fileContent, parser.ParseComments) + if err != nil { + return "", err + } + + returnStmts := make([]ast.Expr, 0) + for _, returnVar := range returnVars { + // Parse the new return var to expression. + newRetExpr, err := parser.ParseExpr(returnVar) + if err != nil { + return "", err + } + returnStmts = append(returnStmts, newRetExpr) + } + + found := false + ast.Inspect(f, func(n ast.Node) bool { + if funcDecl, ok := n.(*ast.FuncDecl); ok { + // Check if the function has the code you want to replace. + if funcDecl.Name.Name == functionName { + // Replace the return statements. + for _, stmt := range funcDecl.Body.List { + if retStmt, ok := stmt.(*ast.ReturnStmt); ok { + // Remove existing return statements. + retStmt.Results = nil + // Add the new return statement. + retStmt.Results = append(retStmt.Results, returnStmts...) + } + } + found = true + return false + } + } + return true + }) + + if !found { + return "", errors.Errorf("function %s not found", functionName) + } + + // Write the modified AST to a buffer. + var buf bytes.Buffer + if err := format.Node(&buf, fileSet, f); err != nil { + return "", err + } + + return buf.String(), nil +} + +// ReplaceFunctionContent replaces a function implementation content in Go source code. +func ReplaceFunctionContent(fileContent, oldFunctionName, newFunction string) (modifiedContent string, err error) { + fileSet := token.NewFileSet() + + // Parse the Go source code content. + f, err := parser.ParseFile(fileSet, "", fileContent, parser.ParseComments) + if err != nil { + return "", err + } + + // Parse the content of the new function into an ast.File. + newFuncContent := fmt.Sprintf("package p; func _() { %s }", strings.TrimSpace(newFunction)) + newFile, err := parser.ParseFile(fileSet, "", newFuncContent, parser.ParseComments) + if err != nil { + return "", err + } + + found := false + ast.Inspect(f, func(n ast.Node) bool { + if funcDecl, ok := n.(*ast.FuncDecl); ok { + // Check if the function has the code you want to replace. + if funcDecl.Name.Name == oldFunctionName { + // Take the body of the new function from the parsed file. + newFunctionBody := newFile.Decls[0].(*ast.FuncDecl).Body + // Replace the function body with the body of the new function. + funcDecl.Body = newFunctionBody + found = true + return false + } + } + return true + }) + + if !found { + return "", errors.Errorf("function %s not found in file content", oldFunctionName) + } + + // Write the modified AST to a buffer. + var buf bytes.Buffer + if err := format.Node(&buf, fileSet, f); err != nil { + return "", err + } + + return buf.String(), nil +} diff --git a/ignite/pkg/goanalysis/replacer/replacer_test.go b/ignite/pkg/goanalysis/replacer/replacer_test.go new file mode 100644 index 0000000000..1f5da387f3 --- /dev/null +++ b/ignite/pkg/goanalysis/replacer/replacer_test.go @@ -0,0 +1,660 @@ +package replacer + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ignite/cli/v28/ignite/pkg/errors" +) + +func TestAppendFunction(t *testing.T) { + type args struct { + fileContent string + function string + } + tests := []struct { + name string + args args + want string + err error + }{ + { + name: "Append a function after the package declaration", + args: args{ + fileContent: `package main`, + function: `func add(a, b int) int { + return a + b +}`, + }, + want: `package main + +func add(a, b int) int { + return a + b +} +`, + }, + { + name: "Append a function after a var", + args: args{ + fileContent: `package main + +import ( + "fmt" +) + +var myIntVar int = 42 +`, + function: `func add(a, b int) int { + return a + b +}`, + }, + want: `package main + +import ( + "fmt" +) + +var myIntVar int = 42 + +func add(a, b int) int { + return a + b +} +`, + }, + { + name: "Append a function after the import", + args: args{ + fileContent: `package main + +import ( + "fmt" +) +`, + function: `func add(a, b int) int { + return a + b +}`, + }, + want: `package main + +import ( + "fmt" +) + +func add(a, b int) int { + return a + b +} +`, + }, + { + name: "Append a function after another function", + args: args{ + fileContent: `package main + +import ( + "fmt" +) + +var myIntVar int = 42 + +func myFunction() int { + return 42 +} +`, + function: `func add(a, b int) int { + return a + b +}`, + }, + want: `package main + +import ( + "fmt" +) + +var myIntVar int = 42 + +func myFunction() int { + return 42 +} +func add(a, b int) int { + return a + b +} +`, + }, + { + name: "Append a function in an empty file", + args: args{ + fileContent: ``, + function: `func add(a, b int) int { + return a + b +}`, + }, + err: errors.New("1:1: expected 'package', found 'EOF'"), + }, + { + name: "Append a empty function", + args: args{ + fileContent: `package main`, + function: ``, + }, + err: errors.New("no function declaration found in the provided function body"), + }, + { + name: "Append an invalid function", + args: args{ + fileContent: `package main`, + function: `@,.l.e,`, + }, + err: errors.New("2:1: illegal character U+0040 '@'"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := AppendFunction(tt.args.fileContent, tt.args.function) + if tt.err != nil { + require.Error(t, err) + require.Equal(t, tt.err.Error(), err.Error()) + return + } + require.NoError(t, err) + require.Equal(t, tt.want, got) + }) + } +} + +func TestAppendImports(t *testing.T) { + existingContent := `package main + +import ( + "fmt" +) + +func main() { + fmt.Println("Hello, world!") +}` + + type args struct { + fileContent string + importStatements []string + } + tests := []struct { + name string + args args + want string + err error + }{ + { + name: "Add single import statement", + args: args{ + fileContent: existingContent, + importStatements: []string{"strings"}, + }, + want: `package main + +import ( + "fmt" + "strings" +) + +func main() { + fmt.Println("Hello, world!") +} +`, + err: nil, + }, + { + name: "Add multiple import statements", + args: args{ + fileContent: existingContent, + importStatements: []string{"st strings", "strconv", "os"}, + }, + want: `package main + +import ( + "fmt" + "os" + "strconv" + st "strings" +) + +func main() { + fmt.Println("Hello, world!") +} +`, + err: nil, + }, + { + name: "Add multiple import statements with an existing one", + args: args{ + fileContent: existingContent, + importStatements: []string{"st strings", "strconv", "os", "fmt"}, + }, + want: `package main + +import ( + "fmt" + "os" + "strconv" + st "strings" +) + +func main() { + fmt.Println("Hello, world!") +} +`, + err: nil, + }, + { + name: "Add duplicate import statement", + args: args{ + fileContent: existingContent, + importStatements: []string{"fmt"}, + }, + want: existingContent + "\n", + err: nil, + }, + { + name: "No import statement", + args: args{ + fileContent: `package main + +func main() { + fmt.Println("Hello, world!") +}`, + importStatements: []string{"fmt"}, + }, + want: `package main + +import "fmt" + +func main() { + fmt.Println("Hello, world!") +} +`, + err: nil, + }, + { + name: "No import statement and add two imports", + args: args{ + fileContent: `package main + +func main() { + fmt.Println("Hello, world!") +}`, + importStatements: []string{"fmt", "os"}, + }, + want: `package main + +import ( + "fmt" + "os" +) + +func main() { + fmt.Println("Hello, world!") +} +`, + err: nil, + }, + { + name: "Add invalid import statement", + args: args{ + fileContent: existingContent, + importStatements: []string{"fmt\""}, + }, + err: errors.New("format.Node internal error (5:8: string literal not terminated (and 1 more errors))"), + }, + { + name: "Add empty file content", + args: args{ + fileContent: "", + importStatements: []string{"fmt"}, + }, + err: errors.New("1:1: expected 'package', found 'EOF'"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := AppendImports(tt.args.fileContent, tt.args.importStatements...) + if tt.err != nil { + require.Error(t, err) + require.Equal(t, tt.err.Error(), err.Error()) + return + } + require.NoError(t, err) + require.Equal(t, tt.want, got) + }) + } +} + +func TestAppendCode(t *testing.T) { + existingContent := `package main + +import ( + "fmt" +) + +func main() { + fmt.Println("Hello, world!") +} + +func anotherFunction() bool { + // Some code here + fmt.Println("Another function") + return true +}` + + type args struct { + fileContent string + functionName string + codeToInsert string + } + tests := []struct { + name string + args args + want string + err error + }{ + { + name: "Append code to the end of the function", + args: args{ + fileContent: existingContent, + functionName: "main", + codeToInsert: "fmt.Println(\"Inserted code here\")", + }, + want: `package main + +import ( + "fmt" +) + +func main() { + fmt.Println("Hello, world!") + fmt.Println("Inserted code here") + +} + +func anotherFunction() bool { + // Some code here + fmt.Println("Another function") + return true +} +`, + }, + { + name: "Append code with return statement", + args: args{ + fileContent: existingContent, + functionName: "anotherFunction", + codeToInsert: "fmt.Println(\"Inserted code here\")", + }, + want: `package main + +import ( + "fmt" +) + +func main() { + fmt.Println("Hello, world!") +} + +func anotherFunction() bool { + // Some code here + fmt.Println("Another function") + fmt.Println("Inserted code here") + + return true +} +`, + }, + { + name: "Function not found", + args: args{ + fileContent: existingContent, + functionName: "nonexistentFunction", + codeToInsert: "fmt.Println(\"Inserted code here\")", + }, + err: errors.New("function nonexistentFunction not found"), + }, + { + name: "Invalid code", + args: args{ + fileContent: existingContent, + functionName: "anotherFunction", + codeToInsert: "%#)(u309f/..\"", + }, + err: errors.New("1:1: expected operand, found '%' (and 2 more errors)"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := AppendCodeToFunction(tt.args.fileContent, tt.args.functionName, tt.args.codeToInsert) + if tt.err != nil { + require.Error(t, err) + require.Equal(t, tt.err.Error(), err.Error()) + return + } + require.NoError(t, err) + require.Equal(t, tt.want, got) + }) + } +} + +func TestReplaceReturn(t *testing.T) { + existingContent := `package main + +import ( + "fmt" +) + +func main() { + x := calculate() + fmt.Println("Result:", x) +} + +func calculate() int { + return 42 +}` + + type args struct { + fileContent string + functionName string + returnVars []string + } + tests := []struct { + name string + args args + want string + err error + }{ + { + name: "Replace return statement with a single variable", + args: args{ + fileContent: existingContent, + functionName: "calculate", + returnVars: []string{"result"}, + }, + want: `package main + +import ( + "fmt" +) + +func main() { + x := calculate() + fmt.Println("Result:", x) +} + +func calculate() int { + return result + +} +`, + }, + { + name: "Replace return statement with multiple variables", + args: args{ + fileContent: existingContent, + functionName: "calculate", + returnVars: []string{"result", "err"}, + }, + want: `package main + +import ( + "fmt" +) + +func main() { + x := calculate() + fmt.Println("Result:", x) +} + +func calculate() int { + return result, err + +} +`, + }, + { + name: "Function not found", + args: args{ + fileContent: existingContent, + functionName: "nonexistentFunction", + returnVars: []string{"result"}, + }, + err: errors.New("function nonexistentFunction not found"), + }, + { + name: "Invalid result", + args: args{ + fileContent: existingContent, + functionName: "nonexistentFunction", + returnVars: []string{"ae@@of..!\""}, + }, + err: errors.New("1:3: illegal character U+0040 '@' (and 1 more errors)"), + }, + { + name: "Reserved word", + args: args{ + fileContent: existingContent, + functionName: "nonexistentFunction", + returnVars: []string{"range"}, + }, + err: errors.New("1:1: expected operand, found 'range'"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ReplaceReturnVars(tt.args.fileContent, tt.args.functionName, tt.args.returnVars...) + if tt.err != nil { + require.Error(t, err) + require.Equal(t, tt.err.Error(), err.Error()) + return + } + require.NoError(t, err) + require.Equal(t, tt.want, got) + }) + } +} + +func TestReplaceCode(t *testing.T) { + var ( + newFunction = `fmt.Println("This is the new function.")` + existingContent = `package main + +import ( + "fmt" +) + +func main() { + fmt.Println("Hello, world!") +} + +func oldFunction() { + fmt.Println("This is the old function.") +}` + ) + + type args struct { + fileContent string + oldFunctionName string + newFunction string + } + tests := []struct { + name string + args args + want string + err error + }{ + { + name: "Replace function implementation", + args: args{ + fileContent: existingContent, + oldFunctionName: "oldFunction", + newFunction: newFunction, + }, + want: `package main + +import ( + "fmt" +) + +func main() { + fmt.Println("Hello, world!") +} + +func oldFunction() { fmt.Println("This is the new function.") } +`, + }, + { + name: "Replace main function implementation", + args: args{ + fileContent: existingContent, + oldFunctionName: "main", + newFunction: newFunction, + }, + want: `package main + +import ( + "fmt" +) + +func main() { fmt.Println("This is the new function.") } + +func oldFunction() { + fmt.Println("This is the old function.") +} +`, + }, + { + name: "Function not found", + args: args{ + fileContent: existingContent, + oldFunctionName: "nonexistentFunction", + newFunction: newFunction, + }, + err: errors.New("function nonexistentFunction not found in file content"), + }, + { + name: "Invalid new function", + args: args{ + fileContent: existingContent, + oldFunctionName: "nonexistentFunction", + newFunction: "ae@@of..!\"", + }, + err: errors.New("1:25: illegal character U+0040 '@' (and 2 more errors)"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ReplaceFunctionContent(tt.args.fileContent, tt.args.oldFunctionName, tt.args.newFunction) + if tt.err != nil { + require.Error(t, err) + require.Equal(t, tt.err.Error(), err.Error()) + return + } + require.NoError(t, err) + require.Equal(t, tt.want, got) + }) + } +} From 272986e9b4b03a2d44361c1d19ab2ee4e3114cb3 Mon Sep 17 00:00:00 2001 From: Pantani Date: Mon, 19 Feb 2024 10:04:57 -0300 Subject: [PATCH 22/35] add TODO comment --- ignite/pkg/goanalysis/replacer/replacer.go | 1 + 1 file changed, 1 insertion(+) diff --git a/ignite/pkg/goanalysis/replacer/replacer.go b/ignite/pkg/goanalysis/replacer/replacer.go index ead50d898e..fdc1d4940a 100644 --- a/ignite/pkg/goanalysis/replacer/replacer.go +++ b/ignite/pkg/goanalysis/replacer/replacer.go @@ -53,6 +53,7 @@ func AppendFunction(fileContent string, function string) (modifiedContent string } // AppendImports appends import statements to the existing import block in Go source code content. +// TODO add options to add import in the end, begin or a specific line func AppendImports(fileContent string, importStatements ...string) (modifiedContent string, err error) { fileSet := token.NewFileSet() From 8c8499f522719f214856cb710c25568f1572f04f Mon Sep 17 00:00:00 2001 From: Pantani Date: Mon, 19 Feb 2024 18:36:21 -0300 Subject: [PATCH 23/35] add modify call function --- ignite/pkg/goanalysis/replacer/function.go | 165 +++++++++++------- ignite/pkg/goanalysis/replacer/replacer.go | 73 ++++++++ .../pkg/goanalysis/replacer/replacer_test.go | 128 ++++++++++++++ 3 files changed, 302 insertions(+), 64 deletions(-) diff --git a/ignite/pkg/goanalysis/replacer/function.go b/ignite/pkg/goanalysis/replacer/function.go index 35a8749d85..997410d67b 100644 --- a/ignite/pkg/goanalysis/replacer/function.go +++ b/ignite/pkg/goanalysis/replacer/function.go @@ -7,6 +7,7 @@ import ( "go/format" "go/parser" "go/token" + "strconv" "strings" "github.com/ignite/cli/v28/ignite/pkg/errors" @@ -27,7 +28,9 @@ type ( FunctionOptions func(*functionOpts) call struct { - name, code string + name string + code string + index int } param struct { name string @@ -152,73 +155,118 @@ func ModifyFunction(fileContent, functionName string, functions ...FunctionOptio returnStmts = append(returnStmts, newRetExpr) } + callMap := make(map[string]call) + for _, call := range opts.insideCall { + callMap[call.name] = call + } + // Parse the Go code to insert. var ( found bool errInspect error ) ast.Inspect(f, func(n ast.Node) bool { - if funcDecl, ok := n.(*ast.FuncDecl); ok { - // Check if the function has the code you want to replace. - if funcDecl.Name.Name == functionName { - for _, param := range opts.newParams { - funcDecl.Type.Params.List = append(funcDecl.Type.Params.List, &ast.Field{ - Names: []*ast.Ident{ast.NewIdent(param.name)}, - Type: &ast.Ident{Name: param.varType}, - }) - } + funcDecl, ok := n.(*ast.FuncDecl) + if !ok || funcDecl.Name.Name != functionName { + return true + } - if newFunctionBody != nil { - funcDecl.Body = newFunctionBody - } + for _, param := range opts.newParams { + funcDecl.Type.Params.List = append(funcDecl.Type.Params.List, &ast.Field{ + Names: []*ast.Ident{ast.NewIdent(param.name)}, + Type: &ast.Ident{Name: param.varType}, + }) + } - // Add the new code at line. - for _, newLine := range opts.newLines { - // Check if the function body has enough lines. - if newLine.number <= uint64(len(funcDecl.Body.List)) { - // Parse the Go code to insert. - insertionExpr, err := parser.ParseExpr(newLine.code) - if err != nil { - errInspect = err - return false - } - // Insert code at the specified line number. - funcDecl.Body.List = append(funcDecl.Body.List[:newLine.number-1], append([]ast.Stmt{&ast.ExprStmt{X: insertionExpr}}, funcDecl.Body.List[newLine.number-1:]...)...) - } - } + // Check if the function has the code you want to replace. + if newFunctionBody != nil { + funcDecl.Body = newFunctionBody + } - // Check if there is a return statement in the function. - if len(funcDecl.Body.List) > 0 { - // Replace the return statements. - for _, stmt := range funcDecl.Body.List { - if retStmt, ok := stmt.(*ast.ReturnStmt); ok && len(returnStmts) > 0 { - // Remove existing return statements. - retStmt.Results = nil - // Add the new return statement. - retStmt.Results = append(retStmt.Results, returnStmts...) - } - } - - lastStmt := funcDecl.Body.List[len(funcDecl.Body.List)-1] - switch lastStmt.(type) { - case *ast.ReturnStmt: - // If there is a return, insert before it. - appendCode = append(appendCode, lastStmt) - funcDecl.Body.List = append(funcDecl.Body.List[:len(funcDecl.Body.List)-1], appendCode...) - default: - // If there is no return, insert at the end of the function body. - funcDecl.Body.List = append(funcDecl.Body.List, appendCode...) - } + ast.Inspect(funcDecl, func(n ast.Node) bool { + callExpr, ok := n.(*ast.CallExpr) + if !ok { + return true + } + // Check if the call expression matches the function call name + var c call + ident, ok := callExpr.Fun.(*ast.Ident) + if ok { + c = callMap[ident.Name] + } else { + selector, ok := callExpr.Fun.(*ast.SelectorExpr) + if ok { + c = callMap[selector.Sel.Name] } else { - // If there are no statements in the function body, insert at the end of the function body. - funcDecl.Body.List = append(funcDecl.Body.List, appendCode...) + return true } + } + // Construct the new argument to be added + newArg := &ast.BasicLit{ + Kind: token.STRING, + Value: strconv.Quote(c.code), + } + switch { + case c.index == -1: + // Append the new argument to the end + callExpr.Args = append(callExpr.Args, newArg) + found = true + case c.index >= 0 && c.index <= len(callExpr.Args): + // Insert the new argument at the specified index + callExpr.Args = append(callExpr.Args[:c.index], append([]ast.Expr{newArg}, callExpr.Args[c.index:]...)...) found = true - return false + default: + errInspect = fmt.Errorf("index out of range") + return false // Stop the inspection, an error occurred + } + return true // Continue the inspection for duplicated calls + }) + + // Add the new code at line. + for _, newLine := range opts.newLines { + // Check if the function body has enough lines. + if newLine.number <= uint64(len(funcDecl.Body.List)) { + // Parse the Go code to insert. + insertionExpr, err := parser.ParseExpr(newLine.code) + if err != nil { + errInspect = err + return false + } + // Insert code at the specified line number. + funcDecl.Body.List = append(funcDecl.Body.List[:newLine.number-1], append([]ast.Stmt{&ast.ExprStmt{X: insertionExpr}}, funcDecl.Body.List[newLine.number-1:]...)...) + } + } + + // Check if there is a return statement in the function. + if len(funcDecl.Body.List) > 0 { + // Replace the return statements. + for _, stmt := range funcDecl.Body.List { + if retStmt, ok := stmt.(*ast.ReturnStmt); ok && len(returnStmts) > 0 { + // Remove existing return statements. + retStmt.Results = nil + // Add the new return statement. + retStmt.Results = append(retStmt.Results, returnStmts...) + } } + + lastStmt := funcDecl.Body.List[len(funcDecl.Body.List)-1] + switch lastStmt.(type) { + case *ast.ReturnStmt: + // If there is a return, insert before it. + appendCode = append(appendCode, lastStmt) + funcDecl.Body.List = append(funcDecl.Body.List[:len(funcDecl.Body.List)-1], appendCode...) + default: + // If there is no return, insert at the end of the function body. + funcDecl.Body.List = append(funcDecl.Body.List, appendCode...) + } + } else { + // If there are no statements in the function body, insert at the end of the function body. + funcDecl.Body.List = append(funcDecl.Body.List, appendCode...) } - return true + + found = true + return false }) if errInspect != nil { return "", errInspect @@ -227,17 +275,6 @@ func ModifyFunction(fileContent, functionName string, functions ...FunctionOptio return "", errors.Errorf("function %s not found in file content", functionName) } - if len(opts.insideCall) > 0 { - // Add code inside another function call. - // Implement this logic based on your requirements. - // For simplicity, we'll just add a comment with the inserted code. - for _, call := range opts.insideCall { - targetFunc.Body.List = append(targetFunc.Body.List, &ast.Comment{ - Text: fmt.Sprintf("// Added code inside call '%s': %s", call.name, call.code), - }) - } - } - // Format the modified AST. var buf bytes.Buffer if err := format.Node(&buf, fileSet, f); err != nil { diff --git a/ignite/pkg/goanalysis/replacer/replacer.go b/ignite/pkg/goanalysis/replacer/replacer.go index fdc1d4940a..120ccb2c2e 100644 --- a/ignite/pkg/goanalysis/replacer/replacer.go +++ b/ignite/pkg/goanalysis/replacer/replacer.go @@ -7,6 +7,7 @@ import ( "go/format" "go/parser" "go/token" + "strconv" "strings" "github.com/ignite/cli/v28/ignite/pkg/errors" @@ -285,3 +286,75 @@ func ReplaceFunctionContent(fileContent, oldFunctionName, newFunction string) (m return buf.String(), nil } + +// AppendParamToFunctionCall inserts a parameter to a function call inside a function in Go source code content. +func AppendParamToFunctionCall(fileContent, functionName, functionCallName, paramToAdd string, index int) (modifiedContent string, err error) { + fileSet := token.NewFileSet() + + // Parse the Go source code content. + f, err := parser.ParseFile(fileSet, "", fileContent, parser.ParseComments) + if err != nil { + return "", err + } + + var ( + found bool + errInspect error + ) + ast.Inspect(f, func(n ast.Node) bool { + if funcDecl, ok := n.(*ast.FuncDecl); ok { + // Check if the function has the name you want to replace. + if funcDecl.Name.Name == functionName { + ast.Inspect(funcDecl, func(n ast.Node) bool { + callExpr, ok := n.(*ast.CallExpr) + if !ok { + return true + } + // Check if the call expression matches the function call name + ident, ok := callExpr.Fun.(*ast.Ident) + if !ok || ident.Name != functionCallName { + selector, ok := callExpr.Fun.(*ast.SelectorExpr) + if !ok || selector.Sel.Name != functionCallName { + return true + } + } + // Construct the new argument to be added + newArg := &ast.BasicLit{ + Kind: token.STRING, + Value: strconv.Quote(paramToAdd), + } + switch { + case index == -1: + // Append the new argument to the end + callExpr.Args = append(callExpr.Args, newArg) + found = true + case index >= 0 && index <= len(callExpr.Args): + // Insert the new argument at the specified index + callExpr.Args = append(callExpr.Args[:index], append([]ast.Expr{newArg}, callExpr.Args[index:]...)...) + found = true + default: + errInspect = fmt.Errorf("index out of range") + return false // Stop the inspection, an error occurred + } + return true // Continue the inspection for duplicated calls + }) + return false // Stop the inspection, we found what we needed + } + } + return true // Continue inspecting + }) + if errInspect != nil { + return "", errInspect + } + if !found { + return "", fmt.Errorf("function %s not found or no calls to %s inside the function", functionName, functionCallName) + } + + // Write the modified AST to a buffer. + var buf bytes.Buffer + if err := format.Node(&buf, fileSet, f); err != nil { + return "", err + } + + return buf.String(), nil +} diff --git a/ignite/pkg/goanalysis/replacer/replacer_test.go b/ignite/pkg/goanalysis/replacer/replacer_test.go index 1f5da387f3..11b184cadd 100644 --- a/ignite/pkg/goanalysis/replacer/replacer_test.go +++ b/ignite/pkg/goanalysis/replacer/replacer_test.go @@ -658,3 +658,131 @@ func oldFunction() { }) } } + +func TestAppendParamToFunctionCall(t *testing.T) { + tests := []struct { + name string + fileContent string + functionName string + functionCallName string + index int + paramToAdd string + want string + err error + }{ + { + name: "add new parameter to index 1 in a function extension", + fileContent: `package main + +func myFunction() { + p := NewParam() + p.New("param1", "param2") +}`, + functionName: "myFunction", + functionCallName: "New", + paramToAdd: "param3", + index: 1, + want: `package main + +func myFunction() { + p := NewParam() + p.New("param1", "param3", "param2") +} +`, + }, + { + name: "add new parameter to index 1", + fileContent: `package main + +func myFunction() { + New("param1", "param2") +}`, + functionName: "myFunction", + functionCallName: "New", + paramToAdd: "param3", + index: 1, + want: `package main + +func myFunction() { + New("param1", "param3", "param2") +} +`, + }, + { + name: "add a new parameter for two functions", + fileContent: `package main + +func myFunction() { + New("param1", "param2") + New("param1", "param2", "param4") +}`, + functionName: "myFunction", + functionCallName: "New", + index: -1, + paramToAdd: "param3", + want: `package main + +func myFunction() { + New("param1", "param2", "param3") + New("param1", "param2", "param4", "param3") +} +`, + }, + { + name: "FunctionNotFound", + fileContent: `package main + +func anotherFunction() { + New("param1", "param2") +}`, + functionName: "myFunction", + functionCallName: "New", + paramToAdd: "param3", + index: 1, + err: errors.Errorf("function myFunction not found or no calls to New inside the function"), + }, + { + name: "FunctionCallNotFound", + fileContent: `package main + +func myFunction() { + AnotherFunction("param1", "param2") +}`, + functionName: "myFunction", + functionCallName: "New", + paramToAdd: "param3", + index: 1, + want: `package main + +func myFunction() { + AnotherFunction("param1", "param2") +}`, + err: errors.Errorf("function myFunction not found or no calls to New inside the function"), + }, + { + name: "IndexOutOfRange", + fileContent: `package main + +func myFunction() { + New("param1", "param2") +}`, + functionName: "myFunction", + functionCallName: "New", + paramToAdd: "param3", + index: 3, + err: errors.Errorf("index out of range"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := AppendParamToFunctionCall(tt.fileContent, tt.functionName, tt.functionCallName, tt.paramToAdd, tt.index) + if tt.err != nil { + require.Error(t, err) + require.Equal(t, tt.err.Error(), err.Error()) + return + } + require.NoError(t, err) + require.Equal(t, tt.want, got) + }) + } +} From c14f9c5a0a2b5ef55b76e523c3d2d3006939d32c Mon Sep 17 00:00:00 2001 From: Pantani Date: Mon, 19 Feb 2024 18:38:43 -0300 Subject: [PATCH 24/35] test readbility --- .../pkg/goanalysis/replacer/replacer_test.go | 95 +++++++++++-------- 1 file changed, 58 insertions(+), 37 deletions(-) diff --git a/ignite/pkg/goanalysis/replacer/replacer_test.go b/ignite/pkg/goanalysis/replacer/replacer_test.go index 11b184cadd..150c123790 100644 --- a/ignite/pkg/goanalysis/replacer/replacer_test.go +++ b/ignite/pkg/goanalysis/replacer/replacer_test.go @@ -660,28 +660,33 @@ func oldFunction() { } func TestAppendParamToFunctionCall(t *testing.T) { - tests := []struct { - name string + type args struct { fileContent string functionName string functionCallName string index int paramToAdd string - want string - err error + } + tests := []struct { + name string + args args + want string + err error }{ { name: "add new parameter to index 1 in a function extension", - fileContent: `package main + args: args{ + fileContent: `package main func myFunction() { p := NewParam() p.New("param1", "param2") }`, - functionName: "myFunction", - functionCallName: "New", - paramToAdd: "param3", - index: 1, + functionName: "myFunction", + functionCallName: "New", + paramToAdd: "param3", + index: 1, + }, want: `package main func myFunction() { @@ -692,15 +697,17 @@ func myFunction() { }, { name: "add new parameter to index 1", - fileContent: `package main + args: args{ + fileContent: `package main func myFunction() { New("param1", "param2") }`, - functionName: "myFunction", - functionCallName: "New", - paramToAdd: "param3", - index: 1, + functionName: "myFunction", + functionCallName: "New", + paramToAdd: "param3", + index: 1, + }, want: `package main func myFunction() { @@ -710,16 +717,18 @@ func myFunction() { }, { name: "add a new parameter for two functions", - fileContent: `package main + args: args{ + fileContent: `package main func myFunction() { New("param1", "param2") New("param1", "param2", "param4") }`, - functionName: "myFunction", - functionCallName: "New", - index: -1, - paramToAdd: "param3", + functionName: "myFunction", + functionCallName: "New", + index: -1, + paramToAdd: "param3", + }, want: `package main func myFunction() { @@ -730,28 +739,32 @@ func myFunction() { }, { name: "FunctionNotFound", - fileContent: `package main + args: args{ + fileContent: `package main func anotherFunction() { New("param1", "param2") }`, - functionName: "myFunction", - functionCallName: "New", - paramToAdd: "param3", - index: 1, - err: errors.Errorf("function myFunction not found or no calls to New inside the function"), + functionName: "myFunction", + functionCallName: "New", + paramToAdd: "param3", + index: 1, + }, + err: errors.Errorf("function myFunction not found or no calls to New inside the function"), }, { name: "FunctionCallNotFound", - fileContent: `package main + args: args{ + fileContent: `package main func myFunction() { AnotherFunction("param1", "param2") }`, - functionName: "myFunction", - functionCallName: "New", - paramToAdd: "param3", - index: 1, + functionName: "myFunction", + functionCallName: "New", + paramToAdd: "param3", + index: 1, + }, want: `package main func myFunction() { @@ -761,21 +774,29 @@ func myFunction() { }, { name: "IndexOutOfRange", - fileContent: `package main + args: args{ + fileContent: `package main func myFunction() { New("param1", "param2") }`, - functionName: "myFunction", - functionCallName: "New", - paramToAdd: "param3", - index: 3, - err: errors.Errorf("index out of range"), + functionName: "myFunction", + functionCallName: "New", + paramToAdd: "param3", + index: 3, + }, + err: errors.Errorf("index out of range"), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := AppendParamToFunctionCall(tt.fileContent, tt.functionName, tt.functionCallName, tt.paramToAdd, tt.index) + got, err := AppendParamToFunctionCall( + tt.args.fileContent, + tt.args.functionName, + tt.args.functionCallName, + tt.args.paramToAdd, + tt.args.index, + ) if tt.err != nil { require.Error(t, err) require.Equal(t, tt.err.Error(), err.Error()) From 62864db8eb8b3f590da51752421f05619b81e3ac Mon Sep 17 00:00:00 2001 From: Pantani Date: Mon, 19 Feb 2024 19:16:30 -0300 Subject: [PATCH 25/35] improve organization --- ignite/pkg/goanalysis/replacer/function.go | 1 + .../pkg/goanalysis/replacer/function_test.go | 35 +++ ignite/pkg/goanalysis/replacer/import.go | 129 +++++++++ ignite/pkg/goanalysis/replacer/import_test.go | 252 ++++++++++++++++++ ignite/pkg/goanalysis/replacer/replacer.go | 78 ------ .../pkg/goanalysis/replacer/replacer_test.go | 197 ++------------ 6 files changed, 433 insertions(+), 259 deletions(-) create mode 100644 ignite/pkg/goanalysis/replacer/function_test.go create mode 100644 ignite/pkg/goanalysis/replacer/import.go create mode 100644 ignite/pkg/goanalysis/replacer/import_test.go diff --git a/ignite/pkg/goanalysis/replacer/function.go b/ignite/pkg/goanalysis/replacer/function.go index 997410d67b..5b2e851768 100644 --- a/ignite/pkg/goanalysis/replacer/function.go +++ b/ignite/pkg/goanalysis/replacer/function.go @@ -108,6 +108,7 @@ func newFunctionOptions() functionOpts { } } +// ModifyFunction modify a function based in the options. func ModifyFunction(fileContent, functionName string, functions ...FunctionOptions) (modifiedContent string, err error) { // Apply function options. opts := newFunctionOptions() diff --git a/ignite/pkg/goanalysis/replacer/function_test.go b/ignite/pkg/goanalysis/replacer/function_test.go new file mode 100644 index 0000000000..fc4825c396 --- /dev/null +++ b/ignite/pkg/goanalysis/replacer/function_test.go @@ -0,0 +1,35 @@ +package replacer + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestModifyFunction(t *testing.T) { + type args struct { + fileContent string + functionName string + functions []FunctionOptions + } + tests := []struct { + name string + args args + want string + err error + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ModifyFunction(tt.args.fileContent, tt.args.functionName, tt.args.functions...) + if tt.err != nil { + require.Error(t, err) + require.Equal(t, tt.err.Error(), err.Error()) + return + } + require.NoError(t, err) + require.Equal(t, tt.want, got) + }) + } +} diff --git a/ignite/pkg/goanalysis/replacer/import.go b/ignite/pkg/goanalysis/replacer/import.go new file mode 100644 index 0000000000..9a0adcdf16 --- /dev/null +++ b/ignite/pkg/goanalysis/replacer/import.go @@ -0,0 +1,129 @@ +package replacer + +import ( + "bytes" + "fmt" + "go/ast" + "go/format" + "go/parser" + "go/token" + "strconv" +) + +type ( + // importOpts represent the options for imp. + importOpts struct { + imports []imp + } + + // ImportOptions configures code generation. + ImportOptions func(*importOpts) + + imp struct { + repo string + name string + index int + } +) + +// WithImport add a new import. If the index is -1 will append in the end of the imports. +func WithImport(repo, name string, index int) ImportOptions { + return func(c *importOpts) { + c.imports = append(c.imports, imp{ + repo: repo, + name: name, + index: index, + }) + } +} + +func newImportOptions() importOpts { + return importOpts{ + imports: make([]imp, 0), + } +} + +// AppendImports appends import statements to the existing import block in Go source code content. +func AppendImports(fileContent string, imports ...ImportOptions) (string, error) { + // apply global options. + opts := newImportOptions() + for _, o := range imports { + o(&opts) + } + + fileSet := token.NewFileSet() + + // Parse the Go source code content. + f, err := parser.ParseFile(fileSet, "", fileContent, parser.ParseComments) + if err != nil { + return "", err + } + + // Find the existing import declaration. + var importDecl *ast.GenDecl + for _, decl := range f.Decls { + genDecl, ok := decl.(*ast.GenDecl) + if !ok || genDecl.Tok != token.IMPORT || len(genDecl.Specs) == 0 { + continue + } + importDecl = genDecl + break + } + + if importDecl == nil { + // If no existing import declaration found, create a new one. + importDecl = &ast.GenDecl{ + Tok: token.IMPORT, + Specs: make([]ast.Spec, 0), + } + f.Decls = append([]ast.Decl{importDecl}, f.Decls...) + } + + // Check existing imports to avoid duplicates. + existImports := make(map[string]struct{}) + for _, spec := range importDecl.Specs { + importSpec, ok := spec.(*ast.ImportSpec) + if !ok { + continue + } + existImports[importSpec.Path.Value] = struct{}{} + } + + // Add new import statements. + for _, importStmt := range opts.imports { + // Check if the import already exists. + path := strconv.Quote(importStmt.repo) + if _, ok := existImports[path]; ok { + continue + } + // Create a new import spec. + spec := &ast.ImportSpec{ + Name: &ast.Ident{ + Name: importStmt.name, + }, + Path: &ast.BasicLit{ + Kind: token.STRING, + Value: path, + }, + } + + switch { + case importStmt.index == -1: + // Append the new argument to the end + importDecl.Specs = append(importDecl.Specs, spec) + case importStmt.index >= 0 && importStmt.index <= len(importDecl.Specs): + // Insert the new argument at the specified index + importDecl.Specs = append(importDecl.Specs[:importStmt.index], append([]ast.Spec{spec}, importDecl.Specs[importStmt.index:]...)...) + default: + return "", fmt.Errorf("index out of range") // Stop the inspection, an error occurred + } + } + + // Format the modified AST. + var buf bytes.Buffer + if err := format.Node(&buf, fileSet, f); err != nil { + return "", err + } + + return buf.String(), nil +} diff --git a/ignite/pkg/goanalysis/replacer/import_test.go b/ignite/pkg/goanalysis/replacer/import_test.go new file mode 100644 index 0000000000..f1c4c2e881 --- /dev/null +++ b/ignite/pkg/goanalysis/replacer/import_test.go @@ -0,0 +1,252 @@ +package replacer + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ignite/cli/v28/ignite/pkg/errors" +) + +func TestAppendImports(t *testing.T) { + existingContent := `package main + +import ( + "fmt" +) + +func main() { + fmt.Println("Hello, world!") +}` + + type args struct { + fileContent string + imports []ImportOptions + } + tests := []struct { + name string + args args + want string + err error + }{ + { + name: "add single import statement", + args: args{ + fileContent: existingContent, + imports: []ImportOptions{ + WithImport("strings", "", -1), + }, + }, + want: `package main + +import ( + "fmt" + "strings" +) + +func main() { + fmt.Println("Hello, world!") +} +`, + }, + { + name: "add multiple import statements", + args: args{ + fileContent: existingContent, + imports: []ImportOptions{ + WithImport("strings", "st", -1), + WithImport("strconv", "", -1), + WithImport("os", "", -1), + }, + }, + want: `package main + +import ( + "fmt" + "os" + "strconv" + st "strings" +) + +func main() { + fmt.Println("Hello, world!") +} +`, + }, + { + name: "add multiple import statements with an existing one", + args: args{ + fileContent: existingContent, + imports: []ImportOptions{ + WithImport("strings", "st", -1), + WithImport("strconv", "", -1), + WithImport("os", "", -1), + WithImport("fmt", "", -1), + }, + }, + want: `package main + +import ( + "fmt" + "os" + "strconv" + st "strings" +) + +func main() { + fmt.Println("Hello, world!") +} +`, + }, + { + name: "add import to specific index", + args: args{ + fileContent: `package main + +import ( + "fmt" + "os" + st "strings" +)`, + imports: []ImportOptions{ + WithImport("strconv", "", 1), + }, + }, + want: `package main + +import ( + "fmt" + "os" + "strconv" + st "strings" +) +`, + }, + { + name: "add multiple imports to specific index", + args: args{ + fileContent: `package main + +import ( + "fmt" + "os" + st "strings" +)`, + imports: []ImportOptions{ + WithImport("strconv", "", 0), + WithImport("testing", "", 3), + WithImport("bytes", "", -1), + }, + }, + want: `package main + +import ( + "bytes" + "fmt" + "os" + "strconv" + st "strings" + "testing" +) +`, + }, + { + name: "add duplicate import statement", + args: args{ + fileContent: existingContent, + imports: []ImportOptions{ + WithImport("fmt", "", -1), + }, + }, + want: existingContent + "\n", + }, + { + name: "no import statement", + args: args{ + fileContent: `package main + +func main() { + fmt.Println("Hello, world!") +}`, + imports: []ImportOptions{ + WithImport("fmt", "", -1), + }, + }, + want: `package main + +import "fmt" + +func main() { + fmt.Println("Hello, world!") +} +`, + }, + { + name: "no import statement and add two imports", + args: args{ + fileContent: `package main + +func main() { + fmt.Println("Hello, world!") +}`, + imports: []ImportOptions{ + WithImport("fmt", "", -1), + WithImport("os", "", -1), + }, + }, + want: `package main + +import ( + "fmt" + "os" +) + +func main() { + fmt.Println("Hello, world!") +} +`, + }, + { + name: "invalid index", + args: args{ + fileContent: existingContent, + imports: []ImportOptions{ + WithImport("strings", "", 10), + }, + }, + err: errors.New("index out of range"), + }, + { + name: "add invalid import name", + args: args{ + fileContent: existingContent, + imports: []ImportOptions{ + WithImport("fmt\"", "fmt\"", -1), + }, + }, + err: errors.New("format.Node internal error (5:8: expected ';', found fmt (and 2 more errors))"), + }, + { + name: "add empty file content", + args: args{ + fileContent: "", + imports: []ImportOptions{ + WithImport("fmt", "", -1), + }, + }, + err: errors.New("1:1: expected 'package', found 'EOF'"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := AppendImports(tt.args.fileContent, tt.args.imports...) + if tt.err != nil { + require.Error(t, err) + require.Equal(t, tt.err.Error(), err.Error()) + return + } + require.NoError(t, err) + require.Equal(t, tt.want, got) + }) + } +} diff --git a/ignite/pkg/goanalysis/replacer/replacer.go b/ignite/pkg/goanalysis/replacer/replacer.go index 120ccb2c2e..be8385e124 100644 --- a/ignite/pkg/goanalysis/replacer/replacer.go +++ b/ignite/pkg/goanalysis/replacer/replacer.go @@ -53,84 +53,6 @@ func AppendFunction(fileContent string, function string) (modifiedContent string return buf.String(), nil } -// AppendImports appends import statements to the existing import block in Go source code content. -// TODO add options to add import in the end, begin or a specific line -func AppendImports(fileContent string, importStatements ...string) (modifiedContent string, err error) { - fileSet := token.NewFileSet() - - // Parse the Go source code content. - f, err := parser.ParseFile(fileSet, "", fileContent, parser.ParseComments) - if err != nil { - return "", err - } - - // Find the existing import declaration. - var importDecl *ast.GenDecl - for _, decl := range f.Decls { - genDecl, ok := decl.(*ast.GenDecl) - if !ok || genDecl.Tok != token.IMPORT || len(genDecl.Specs) == 0 { - continue - } - importDecl = genDecl - break - } - - if importDecl == nil { - // If no existing import declaration found, create a new one. - importDecl = &ast.GenDecl{ - Tok: token.IMPORT, - Specs: make([]ast.Spec, 0), - } - f.Decls = append([]ast.Decl{importDecl}, f.Decls...) - } - - // Check existing imports to avoid duplicates. - existImports := make(map[string]struct{}) - for _, spec := range importDecl.Specs { - importSpec, ok := spec.(*ast.ImportSpec) - if !ok { - continue - } - existImports[importSpec.Path.Value] = struct{}{} - } - - // Add new import statements. - for _, importStatement := range importStatements { - impSplit := strings.Split(importStatement, " ") - var ( - importRepo = impSplit[len(impSplit)-1] - importname = "" - ) - if len(impSplit) > 1 { - importname = impSplit[0] - } - - // Check if the import already exists. - if _, ok := existImports[`"`+importRepo+`"`]; ok { - continue - } - // Create a new import spec. - spec := &ast.ImportSpec{ - Name: &ast.Ident{ - Name: importname, - }, - Path: &ast.BasicLit{ - Kind: token.STRING, - Value: `"` + importRepo + `"`, - }, - } - importDecl.Specs = append(importDecl.Specs, spec) - } - - // Format the modified AST. - var buf bytes.Buffer - if err := format.Node(&buf, fileSet, f); err != nil { - return "", err - } - - return buf.String(), nil -} - // AppendCodeToFunction inserts code before the end or the return, if exists, of a function in Go source code content. func AppendCodeToFunction(fileContent, functionName, codeToInsert string) (modifiedContent string, err error) { fileSet := token.NewFileSet() diff --git a/ignite/pkg/goanalysis/replacer/replacer_test.go b/ignite/pkg/goanalysis/replacer/replacer_test.go index 150c123790..3f5aca76fe 100644 --- a/ignite/pkg/goanalysis/replacer/replacer_test.go +++ b/ignite/pkg/goanalysis/replacer/replacer_test.go @@ -162,171 +162,6 @@ func add(a, b int) int { } } -func TestAppendImports(t *testing.T) { - existingContent := `package main - -import ( - "fmt" -) - -func main() { - fmt.Println("Hello, world!") -}` - - type args struct { - fileContent string - importStatements []string - } - tests := []struct { - name string - args args - want string - err error - }{ - { - name: "Add single import statement", - args: args{ - fileContent: existingContent, - importStatements: []string{"strings"}, - }, - want: `package main - -import ( - "fmt" - "strings" -) - -func main() { - fmt.Println("Hello, world!") -} -`, - err: nil, - }, - { - name: "Add multiple import statements", - args: args{ - fileContent: existingContent, - importStatements: []string{"st strings", "strconv", "os"}, - }, - want: `package main - -import ( - "fmt" - "os" - "strconv" - st "strings" -) - -func main() { - fmt.Println("Hello, world!") -} -`, - err: nil, - }, - { - name: "Add multiple import statements with an existing one", - args: args{ - fileContent: existingContent, - importStatements: []string{"st strings", "strconv", "os", "fmt"}, - }, - want: `package main - -import ( - "fmt" - "os" - "strconv" - st "strings" -) - -func main() { - fmt.Println("Hello, world!") -} -`, - err: nil, - }, - { - name: "Add duplicate import statement", - args: args{ - fileContent: existingContent, - importStatements: []string{"fmt"}, - }, - want: existingContent + "\n", - err: nil, - }, - { - name: "No import statement", - args: args{ - fileContent: `package main - -func main() { - fmt.Println("Hello, world!") -}`, - importStatements: []string{"fmt"}, - }, - want: `package main - -import "fmt" - -func main() { - fmt.Println("Hello, world!") -} -`, - err: nil, - }, - { - name: "No import statement and add two imports", - args: args{ - fileContent: `package main - -func main() { - fmt.Println("Hello, world!") -}`, - importStatements: []string{"fmt", "os"}, - }, - want: `package main - -import ( - "fmt" - "os" -) - -func main() { - fmt.Println("Hello, world!") -} -`, - err: nil, - }, - { - name: "Add invalid import statement", - args: args{ - fileContent: existingContent, - importStatements: []string{"fmt\""}, - }, - err: errors.New("format.Node internal error (5:8: string literal not terminated (and 1 more errors))"), - }, - { - name: "Add empty file content", - args: args{ - fileContent: "", - importStatements: []string{"fmt"}, - }, - err: errors.New("1:1: expected 'package', found 'EOF'"), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := AppendImports(tt.args.fileContent, tt.args.importStatements...) - if tt.err != nil { - require.Error(t, err) - require.Equal(t, tt.err.Error(), err.Error()) - return - } - require.NoError(t, err) - require.Equal(t, tt.want, got) - }) - } -} - func TestAppendCode(t *testing.T) { existingContent := `package main @@ -356,7 +191,7 @@ func anotherFunction() bool { err error }{ { - name: "Append code to the end of the function", + name: "append code to the end of the function", args: args{ fileContent: existingContent, functionName: "main", @@ -382,7 +217,7 @@ func anotherFunction() bool { `, }, { - name: "Append code with return statement", + name: "append code with return statement", args: args{ fileContent: existingContent, functionName: "anotherFunction", @@ -408,7 +243,7 @@ func anotherFunction() bool { `, }, { - name: "Function not found", + name: "function not found", args: args{ fileContent: existingContent, functionName: "nonexistentFunction", @@ -417,7 +252,7 @@ func anotherFunction() bool { err: errors.New("function nonexistentFunction not found"), }, { - name: "Invalid code", + name: "invalid code", args: args{ fileContent: existingContent, functionName: "anotherFunction", @@ -468,7 +303,7 @@ func calculate() int { err error }{ { - name: "Replace return statement with a single variable", + name: "replace return statement with a single variable", args: args{ fileContent: existingContent, functionName: "calculate", @@ -492,7 +327,7 @@ func calculate() int { `, }, { - name: "Replace return statement with multiple variables", + name: "replace return statement with multiple variables", args: args{ fileContent: existingContent, functionName: "calculate", @@ -516,7 +351,7 @@ func calculate() int { `, }, { - name: "Function not found", + name: "function not found", args: args{ fileContent: existingContent, functionName: "nonexistentFunction", @@ -525,7 +360,7 @@ func calculate() int { err: errors.New("function nonexistentFunction not found"), }, { - name: "Invalid result", + name: "invalid result", args: args{ fileContent: existingContent, functionName: "nonexistentFunction", @@ -534,7 +369,7 @@ func calculate() int { err: errors.New("1:3: illegal character U+0040 '@' (and 1 more errors)"), }, { - name: "Reserved word", + name: "reserved word", args: args{ fileContent: existingContent, functionName: "nonexistentFunction", @@ -587,7 +422,7 @@ func oldFunction() { err error }{ { - name: "Replace function implementation", + name: "replace function implementation", args: args{ fileContent: existingContent, oldFunctionName: "oldFunction", @@ -607,7 +442,7 @@ func oldFunction() { fmt.Println("This is the new function.") } `, }, { - name: "Replace main function implementation", + name: "replace main function implementation", args: args{ fileContent: existingContent, oldFunctionName: "main", @@ -627,7 +462,7 @@ func oldFunction() { `, }, { - name: "Function not found", + name: "function not found", args: args{ fileContent: existingContent, oldFunctionName: "nonexistentFunction", @@ -636,7 +471,7 @@ func oldFunction() { err: errors.New("function nonexistentFunction not found in file content"), }, { - name: "Invalid new function", + name: "invalid new function", args: args{ fileContent: existingContent, oldFunctionName: "nonexistentFunction", @@ -738,7 +573,7 @@ func myFunction() { `, }, { - name: "FunctionNotFound", + name: "function not found", args: args{ fileContent: `package main @@ -753,7 +588,7 @@ func anotherFunction() { err: errors.Errorf("function myFunction not found or no calls to New inside the function"), }, { - name: "FunctionCallNotFound", + name: "function call not found", args: args{ fileContent: `package main @@ -773,7 +608,7 @@ func myFunction() { err: errors.Errorf("function myFunction not found or no calls to New inside the function"), }, { - name: "IndexOutOfRange", + name: "index out of range", args: args{ fileContent: `package main From 951de5eda26e98bdf972f99292815924cc8566bb Mon Sep 17 00:00:00 2001 From: Pantani Date: Tue, 20 Feb 2024 00:29:58 -0300 Subject: [PATCH 26/35] fix the function modifier logic --- ignite/pkg/goanalysis/replacer/function.go | 181 ++++++++++-------- .../pkg/goanalysis/replacer/function_test.go | 55 +++++- .../pkg/goanalysis/replacer/replacer_test.go | 3 - 3 files changed, 160 insertions(+), 79 deletions(-) diff --git a/ignite/pkg/goanalysis/replacer/function.go b/ignite/pkg/goanalysis/replacer/function.go index 5b2e851768..be7640145c 100644 --- a/ignite/pkg/goanalysis/replacer/function.go +++ b/ignite/pkg/goanalysis/replacer/function.go @@ -7,7 +7,6 @@ import ( "go/format" "go/parser" "go/token" - "strconv" "strings" "github.com/ignite/cli/v28/ignite/pkg/errors" @@ -35,7 +34,7 @@ type ( param struct { name string varType string - newLine bool + index int } line struct { code string @@ -44,12 +43,12 @@ type ( ) // AppendParams add a new param value. -func AppendParams(name, varType string, newLine bool) FunctionOptions { +func AppendParams(name, varType string, index int) FunctionOptions { return func(c *functionOpts) { c.newParams = append(c.newParams, param{ name: name, varType: varType, - newLine: newLine, + index: index, }) } } @@ -82,11 +81,12 @@ func AppendAtLine(code string, lineNumber uint64) FunctionOptions { // call 'New(param1, param2)' and we want to add the param3 the result will be 'New(param1, param2, param3)'. // Or if we have a struct call Params{Param1: param1} and we want to add the param2 the result will // be Params{Param1: param1, Param2: param2}. -func InsideCall(callName, code string) FunctionOptions { +func InsideCall(callName, code string, index int) FunctionOptions { return func(c *functionOpts) { c.insideCall = append(c.insideCall, call{ - name: callName, - code: code, + name: callName, + code: code, + index: index, }) } } @@ -156,9 +156,13 @@ func ModifyFunction(fileContent, functionName string, functions ...FunctionOptio returnStmts = append(returnStmts, newRetExpr) } - callMap := make(map[string]call) - for _, call := range opts.insideCall { - callMap[call.name] = call + callMap := make(map[string][]call) + for _, c := range opts.insideCall { + calls, ok := callMap[c.name] + if !ok { + calls = []call{} + } + callMap[c.name] = append(calls, c) } // Parse the Go code to insert. @@ -172,100 +176,127 @@ func ModifyFunction(fileContent, functionName string, functions ...FunctionOptio return true } - for _, param := range opts.newParams { - funcDecl.Type.Params.List = append(funcDecl.Type.Params.List, &ast.Field{ - Names: []*ast.Ident{ast.NewIdent(param.name)}, - Type: &ast.Ident{Name: param.varType}, - }) - } - - // Check if the function has the code you want to replace. - if newFunctionBody != nil { - funcDecl.Body = newFunctionBody - } - - ast.Inspect(funcDecl, func(n ast.Node) bool { - callExpr, ok := n.(*ast.CallExpr) - if !ok { - return true - } - // Check if the call expression matches the function call name - var c call - ident, ok := callExpr.Fun.(*ast.Ident) - if ok { - c = callMap[ident.Name] - } else { - selector, ok := callExpr.Fun.(*ast.SelectorExpr) - if ok { - c = callMap[selector.Sel.Name] - } else { - return true - } - } - - // Construct the new argument to be added - newArg := &ast.BasicLit{ - Kind: token.STRING, - Value: strconv.Quote(c.code), + for _, p := range opts.newParams { + fieldParam := &ast.Field{ + Names: []*ast.Ident{ast.NewIdent(p.name)}, + Type: &ast.Ident{Name: p.varType}, } switch { - case c.index == -1: + case p.index == -1: // Append the new argument to the end - callExpr.Args = append(callExpr.Args, newArg) - found = true - case c.index >= 0 && c.index <= len(callExpr.Args): + funcDecl.Type.Params.List = append(funcDecl.Type.Params.List, fieldParam) + case p.index >= 0 && p.index <= len(funcDecl.Type.Params.List): // Insert the new argument at the specified index - callExpr.Args = append(callExpr.Args[:c.index], append([]ast.Expr{newArg}, callExpr.Args[c.index:]...)...) - found = true + funcDecl.Type.Params.List = append( + funcDecl.Type.Params.List[:p.index], + append([]*ast.Field{fieldParam}, funcDecl.Type.Params.List[p.index:]...)..., + ) default: - errInspect = fmt.Errorf("index out of range") - return false // Stop the inspection, an error occurred + errInspect = fmt.Errorf("params index out of range") + return false } - return true // Continue the inspection for duplicated calls - }) + } + + // Check if the function has the code you want to replace. + if newFunctionBody != nil { + funcDecl.Body = newFunctionBody + } // Add the new code at line. for _, newLine := range opts.newLines { // Check if the function body has enough lines. - if newLine.number <= uint64(len(funcDecl.Body.List)) { - // Parse the Go code to insert. - insertionExpr, err := parser.ParseExpr(newLine.code) - if err != nil { - errInspect = err - return false - } - // Insert code at the specified line number. - funcDecl.Body.List = append(funcDecl.Body.List[:newLine.number-1], append([]ast.Stmt{&ast.ExprStmt{X: insertionExpr}}, funcDecl.Body.List[newLine.number-1:]...)...) + if newLine.number > uint64(len(funcDecl.Body.List))-1 { + errInspect = fmt.Errorf("code at line index out of range") + return false + } + // Parse the Go code to insert. + insertionExpr, err := parser.ParseExpr(newLine.code) + if err != nil { + errInspect = err + return false } + // Insert code at the specified line number. + funcDecl.Body.List = append( + funcDecl.Body.List[:newLine.number], + append([]ast.Stmt{&ast.ExprStmt{X: insertionExpr}}, funcDecl.Body.List[newLine.number:]...)..., + ) } // Check if there is a return statement in the function. if len(funcDecl.Body.List) > 0 { - // Replace the return statements. - for _, stmt := range funcDecl.Body.List { - if retStmt, ok := stmt.(*ast.ReturnStmt); ok && len(returnStmts) > 0 { + lastStmt := funcDecl.Body.List[len(funcDecl.Body.List)-1] + switch stmt := lastStmt.(type) { + case *ast.ReturnStmt: + // Replace the return statements. + if len(returnStmts) > 0 { // Remove existing return statements. - retStmt.Results = nil + stmt.Results = nil // Add the new return statement. - retStmt.Results = append(retStmt.Results, returnStmts...) + stmt.Results = append(stmt.Results, returnStmts...) } - } - - lastStmt := funcDecl.Body.List[len(funcDecl.Body.List)-1] - switch lastStmt.(type) { - case *ast.ReturnStmt: // If there is a return, insert before it. - appendCode = append(appendCode, lastStmt) + appendCode = append(appendCode, stmt) funcDecl.Body.List = append(funcDecl.Body.List[:len(funcDecl.Body.List)-1], appendCode...) default: + if len(returnStmts) > 0 { + errInspect = errors.New("return statement not found") + return false + } // If there is no return, insert at the end of the function body. funcDecl.Body.List = append(funcDecl.Body.List, appendCode...) } } else { + if len(returnStmts) > 0 { + errInspect = errors.New("return statement not found") + return false + } // If there are no statements in the function body, insert at the end of the function body. funcDecl.Body.List = append(funcDecl.Body.List, appendCode...) } + // Add new code to the function callers. + ast.Inspect(funcDecl, func(n ast.Node) bool { + callExpr, ok := n.(*ast.CallExpr) + if !ok { + return true + } + // Check if the call expression matches the function call name + name := "" + switch exp := callExpr.Fun.(type) { + case *ast.Ident: + name = exp.Name + case *ast.SelectorExpr: + name = exp.Sel.Name + default: + return true + } + + calls, ok := callMap[name] + if !ok { + return true + } + + // Construct the new argument to be added + for _, c := range calls { + newArg := &ast.BasicLit{ + Kind: token.STRING, + Value: c.code, + } + switch { + case c.index == -1: + // Append the new argument to the end + callExpr.Args = append(callExpr.Args, newArg) + case c.index >= 0 && c.index <= len(callExpr.Args): + // Insert the new argument at the specified index + callExpr.Args = append(callExpr.Args[:c.index], append([]ast.Expr{newArg}, callExpr.Args[c.index:]...)...) + default: + errInspect = fmt.Errorf("function call index out of range") + return false // Stop the inspection, an error occurred + } + } + return true // Continue the inspection for duplicated calls + }) + found = true return false }) diff --git a/ignite/pkg/goanalysis/replacer/function_test.go b/ignite/pkg/goanalysis/replacer/function_test.go index fc4825c396..db2fba160b 100644 --- a/ignite/pkg/goanalysis/replacer/function_test.go +++ b/ignite/pkg/goanalysis/replacer/function_test.go @@ -1,12 +1,30 @@ package replacer import ( + "strconv" "testing" "github.com/stretchr/testify/require" ) func TestModifyFunction(t *testing.T) { + existingContent := `package main + +import ( + "fmt" +) + +func main() { + fmt.Println("Hello, world!") + New(param1, param2) +} + +func anotherFunction() bool { + p := bla.NewParam() + p.CallSomething("Another call") + return true +}` + type args struct { fileContent string functionName string @@ -18,7 +36,42 @@ func TestModifyFunction(t *testing.T) { want string err error }{ - // TODO: Add test cases. + { + name: "", + args: args{ + fileContent: existingContent, + functionName: "anotherFunction", + functions: []FunctionOptions{ + AppendParams("param1", "string", 0), + ReplaceBody(`return false`), + AppendAtLine(`fmt.Println("Appended at line 0.")`, 0), + AppendCode(`fmt.Println("Appended code.")`), + AppendAtLine(`SimpleCall(foo, bar)`, 1), + InsideCall("SimpleCall", "baz", 0), + InsideCall("SimpleCall", "bla", -1), + InsideCall("Println", strconv.Quote("test"), -1), + NewReturn("1"), + }, + }, + want: `package main + +import ( + "fmt" +) + +func main() { + fmt.Println("Hello, world!") + New(param1, param2) +} + +func anotherFunction(param1 string) bool { + fmt.Println("Appended at line 0.", "test") + SimpleCall(baz, foo, bar, bla) + fmt.Println("Appended code.", "test") + return 1 +} +`, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/ignite/pkg/goanalysis/replacer/replacer_test.go b/ignite/pkg/goanalysis/replacer/replacer_test.go index 3f5aca76fe..9fd0b8e806 100644 --- a/ignite/pkg/goanalysis/replacer/replacer_test.go +++ b/ignite/pkg/goanalysis/replacer/replacer_test.go @@ -174,7 +174,6 @@ func main() { } func anotherFunction() bool { - // Some code here fmt.Println("Another function") return true }` @@ -210,7 +209,6 @@ func main() { } func anotherFunction() bool { - // Some code here fmt.Println("Another function") return true } @@ -234,7 +232,6 @@ func main() { } func anotherFunction() bool { - // Some code here fmt.Println("Another function") fmt.Println("Inserted code here") From 35c848792fea5502d5e28179328f2debac09398f Mon Sep 17 00:00:00 2001 From: Pantani Date: Tue, 20 Feb 2024 00:40:17 -0300 Subject: [PATCH 27/35] fix the arg idente for caller replace --- ignite/pkg/goanalysis/replacer/function.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ignite/pkg/goanalysis/replacer/function.go b/ignite/pkg/goanalysis/replacer/function.go index be7640145c..eb63c02640 100644 --- a/ignite/pkg/goanalysis/replacer/function.go +++ b/ignite/pkg/goanalysis/replacer/function.go @@ -278,9 +278,9 @@ func ModifyFunction(fileContent, functionName string, functions ...FunctionOptio // Construct the new argument to be added for _, c := range calls { - newArg := &ast.BasicLit{ - Kind: token.STRING, - Value: c.code, + newArg := &ast.Ident{ + NamePos: 1, // using the 1 position solve the arg indentation by magic ?! + Name: c.code, } switch { case c.index == -1: From 98064b165a59823e553bc21adfa63dde0a019444 Mon Sep 17 00:00:00 2001 From: Pantani Date: Tue, 20 Feb 2024 01:26:56 -0300 Subject: [PATCH 28/35] add more test cases and last fixes --- ignite/pkg/goanalysis/replacer/function.go | 44 +- .../pkg/goanalysis/replacer/function_test.go | 201 +++++- ignite/pkg/goanalysis/replacer/replacer.go | 282 -------- .../pkg/goanalysis/replacer/replacer_test.go | 641 ------------------ 4 files changed, 223 insertions(+), 945 deletions(-) delete mode 100644 ignite/pkg/goanalysis/replacer/replacer.go delete mode 100644 ignite/pkg/goanalysis/replacer/replacer_test.go diff --git a/ignite/pkg/goanalysis/replacer/function.go b/ignite/pkg/goanalysis/replacer/function.go index eb63c02640..51da59f7cc 100644 --- a/ignite/pkg/goanalysis/replacer/function.go +++ b/ignite/pkg/goanalysis/replacer/function.go @@ -138,7 +138,7 @@ func ModifyFunction(fileContent, functionName string, functions ...FunctionOptio // Parse the content of the append code an ast. appendCode := make([]ast.Stmt, 0) for _, codeToInsert := range opts.appendCode { - insertionExpr, err := parser.ParseExpr(codeToInsert) + insertionExpr, err := parser.ParseExprFrom(fileSet, "", []byte(codeToInsert), parser.ParseComments) if err != nil { return "", err } @@ -149,7 +149,7 @@ func ModifyFunction(fileContent, functionName string, functions ...FunctionOptio returnStmts := make([]ast.Expr, 0) for _, returnVar := range opts.returnVars { // Parse the new return var to expression. - newRetExpr, err := parser.ParseExpr(returnVar) + newRetExpr, err := parser.ParseExprFrom(fileSet, "", []byte(returnVar), parser.ParseComments) if err != nil { return "", err } @@ -157,12 +157,14 @@ func ModifyFunction(fileContent, functionName string, functions ...FunctionOptio } callMap := make(map[string][]call) + callMapCheck := make(map[string][]call) for _, c := range opts.insideCall { calls, ok := callMap[c.name] if !ok { calls = []call{} } callMap[c.name] = append(calls, c) + callMapCheck[c.name] = append(calls, c) } // Parse the Go code to insert. @@ -192,7 +194,7 @@ func ModifyFunction(fileContent, functionName string, functions ...FunctionOptio append([]*ast.Field{fieldParam}, funcDecl.Type.Params.List[p.index:]...)..., ) default: - errInspect = fmt.Errorf("params index out of range") + errInspect = errors.Errorf("params index %d out of range", p.index) return false } } @@ -206,11 +208,11 @@ func ModifyFunction(fileContent, functionName string, functions ...FunctionOptio for _, newLine := range opts.newLines { // Check if the function body has enough lines. if newLine.number > uint64(len(funcDecl.Body.List))-1 { - errInspect = fmt.Errorf("code at line index out of range") + errInspect = errors.Errorf("line number %d out of range", newLine.number) return false } // Parse the Go code to insert. - insertionExpr, err := parser.ParseExpr(newLine.code) + insertionExpr, err := parser.ParseExprFrom(fileSet, "", []byte(newLine.code), parser.ParseComments) if err != nil { errInspect = err return false @@ -234,16 +236,20 @@ func ModifyFunction(fileContent, functionName string, functions ...FunctionOptio // Add the new return statement. stmt.Results = append(stmt.Results, returnStmts...) } - // If there is a return, insert before it. - appendCode = append(appendCode, stmt) - funcDecl.Body.List = append(funcDecl.Body.List[:len(funcDecl.Body.List)-1], appendCode...) + if len(appendCode) > 0 { + // If there is a return, insert before it. + appendCode = append(appendCode, stmt) + funcDecl.Body.List = append(funcDecl.Body.List[:len(funcDecl.Body.List)-1], appendCode...) + } default: if len(returnStmts) > 0 { errInspect = errors.New("return statement not found") return false } // If there is no return, insert at the end of the function body. - funcDecl.Body.List = append(funcDecl.Body.List, appendCode...) + if len(appendCode) > 0 { + funcDecl.Body.List = append(funcDecl.Body.List, appendCode...) + } } } else { if len(returnStmts) > 0 { @@ -251,7 +257,9 @@ func ModifyFunction(fileContent, functionName string, functions ...FunctionOptio return false } // If there are no statements in the function body, insert at the end of the function body. - funcDecl.Body.List = append(funcDecl.Body.List, appendCode...) + if len(appendCode) > 0 { + funcDecl.Body.List = append(funcDecl.Body.List, appendCode...) + } } // Add new code to the function callers. @@ -278,10 +286,7 @@ func ModifyFunction(fileContent, functionName string, functions ...FunctionOptio // Construct the new argument to be added for _, c := range calls { - newArg := &ast.Ident{ - NamePos: 1, // using the 1 position solve the arg indentation by magic ?! - Name: c.code, - } + newArg := &ast.Ident{Name: c.code} switch { case c.index == -1: // Append the new argument to the end @@ -290,13 +295,22 @@ func ModifyFunction(fileContent, functionName string, functions ...FunctionOptio // Insert the new argument at the specified index callExpr.Args = append(callExpr.Args[:c.index], append([]ast.Expr{newArg}, callExpr.Args[c.index:]...)...) default: - errInspect = fmt.Errorf("function call index out of range") + errInspect = errors.Errorf("function call index %d out of range", c.index) return false // Stop the inspection, an error occurred } } + delete(callMapCheck, name) return true // Continue the inspection for duplicated calls }) + if errInspect != nil { + return false + } + if len(callMapCheck) > 0 { + errInspect = errors.Errorf("function calls not found: %v", callMapCheck) + return false + } + // everything is ok, mark as found and stop the inspect found = true return false }) diff --git a/ignite/pkg/goanalysis/replacer/function_test.go b/ignite/pkg/goanalysis/replacer/function_test.go index db2fba160b..f79afde7d6 100644 --- a/ignite/pkg/goanalysis/replacer/function_test.go +++ b/ignite/pkg/goanalysis/replacer/function_test.go @@ -2,27 +2,30 @@ package replacer import ( "strconv" + "strings" "testing" "github.com/stretchr/testify/require" + + "github.com/ignite/cli/v28/ignite/pkg/errors" ) func TestModifyFunction(t *testing.T) { existingContent := `package main import ( - "fmt" + "fmt" ) func main() { - fmt.Println("Hello, world!") + fmt.Println("Hello, world!") New(param1, param2) } func anotherFunction() bool { p := bla.NewParam() - p.CallSomething("Another call") - return true + p.CallSomething("Another call") + return true }` type args struct { @@ -37,7 +40,7 @@ func anotherFunction() bool { err error }{ { - name: "", + name: "add all modifications type", args: args{ fileContent: existingContent, functionName: "anotherFunction", @@ -45,12 +48,12 @@ func anotherFunction() bool { AppendParams("param1", "string", 0), ReplaceBody(`return false`), AppendAtLine(`fmt.Println("Appended at line 0.")`, 0), - AppendCode(`fmt.Println("Appended code.")`), AppendAtLine(`SimpleCall(foo, bar)`, 1), + AppendCode(`fmt.Println("Appended code.")`), + NewReturn("1"), InsideCall("SimpleCall", "baz", 0), InsideCall("SimpleCall", "bla", -1), InsideCall("Println", strconv.Quote("test"), -1), - NewReturn("1"), }, }, want: `package main @@ -72,6 +75,190 @@ func anotherFunction(param1 string) bool { } `, }, + { + name: "add the replace body", + args: args{ + fileContent: existingContent, + functionName: "anotherFunction", + functions: []FunctionOptions{ReplaceBody(`return false`)}, + }, + want: `package main + +import ( + "fmt" +) + +func main() { + fmt.Println("Hello, world!") + New(param1, param2) +} + +func anotherFunction() bool { return false } +`, + }, + { + name: "add append line and code modification", + args: args{ + fileContent: existingContent, + functionName: "anotherFunction", + functions: []FunctionOptions{ + AppendAtLine(`fmt.Println("Appended at line 0.")`, 0), + AppendAtLine(`SimpleCall(foo, bar)`, 1), + AppendCode(`fmt.Println("Appended code.")`), + }, + }, + want: `package main + +import ( + "fmt" +) + +func main() { + fmt.Println("Hello, world!") + New(param1, param2) +} + +func anotherFunction() bool { + fmt.Println("Appended at line 0.") + SimpleCall(foo, bar) + + p := bla.NewParam() + p.CallSomething("Another call") + fmt.Println("Appended code.") + + return true +} +`, + }, + { + name: "add all modifications type", + args: args{ + fileContent: existingContent, + functionName: "anotherFunction", + functions: []FunctionOptions{NewReturn("1")}, + }, + want: strings.ReplaceAll(existingContent, "return true", "return 1\n") + "\n", + }, + { + name: "add inside call modifications", + args: args{ + fileContent: existingContent, + functionName: "anotherFunction", + functions: []FunctionOptions{ + InsideCall("NewParam", "baz", 0), + InsideCall("NewParam", "bla", -1), + InsideCall("CallSomething", strconv.Quote("test1"), -1), + InsideCall("CallSomething", strconv.Quote("test2"), 0), + }, + }, + want: `package main + +import ( + "fmt" +) + +func main() { + fmt.Println("Hello, world!") + New(param1, param2) +} + +func anotherFunction() bool { + p := bla.NewParam(baz, bla) + p.CallSomething("test2", "Another call", "test1") + return true +} +`, + }, + { + name: "params out of range", + args: args{ + fileContent: existingContent, + functionName: "anotherFunction", + functions: []FunctionOptions{AppendParams("param1", "string", 1)}, + }, + err: errors.New("params index 1 out of range"), + }, + { + name: "invalid params", + args: args{ + fileContent: existingContent, + functionName: "anotherFunction", + functions: []FunctionOptions{AppendParams("9#.(c", "string", 0)}, + }, + err: errors.New("format.Node internal error (12:22: expected ')', found 9 (and 1 more errors))"), + }, + { + name: "invalid content for replace body", + args: args{ + fileContent: existingContent, + functionName: "anotherFunction", + functions: []FunctionOptions{ReplaceBody("9#.(c")}, + }, + err: errors.New("1:24: illegal character U+0023 '#'"), + }, + { + name: "line number out of range", + args: args{ + fileContent: existingContent, + functionName: "anotherFunction", + functions: []FunctionOptions{AppendAtLine(`fmt.Println("")`, 4)}, + }, + err: errors.New("line number 4 out of range"), + }, + { + name: "invalid code for append at line", + args: args{ + fileContent: existingContent, + functionName: "anotherFunction", + functions: []FunctionOptions{AppendAtLine("9#.(c", 0)}, + }, + err: errors.New("1:2: illegal character U+0023 '#'"), + }, + { + name: "invalid code append", + args: args{ + fileContent: existingContent, + functionName: "anotherFunction", + functions: []FunctionOptions{AppendCode("9#.(c")}, + }, + err: errors.New("1:2: illegal character U+0023 '#'"), + }, + { + name: "invalid new return", + args: args{ + fileContent: existingContent, + functionName: "anotherFunction", + functions: []FunctionOptions{NewReturn("9#.(c")}, + }, + err: errors.New("1:2: illegal character U+0023 '#'"), + }, + { + name: "call name not found", + args: args{ + fileContent: existingContent, + functionName: "anotherFunction", + functions: []FunctionOptions{InsideCall("FooFunction", "baz", 0)}, + }, + err: errors.New("function calls not found: map[FooFunction:[{FooFunction baz 0}]]"), + }, + { + name: "invalid call param", + args: args{ + fileContent: existingContent, + functionName: "anotherFunction", + functions: []FunctionOptions{InsideCall("NewParam", "9#.(c", 0)}, + }, + err: errors.New("format.Node internal error (13:21: illegal character U+0023 '#' (and 2 more errors))"), + }, + { + name: "call params out of range", + args: args{ + fileContent: existingContent, + functionName: "anotherFunction", + functions: []FunctionOptions{InsideCall("NewParam", "baz", 1)}, + }, + err: errors.New("function call index 1 out of range"), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/ignite/pkg/goanalysis/replacer/replacer.go b/ignite/pkg/goanalysis/replacer/replacer.go deleted file mode 100644 index be8385e124..0000000000 --- a/ignite/pkg/goanalysis/replacer/replacer.go +++ /dev/null @@ -1,282 +0,0 @@ -package replacer - -import ( - "bytes" - "fmt" - "go/ast" - "go/format" - "go/parser" - "go/token" - "strconv" - "strings" - - "github.com/ignite/cli/v28/ignite/pkg/errors" -) - -// AppendFunction appends a new function to the end of the Go source code content. -func AppendFunction(fileContent string, function string) (modifiedContent string, err error) { - fileSet := token.NewFileSet() - - // Parse the function body as a separate file. - funcFile, err := parser.ParseFile(fileSet, "", "package main\n"+function, parser.AllErrors) - if err != nil { - return "", err - } - - // Extract the first declaration, assuming it's a function declaration. - var funcDecl *ast.FuncDecl - for _, decl := range funcFile.Decls { - if fDecl, ok := decl.(*ast.FuncDecl); ok { - funcDecl = fDecl - break - } - } - if funcDecl == nil { - return "", errors.Errorf("no function declaration found in the provided function body") - } - - // Parse the Go source code content. - f, err := parser.ParseFile(fileSet, "", fileContent, parser.ParseComments) - if err != nil { - return "", err - } - - // Append the function declaration to the file's declarations. - f.Decls = append(f.Decls, funcDecl) - - // Format the modified AST. - var buf bytes.Buffer - if err := format.Node(&buf, fileSet, f); err != nil { - return "", err - } - - return buf.String(), nil -} - -// AppendCodeToFunction inserts code before the end or the return, if exists, of a function in Go source code content. -func AppendCodeToFunction(fileContent, functionName, codeToInsert string) (modifiedContent string, err error) { - fileSet := token.NewFileSet() - - // Parse the Go source code content. - f, err := parser.ParseFile(fileSet, "", fileContent, parser.ParseComments) - if err != nil { - return "", err - } - - // Parse the Go code to insert. - insertionExpr, err := parser.ParseExpr(codeToInsert) - if err != nil { - return "", err - } - - found := false - ast.Inspect(f, func(n ast.Node) bool { - if funcDecl, ok := n.(*ast.FuncDecl); ok { - // Check if the function has the code you want to replace. - if funcDecl.Name.Name == functionName { - // Check if there is a return statement in the function. - if len(funcDecl.Body.List) > 0 { - lastStmt := funcDecl.Body.List[len(funcDecl.Body.List)-1] - switch lastStmt.(type) { - case *ast.ReturnStmt: - // If there is a return, insert before it. - funcDecl.Body.List = append(funcDecl.Body.List[:len(funcDecl.Body.List)-1], &ast.ExprStmt{X: insertionExpr}, lastStmt) - default: - // If there is no return, insert at the end of the function body. - funcDecl.Body.List = append(funcDecl.Body.List, &ast.ExprStmt{X: insertionExpr}) - } - } else { - // If there are no statements in the function body, insert at the end of the function body. - funcDecl.Body.List = append(funcDecl.Body.List, &ast.ExprStmt{X: insertionExpr}) - } - found = true - return false - } - } - return true - }) - - if !found { - return "", errors.Errorf("function %s not found", functionName) - } - - // Write the modified AST to a buffer. - var buf bytes.Buffer - if err := format.Node(&buf, fileSet, f); err != nil { - return "", err - } - - return buf.String(), nil -} - -// ReplaceReturnVars replaces return statements in a Go function with a new return statement. -func ReplaceReturnVars(fileContent, functionName string, returnVars ...string) (string, error) { - fileSet := token.NewFileSet() - - // Parse the Go source code content. - f, err := parser.ParseFile(fileSet, "", fileContent, parser.ParseComments) - if err != nil { - return "", err - } - - returnStmts := make([]ast.Expr, 0) - for _, returnVar := range returnVars { - // Parse the new return var to expression. - newRetExpr, err := parser.ParseExpr(returnVar) - if err != nil { - return "", err - } - returnStmts = append(returnStmts, newRetExpr) - } - - found := false - ast.Inspect(f, func(n ast.Node) bool { - if funcDecl, ok := n.(*ast.FuncDecl); ok { - // Check if the function has the code you want to replace. - if funcDecl.Name.Name == functionName { - // Replace the return statements. - for _, stmt := range funcDecl.Body.List { - if retStmt, ok := stmt.(*ast.ReturnStmt); ok { - // Remove existing return statements. - retStmt.Results = nil - // Add the new return statement. - retStmt.Results = append(retStmt.Results, returnStmts...) - } - } - found = true - return false - } - } - return true - }) - - if !found { - return "", errors.Errorf("function %s not found", functionName) - } - - // Write the modified AST to a buffer. - var buf bytes.Buffer - if err := format.Node(&buf, fileSet, f); err != nil { - return "", err - } - - return buf.String(), nil -} - -// ReplaceFunctionContent replaces a function implementation content in Go source code. -func ReplaceFunctionContent(fileContent, oldFunctionName, newFunction string) (modifiedContent string, err error) { - fileSet := token.NewFileSet() - - // Parse the Go source code content. - f, err := parser.ParseFile(fileSet, "", fileContent, parser.ParseComments) - if err != nil { - return "", err - } - - // Parse the content of the new function into an ast.File. - newFuncContent := fmt.Sprintf("package p; func _() { %s }", strings.TrimSpace(newFunction)) - newFile, err := parser.ParseFile(fileSet, "", newFuncContent, parser.ParseComments) - if err != nil { - return "", err - } - - found := false - ast.Inspect(f, func(n ast.Node) bool { - if funcDecl, ok := n.(*ast.FuncDecl); ok { - // Check if the function has the code you want to replace. - if funcDecl.Name.Name == oldFunctionName { - // Take the body of the new function from the parsed file. - newFunctionBody := newFile.Decls[0].(*ast.FuncDecl).Body - // Replace the function body with the body of the new function. - funcDecl.Body = newFunctionBody - found = true - return false - } - } - return true - }) - - if !found { - return "", errors.Errorf("function %s not found in file content", oldFunctionName) - } - - // Write the modified AST to a buffer. - var buf bytes.Buffer - if err := format.Node(&buf, fileSet, f); err != nil { - return "", err - } - - return buf.String(), nil -} - -// AppendParamToFunctionCall inserts a parameter to a function call inside a function in Go source code content. -func AppendParamToFunctionCall(fileContent, functionName, functionCallName, paramToAdd string, index int) (modifiedContent string, err error) { - fileSet := token.NewFileSet() - - // Parse the Go source code content. - f, err := parser.ParseFile(fileSet, "", fileContent, parser.ParseComments) - if err != nil { - return "", err - } - - var ( - found bool - errInspect error - ) - ast.Inspect(f, func(n ast.Node) bool { - if funcDecl, ok := n.(*ast.FuncDecl); ok { - // Check if the function has the name you want to replace. - if funcDecl.Name.Name == functionName { - ast.Inspect(funcDecl, func(n ast.Node) bool { - callExpr, ok := n.(*ast.CallExpr) - if !ok { - return true - } - // Check if the call expression matches the function call name - ident, ok := callExpr.Fun.(*ast.Ident) - if !ok || ident.Name != functionCallName { - selector, ok := callExpr.Fun.(*ast.SelectorExpr) - if !ok || selector.Sel.Name != functionCallName { - return true - } - } - // Construct the new argument to be added - newArg := &ast.BasicLit{ - Kind: token.STRING, - Value: strconv.Quote(paramToAdd), - } - switch { - case index == -1: - // Append the new argument to the end - callExpr.Args = append(callExpr.Args, newArg) - found = true - case index >= 0 && index <= len(callExpr.Args): - // Insert the new argument at the specified index - callExpr.Args = append(callExpr.Args[:index], append([]ast.Expr{newArg}, callExpr.Args[index:]...)...) - found = true - default: - errInspect = fmt.Errorf("index out of range") - return false // Stop the inspection, an error occurred - } - return true // Continue the inspection for duplicated calls - }) - return false // Stop the inspection, we found what we needed - } - } - return true // Continue inspecting - }) - if errInspect != nil { - return "", errInspect - } - if !found { - return "", fmt.Errorf("function %s not found or no calls to %s inside the function", functionName, functionCallName) - } - - // Write the modified AST to a buffer. - var buf bytes.Buffer - if err := format.Node(&buf, fileSet, f); err != nil { - return "", err - } - - return buf.String(), nil -} diff --git a/ignite/pkg/goanalysis/replacer/replacer_test.go b/ignite/pkg/goanalysis/replacer/replacer_test.go deleted file mode 100644 index 9fd0b8e806..0000000000 --- a/ignite/pkg/goanalysis/replacer/replacer_test.go +++ /dev/null @@ -1,641 +0,0 @@ -package replacer - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "github.com/ignite/cli/v28/ignite/pkg/errors" -) - -func TestAppendFunction(t *testing.T) { - type args struct { - fileContent string - function string - } - tests := []struct { - name string - args args - want string - err error - }{ - { - name: "Append a function after the package declaration", - args: args{ - fileContent: `package main`, - function: `func add(a, b int) int { - return a + b -}`, - }, - want: `package main - -func add(a, b int) int { - return a + b -} -`, - }, - { - name: "Append a function after a var", - args: args{ - fileContent: `package main - -import ( - "fmt" -) - -var myIntVar int = 42 -`, - function: `func add(a, b int) int { - return a + b -}`, - }, - want: `package main - -import ( - "fmt" -) - -var myIntVar int = 42 - -func add(a, b int) int { - return a + b -} -`, - }, - { - name: "Append a function after the import", - args: args{ - fileContent: `package main - -import ( - "fmt" -) -`, - function: `func add(a, b int) int { - return a + b -}`, - }, - want: `package main - -import ( - "fmt" -) - -func add(a, b int) int { - return a + b -} -`, - }, - { - name: "Append a function after another function", - args: args{ - fileContent: `package main - -import ( - "fmt" -) - -var myIntVar int = 42 - -func myFunction() int { - return 42 -} -`, - function: `func add(a, b int) int { - return a + b -}`, - }, - want: `package main - -import ( - "fmt" -) - -var myIntVar int = 42 - -func myFunction() int { - return 42 -} -func add(a, b int) int { - return a + b -} -`, - }, - { - name: "Append a function in an empty file", - args: args{ - fileContent: ``, - function: `func add(a, b int) int { - return a + b -}`, - }, - err: errors.New("1:1: expected 'package', found 'EOF'"), - }, - { - name: "Append a empty function", - args: args{ - fileContent: `package main`, - function: ``, - }, - err: errors.New("no function declaration found in the provided function body"), - }, - { - name: "Append an invalid function", - args: args{ - fileContent: `package main`, - function: `@,.l.e,`, - }, - err: errors.New("2:1: illegal character U+0040 '@'"), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := AppendFunction(tt.args.fileContent, tt.args.function) - if tt.err != nil { - require.Error(t, err) - require.Equal(t, tt.err.Error(), err.Error()) - return - } - require.NoError(t, err) - require.Equal(t, tt.want, got) - }) - } -} - -func TestAppendCode(t *testing.T) { - existingContent := `package main - -import ( - "fmt" -) - -func main() { - fmt.Println("Hello, world!") -} - -func anotherFunction() bool { - fmt.Println("Another function") - return true -}` - - type args struct { - fileContent string - functionName string - codeToInsert string - } - tests := []struct { - name string - args args - want string - err error - }{ - { - name: "append code to the end of the function", - args: args{ - fileContent: existingContent, - functionName: "main", - codeToInsert: "fmt.Println(\"Inserted code here\")", - }, - want: `package main - -import ( - "fmt" -) - -func main() { - fmt.Println("Hello, world!") - fmt.Println("Inserted code here") - -} - -func anotherFunction() bool { - fmt.Println("Another function") - return true -} -`, - }, - { - name: "append code with return statement", - args: args{ - fileContent: existingContent, - functionName: "anotherFunction", - codeToInsert: "fmt.Println(\"Inserted code here\")", - }, - want: `package main - -import ( - "fmt" -) - -func main() { - fmt.Println("Hello, world!") -} - -func anotherFunction() bool { - fmt.Println("Another function") - fmt.Println("Inserted code here") - - return true -} -`, - }, - { - name: "function not found", - args: args{ - fileContent: existingContent, - functionName: "nonexistentFunction", - codeToInsert: "fmt.Println(\"Inserted code here\")", - }, - err: errors.New("function nonexistentFunction not found"), - }, - { - name: "invalid code", - args: args{ - fileContent: existingContent, - functionName: "anotherFunction", - codeToInsert: "%#)(u309f/..\"", - }, - err: errors.New("1:1: expected operand, found '%' (and 2 more errors)"), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := AppendCodeToFunction(tt.args.fileContent, tt.args.functionName, tt.args.codeToInsert) - if tt.err != nil { - require.Error(t, err) - require.Equal(t, tt.err.Error(), err.Error()) - return - } - require.NoError(t, err) - require.Equal(t, tt.want, got) - }) - } -} - -func TestReplaceReturn(t *testing.T) { - existingContent := `package main - -import ( - "fmt" -) - -func main() { - x := calculate() - fmt.Println("Result:", x) -} - -func calculate() int { - return 42 -}` - - type args struct { - fileContent string - functionName string - returnVars []string - } - tests := []struct { - name string - args args - want string - err error - }{ - { - name: "replace return statement with a single variable", - args: args{ - fileContent: existingContent, - functionName: "calculate", - returnVars: []string{"result"}, - }, - want: `package main - -import ( - "fmt" -) - -func main() { - x := calculate() - fmt.Println("Result:", x) -} - -func calculate() int { - return result - -} -`, - }, - { - name: "replace return statement with multiple variables", - args: args{ - fileContent: existingContent, - functionName: "calculate", - returnVars: []string{"result", "err"}, - }, - want: `package main - -import ( - "fmt" -) - -func main() { - x := calculate() - fmt.Println("Result:", x) -} - -func calculate() int { - return result, err - -} -`, - }, - { - name: "function not found", - args: args{ - fileContent: existingContent, - functionName: "nonexistentFunction", - returnVars: []string{"result"}, - }, - err: errors.New("function nonexistentFunction not found"), - }, - { - name: "invalid result", - args: args{ - fileContent: existingContent, - functionName: "nonexistentFunction", - returnVars: []string{"ae@@of..!\""}, - }, - err: errors.New("1:3: illegal character U+0040 '@' (and 1 more errors)"), - }, - { - name: "reserved word", - args: args{ - fileContent: existingContent, - functionName: "nonexistentFunction", - returnVars: []string{"range"}, - }, - err: errors.New("1:1: expected operand, found 'range'"), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := ReplaceReturnVars(tt.args.fileContent, tt.args.functionName, tt.args.returnVars...) - if tt.err != nil { - require.Error(t, err) - require.Equal(t, tt.err.Error(), err.Error()) - return - } - require.NoError(t, err) - require.Equal(t, tt.want, got) - }) - } -} - -func TestReplaceCode(t *testing.T) { - var ( - newFunction = `fmt.Println("This is the new function.")` - existingContent = `package main - -import ( - "fmt" -) - -func main() { - fmt.Println("Hello, world!") -} - -func oldFunction() { - fmt.Println("This is the old function.") -}` - ) - - type args struct { - fileContent string - oldFunctionName string - newFunction string - } - tests := []struct { - name string - args args - want string - err error - }{ - { - name: "replace function implementation", - args: args{ - fileContent: existingContent, - oldFunctionName: "oldFunction", - newFunction: newFunction, - }, - want: `package main - -import ( - "fmt" -) - -func main() { - fmt.Println("Hello, world!") -} - -func oldFunction() { fmt.Println("This is the new function.") } -`, - }, - { - name: "replace main function implementation", - args: args{ - fileContent: existingContent, - oldFunctionName: "main", - newFunction: newFunction, - }, - want: `package main - -import ( - "fmt" -) - -func main() { fmt.Println("This is the new function.") } - -func oldFunction() { - fmt.Println("This is the old function.") -} -`, - }, - { - name: "function not found", - args: args{ - fileContent: existingContent, - oldFunctionName: "nonexistentFunction", - newFunction: newFunction, - }, - err: errors.New("function nonexistentFunction not found in file content"), - }, - { - name: "invalid new function", - args: args{ - fileContent: existingContent, - oldFunctionName: "nonexistentFunction", - newFunction: "ae@@of..!\"", - }, - err: errors.New("1:25: illegal character U+0040 '@' (and 2 more errors)"), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := ReplaceFunctionContent(tt.args.fileContent, tt.args.oldFunctionName, tt.args.newFunction) - if tt.err != nil { - require.Error(t, err) - require.Equal(t, tt.err.Error(), err.Error()) - return - } - require.NoError(t, err) - require.Equal(t, tt.want, got) - }) - } -} - -func TestAppendParamToFunctionCall(t *testing.T) { - type args struct { - fileContent string - functionName string - functionCallName string - index int - paramToAdd string - } - tests := []struct { - name string - args args - want string - err error - }{ - { - name: "add new parameter to index 1 in a function extension", - args: args{ - fileContent: `package main - -func myFunction() { - p := NewParam() - p.New("param1", "param2") -}`, - functionName: "myFunction", - functionCallName: "New", - paramToAdd: "param3", - index: 1, - }, - want: `package main - -func myFunction() { - p := NewParam() - p.New("param1", "param3", "param2") -} -`, - }, - { - name: "add new parameter to index 1", - args: args{ - fileContent: `package main - -func myFunction() { - New("param1", "param2") -}`, - functionName: "myFunction", - functionCallName: "New", - paramToAdd: "param3", - index: 1, - }, - want: `package main - -func myFunction() { - New("param1", "param3", "param2") -} -`, - }, - { - name: "add a new parameter for two functions", - args: args{ - fileContent: `package main - -func myFunction() { - New("param1", "param2") - New("param1", "param2", "param4") -}`, - functionName: "myFunction", - functionCallName: "New", - index: -1, - paramToAdd: "param3", - }, - want: `package main - -func myFunction() { - New("param1", "param2", "param3") - New("param1", "param2", "param4", "param3") -} -`, - }, - { - name: "function not found", - args: args{ - fileContent: `package main - -func anotherFunction() { - New("param1", "param2") -}`, - functionName: "myFunction", - functionCallName: "New", - paramToAdd: "param3", - index: 1, - }, - err: errors.Errorf("function myFunction not found or no calls to New inside the function"), - }, - { - name: "function call not found", - args: args{ - fileContent: `package main - -func myFunction() { - AnotherFunction("param1", "param2") -}`, - functionName: "myFunction", - functionCallName: "New", - paramToAdd: "param3", - index: 1, - }, - want: `package main - -func myFunction() { - AnotherFunction("param1", "param2") -}`, - err: errors.Errorf("function myFunction not found or no calls to New inside the function"), - }, - { - name: "index out of range", - args: args{ - fileContent: `package main - -func myFunction() { - New("param1", "param2") -}`, - functionName: "myFunction", - functionCallName: "New", - paramToAdd: "param3", - index: 3, - }, - err: errors.Errorf("index out of range"), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := AppendParamToFunctionCall( - tt.args.fileContent, - tt.args.functionName, - tt.args.functionCallName, - tt.args.paramToAdd, - tt.args.index, - ) - if tt.err != nil { - require.Error(t, err) - require.Equal(t, tt.err.Error(), err.Error()) - return - } - require.NoError(t, err) - require.Equal(t, tt.want, got) - }) - } -} From e0d5796b0425ee53b4066e2ba122f2ca7986a76d Mon Sep 17 00:00:00 2001 From: Pantani Date: Tue, 20 Feb 2024 02:02:31 -0300 Subject: [PATCH 29/35] start replace the params template to the xast --- .../{goanalysis/replacer => xast}/function.go | 2 +- .../replacer => xast}/function_test.go | 2 +- .../{goanalysis/replacer => xast}/global.go | 2 +- .../replacer => xast}/global_test.go | 2 +- .../{goanalysis/replacer => xast}/import.go | 2 +- .../replacer => xast}/import_test.go | 2 +- .../x/{{moduleName}}/types/params.go.plush | 9 ----- ignite/templates/module/create/params.go | 33 ++++++++++++------- ignite/templates/module/placeholders.go | 8 ----- 9 files changed, 27 insertions(+), 35 deletions(-) rename ignite/pkg/{goanalysis/replacer => xast}/function.go (99%) rename ignite/pkg/{goanalysis/replacer => xast}/function_test.go (99%) rename ignite/pkg/{goanalysis/replacer => xast}/global.go (99%) rename ignite/pkg/{goanalysis/replacer => xast}/global_test.go (99%) rename ignite/pkg/{goanalysis/replacer => xast}/import.go (99%) rename ignite/pkg/{goanalysis/replacer => xast}/import_test.go (99%) diff --git a/ignite/pkg/goanalysis/replacer/function.go b/ignite/pkg/xast/function.go similarity index 99% rename from ignite/pkg/goanalysis/replacer/function.go rename to ignite/pkg/xast/function.go index 51da59f7cc..c687913939 100644 --- a/ignite/pkg/goanalysis/replacer/function.go +++ b/ignite/pkg/xast/function.go @@ -1,4 +1,4 @@ -package replacer +package xast import ( "bytes" diff --git a/ignite/pkg/goanalysis/replacer/function_test.go b/ignite/pkg/xast/function_test.go similarity index 99% rename from ignite/pkg/goanalysis/replacer/function_test.go rename to ignite/pkg/xast/function_test.go index f79afde7d6..a7eb499654 100644 --- a/ignite/pkg/goanalysis/replacer/function_test.go +++ b/ignite/pkg/xast/function_test.go @@ -1,4 +1,4 @@ -package replacer +package xast import ( "strconv" diff --git a/ignite/pkg/goanalysis/replacer/global.go b/ignite/pkg/xast/global.go similarity index 99% rename from ignite/pkg/goanalysis/replacer/global.go rename to ignite/pkg/xast/global.go index ff209ec040..99457e03e0 100644 --- a/ignite/pkg/goanalysis/replacer/global.go +++ b/ignite/pkg/xast/global.go @@ -1,4 +1,4 @@ -package replacer +package xast import ( "bytes" diff --git a/ignite/pkg/goanalysis/replacer/global_test.go b/ignite/pkg/xast/global_test.go similarity index 99% rename from ignite/pkg/goanalysis/replacer/global_test.go rename to ignite/pkg/xast/global_test.go index 9159642192..66e15b0b11 100644 --- a/ignite/pkg/goanalysis/replacer/global_test.go +++ b/ignite/pkg/xast/global_test.go @@ -1,4 +1,4 @@ -package replacer +package xast import ( "testing" diff --git a/ignite/pkg/goanalysis/replacer/import.go b/ignite/pkg/xast/import.go similarity index 99% rename from ignite/pkg/goanalysis/replacer/import.go rename to ignite/pkg/xast/import.go index 9a0adcdf16..5a3e728b3e 100644 --- a/ignite/pkg/goanalysis/replacer/import.go +++ b/ignite/pkg/xast/import.go @@ -1,4 +1,4 @@ -package replacer +package xast import ( "bytes" diff --git a/ignite/pkg/goanalysis/replacer/import_test.go b/ignite/pkg/xast/import_test.go similarity index 99% rename from ignite/pkg/goanalysis/replacer/import_test.go rename to ignite/pkg/xast/import_test.go index f1c4c2e881..e9f564fbab 100644 --- a/ignite/pkg/goanalysis/replacer/import_test.go +++ b/ignite/pkg/xast/import_test.go @@ -1,4 +1,4 @@ -package replacer +package xast import ( "testing" diff --git a/ignite/templates/module/create/files/base/x/{{moduleName}}/types/params.go.plush b/ignite/templates/module/create/files/base/x/{{moduleName}}/types/params.go.plush index 297f9560f1..b788f28081 100644 --- a/ignite/templates/module/create/files/base/x/{{moduleName}}/types/params.go.plush +++ b/ignite/templates/module/create/files/base/x/{{moduleName}}/types/params.go.plush @@ -6,16 +6,12 @@ package types var Default<%= param.Name.UpperCamel %> <%= param.DataType() %> = <%= if (param.DataType() == "string") { %>"<%= param.Name.Snake %>"<% } else { %><%= param.ValueIndex() %><% } %> <% } %> -// this line is used by starport scaffolding # types/params/vars - // NewParams creates a new Params instance. func NewParams(<%= for (param) in params { %> <%= param.Name.LowerCamel %> <%= param.DataType() %>,<% } %> - // this line is used by starport scaffolding # types/params/new/parameter ) Params { return Params{<%= for (param) in params { %> <%= param.Name.UpperCamel %>: <%= param.Name.LowerCamel %>,<% } %> - // this line is used by starport scaffolding # types/params/new/struct } } @@ -23,7 +19,6 @@ func NewParams(<%= for (param) in params { %> func DefaultParams() Params { return NewParams(<%= for (param) in params { %> Default<%= param.Name.UpperCamel %>,<% } %> - // this line is used by starport scaffolding # types/params/default ) } @@ -34,8 +29,6 @@ func (p Params) Validate() error {<%= for (param) in params { %> } <% } %> - // this line is used by starport scaffolding # types/params/validate - return nil } @@ -46,5 +39,3 @@ func validate<%= param.Name.UpperCamel %>(v <%= param.DataType() %>) error { return nil } <% } %> - -// this line is used by starport scaffolding # types/params/validation \ No newline at end of file diff --git a/ignite/templates/module/create/params.go b/ignite/templates/module/create/params.go index b31f95eeae..81c7f43151 100644 --- a/ignite/templates/module/create/params.go +++ b/ignite/templates/module/create/params.go @@ -9,6 +9,7 @@ import ( "github.com/ignite/cli/v28/ignite/pkg/errors" "github.com/ignite/cli/v28/ignite/pkg/placeholder" "github.com/ignite/cli/v28/ignite/pkg/protoanalysis/protoutil" + "github.com/ignite/cli/v28/ignite/pkg/xast" "github.com/ignite/cli/v28/ignite/templates/module" ) @@ -59,21 +60,29 @@ func paramsTypesModify(replacer placeholder.Replacer, opts ParamsOptions) genny. } content := f.String() + + globalOpts := make([]xast.GlobalOptions, len(opts.Params)) + modifyNewParams := make([]xast.FunctionOptions, len(opts.Params)) + modifyDefaultParams := make([]xast.FunctionOptions, len(opts.Params)) + modifyValidate := make([]xast.FunctionOptions, len(opts.Params)) + //xast.AppendCode() for _, param := range opts.Params { // param key and default value. - templateVars := ` - // Default%[2]v represents the %[2]v default value. - // TODO: Determine the default value. - var Default%[2]v %[3]v = %[4]v - %[1]v` - replacementVars := fmt.Sprintf( - templateVars, - module.PlaceholderParamsVars, - param.Name.UpperCamel, - param.DataType(), - param.Value(), + globalOpts = append( + globalOpts, + xast.WithGlobal(fmt.Sprintf("Default%s", param.Name.UpperCamel), param.DataType(), param.Value()), + ) + + // param key and default value. + content, err = xast.ModifyFunction( + content, + "", + xast.GlobalTypeConst, + xast.WithGlobal(fmt.Sprintf("Default%s", param.Name.UpperCamel), param.DataType(), param.Value()), ) - content = replacer.Replace(content, module.PlaceholderParamsVars, replacementVars) + if err != nil { + return err + } // add parameter to the new method. templateNewParam := "%[2]v %[3]v,\n%[1]v" diff --git a/ignite/templates/module/placeholders.go b/ignite/templates/module/placeholders.go index 6e1add0631..d8cf25041a 100644 --- a/ignite/templates/module/placeholders.go +++ b/ignite/templates/module/placeholders.go @@ -27,12 +27,4 @@ const ( PlaceholderTypesGenesisValidField = "// this line is used by starport scaffolding # types/genesis/validField" PlaceholderGenesisTestState = "// this line is used by starport scaffolding # genesis/test/state" PlaceholderGenesisTestAssert = "// this line is used by starport scaffolding # genesis/test/assert" - - // Params - PlaceholderParamsVars = "// this line is used by starport scaffolding # types/params/vars" - PlaceholderParamsNewParam = "// this line is used by starport scaffolding # types/params/new/parameter" - PlaceholderParamsNewStruct = "// this line is used by starport scaffolding # types/params/new/struct" - PlaceholderParamsDefault = "// this line is used by starport scaffolding # types/params/default" - PlaceholderParamsValidate = "// this line is used by starport scaffolding # types/params/validate" - PlaceholderParamsValidation = "// this line is used by starport scaffolding # types/params/validation" ) From 6b108096fd987aa97a35d9f642f0c44a863191fa Mon Sep 17 00:00:00 2001 From: Pantani Date: Tue, 20 Feb 2024 03:51:30 -0300 Subject: [PATCH 30/35] add append struct param option --- ignite/cmd/chain_lint.go | 3 +- ignite/pkg/xast/function.go | 71 +++++++---- ignite/pkg/xast/function_test.go | 56 ++++----- ignite/pkg/xast/global.go | 42 ++++++- ignite/pkg/xast/global_test.go | 154 +++++++++++++++++++++++ ignite/services/chain/lint_test.go | 1 - ignite/services/scaffolder/params.go | 2 +- ignite/templates/module/create/params.go | 121 +++++++++--------- 8 files changed, 328 insertions(+), 122 deletions(-) diff --git a/ignite/cmd/chain_lint.go b/ignite/cmd/chain_lint.go index f85eb52037..5d23e05087 100644 --- a/ignite/cmd/chain_lint.go +++ b/ignite/cmd/chain_lint.go @@ -1,9 +1,10 @@ package ignitecmd import ( + "github.com/spf13/cobra" + "github.com/ignite/cli/v28/ignite/pkg/cliui" "github.com/ignite/cli/v28/ignite/services/chain" - "github.com/spf13/cobra" ) // NewChainLint returns a lint command to build a blockchain app. diff --git a/ignite/pkg/xast/function.go b/ignite/pkg/xast/function.go index c687913939..3d7e9ad780 100644 --- a/ignite/pkg/xast/function.go +++ b/ignite/pkg/xast/function.go @@ -15,17 +15,24 @@ import ( type ( // functionOpts represent the options for functions. functionOpts struct { - newParams []param - body string - newLines []line - insideCall []call - appendCode []string - returnVars []string + newParams []param + body string + newLines []line + insideCall []call + insideStruct []str + appendCode []string + returnVars []string } // FunctionOptions configures code generation. FunctionOptions func(*functionOpts) + str struct { + structName string + paramName string + code string + index int + } call struct { name string code string @@ -42,8 +49,8 @@ type ( } ) -// AppendParams add a new param value. -func AppendParams(name, varType string, index int) FunctionOptions { +// AppendFuncParams add a new param value. +func AppendFuncParams(name, varType string, index int) FunctionOptions { return func(c *functionOpts) { c.newParams = append(c.newParams, param{ name: name, @@ -53,22 +60,22 @@ func AppendParams(name, varType string, index int) FunctionOptions { } } -// ReplaceBody replace all body of the function, the method will replace first and apply the other options after. -func ReplaceBody(body string) FunctionOptions { +// ReplaceFuncBody replace all body of the function, the method will replace first and apply the other options after. +func ReplaceFuncBody(body string) FunctionOptions { return func(c *functionOpts) { c.body = body } } -// AppendCode append code before the end or the return, if exists, of a function in Go source code content. -func AppendCode(code string) FunctionOptions { +// AppendFuncCode append code before the end or the return, if exists, of a function in Go source code content. +func AppendFuncCode(code string) FunctionOptions { return func(c *functionOpts) { c.appendCode = append(c.appendCode, code) } } -// AppendAtLine append a new code at line. -func AppendAtLine(code string, lineNumber uint64) FunctionOptions { +// AppendFuncAtLine append a new code at line. +func AppendFuncAtLine(code string, lineNumber uint64) FunctionOptions { return func(c *functionOpts) { c.newLines = append(c.newLines, line{ code: code, @@ -77,11 +84,9 @@ func AppendAtLine(code string, lineNumber uint64) FunctionOptions { } } -// InsideCall add code inside another function call. For instances, the method have a parameter a +// AppendInsideFuncCall add code inside another function call. For instances, the method have a parameter a // call 'New(param1, param2)' and we want to add the param3 the result will be 'New(param1, param2, param3)'. -// Or if we have a struct call Params{Param1: param1} and we want to add the param2 the result will -// be Params{Param1: param1, Param2: param2}. -func InsideCall(callName, code string, index int) FunctionOptions { +func AppendInsideFuncCall(callName, code string, index int) FunctionOptions { return func(c *functionOpts) { c.insideCall = append(c.insideCall, call{ name: callName, @@ -91,8 +96,22 @@ func InsideCall(callName, code string, index int) FunctionOptions { } } -// NewReturn replaces return statements in a Go function with a new return statement. -func NewReturn(returnVars ...string) FunctionOptions { +// AppendInsideFuncStruct add code inside another function call. For instances, +// the struct have only one parameter 'Params{Param1: param1}' and we want to add +// the param2 the result will be 'Params{Param1: param1, Param2: param2}'. +func AppendInsideFuncStruct(structName, paramName, code string, index int) FunctionOptions { + return func(c *functionOpts) { + c.insideStruct = append(c.insideStruct, str{ + structName: structName, + paramName: paramName, + code: code, + index: index, + }) + } +} + +// NewFuncReturn replaces return statements in a Go function with a new return statement. +func NewFuncReturn(returnVars ...string) FunctionOptions { return func(c *functionOpts) { c.returnVars = append(c.returnVars, returnVars...) } @@ -100,11 +119,13 @@ func NewReturn(returnVars ...string) FunctionOptions { func newFunctionOptions() functionOpts { return functionOpts{ - newParams: make([]param, 0), - body: "", - newLines: make([]line, 0), - appendCode: make([]string, 0), - returnVars: make([]string, 0), + newParams: make([]param, 0), + body: "", + newLines: make([]line, 0), + insideCall: make([]call, 0), + insideStruct: make([]str, 0), + appendCode: make([]string, 0), + returnVars: make([]string, 0), } } diff --git a/ignite/pkg/xast/function_test.go b/ignite/pkg/xast/function_test.go index a7eb499654..00815e3fd6 100644 --- a/ignite/pkg/xast/function_test.go +++ b/ignite/pkg/xast/function_test.go @@ -45,15 +45,15 @@ func anotherFunction() bool { fileContent: existingContent, functionName: "anotherFunction", functions: []FunctionOptions{ - AppendParams("param1", "string", 0), - ReplaceBody(`return false`), - AppendAtLine(`fmt.Println("Appended at line 0.")`, 0), - AppendAtLine(`SimpleCall(foo, bar)`, 1), - AppendCode(`fmt.Println("Appended code.")`), - NewReturn("1"), - InsideCall("SimpleCall", "baz", 0), - InsideCall("SimpleCall", "bla", -1), - InsideCall("Println", strconv.Quote("test"), -1), + AppendFuncParams("param1", "string", 0), + ReplaceFuncBody(`return false`), + AppendFuncAtLine(`fmt.Println("Appended at line 0.")`, 0), + AppendFuncAtLine(`SimpleCall(foo, bar)`, 1), + AppendFuncCode(`fmt.Println("Appended code.")`), + NewFuncReturn("1"), + AppendInsideFuncCall("SimpleCall", "baz", 0), + AppendInsideFuncCall("SimpleCall", "bla", -1), + AppendInsideFuncCall("Println", strconv.Quote("test"), -1), }, }, want: `package main @@ -80,7 +80,7 @@ func anotherFunction(param1 string) bool { args: args{ fileContent: existingContent, functionName: "anotherFunction", - functions: []FunctionOptions{ReplaceBody(`return false`)}, + functions: []FunctionOptions{ReplaceFuncBody(`return false`)}, }, want: `package main @@ -102,9 +102,9 @@ func anotherFunction() bool { return false } fileContent: existingContent, functionName: "anotherFunction", functions: []FunctionOptions{ - AppendAtLine(`fmt.Println("Appended at line 0.")`, 0), - AppendAtLine(`SimpleCall(foo, bar)`, 1), - AppendCode(`fmt.Println("Appended code.")`), + AppendFuncAtLine(`fmt.Println("Appended at line 0.")`, 0), + AppendFuncAtLine(`SimpleCall(foo, bar)`, 1), + AppendFuncCode(`fmt.Println("Appended code.")`), }, }, want: `package main @@ -135,7 +135,7 @@ func anotherFunction() bool { args: args{ fileContent: existingContent, functionName: "anotherFunction", - functions: []FunctionOptions{NewReturn("1")}, + functions: []FunctionOptions{NewFuncReturn("1")}, }, want: strings.ReplaceAll(existingContent, "return true", "return 1\n") + "\n", }, @@ -145,10 +145,10 @@ func anotherFunction() bool { fileContent: existingContent, functionName: "anotherFunction", functions: []FunctionOptions{ - InsideCall("NewParam", "baz", 0), - InsideCall("NewParam", "bla", -1), - InsideCall("CallSomething", strconv.Quote("test1"), -1), - InsideCall("CallSomething", strconv.Quote("test2"), 0), + AppendInsideFuncCall("NewParam", "baz", 0), + AppendInsideFuncCall("NewParam", "bla", -1), + AppendInsideFuncCall("CallSomething", strconv.Quote("test1"), -1), + AppendInsideFuncCall("CallSomething", strconv.Quote("test2"), 0), }, }, want: `package main @@ -174,7 +174,7 @@ func anotherFunction() bool { args: args{ fileContent: existingContent, functionName: "anotherFunction", - functions: []FunctionOptions{AppendParams("param1", "string", 1)}, + functions: []FunctionOptions{AppendFuncParams("param1", "string", 1)}, }, err: errors.New("params index 1 out of range"), }, @@ -183,7 +183,7 @@ func anotherFunction() bool { args: args{ fileContent: existingContent, functionName: "anotherFunction", - functions: []FunctionOptions{AppendParams("9#.(c", "string", 0)}, + functions: []FunctionOptions{AppendFuncParams("9#.(c", "string", 0)}, }, err: errors.New("format.Node internal error (12:22: expected ')', found 9 (and 1 more errors))"), }, @@ -192,7 +192,7 @@ func anotherFunction() bool { args: args{ fileContent: existingContent, functionName: "anotherFunction", - functions: []FunctionOptions{ReplaceBody("9#.(c")}, + functions: []FunctionOptions{ReplaceFuncBody("9#.(c")}, }, err: errors.New("1:24: illegal character U+0023 '#'"), }, @@ -201,7 +201,7 @@ func anotherFunction() bool { args: args{ fileContent: existingContent, functionName: "anotherFunction", - functions: []FunctionOptions{AppendAtLine(`fmt.Println("")`, 4)}, + functions: []FunctionOptions{AppendFuncAtLine(`fmt.Println("")`, 4)}, }, err: errors.New("line number 4 out of range"), }, @@ -210,7 +210,7 @@ func anotherFunction() bool { args: args{ fileContent: existingContent, functionName: "anotherFunction", - functions: []FunctionOptions{AppendAtLine("9#.(c", 0)}, + functions: []FunctionOptions{AppendFuncAtLine("9#.(c", 0)}, }, err: errors.New("1:2: illegal character U+0023 '#'"), }, @@ -219,7 +219,7 @@ func anotherFunction() bool { args: args{ fileContent: existingContent, functionName: "anotherFunction", - functions: []FunctionOptions{AppendCode("9#.(c")}, + functions: []FunctionOptions{AppendFuncCode("9#.(c")}, }, err: errors.New("1:2: illegal character U+0023 '#'"), }, @@ -228,7 +228,7 @@ func anotherFunction() bool { args: args{ fileContent: existingContent, functionName: "anotherFunction", - functions: []FunctionOptions{NewReturn("9#.(c")}, + functions: []FunctionOptions{NewFuncReturn("9#.(c")}, }, err: errors.New("1:2: illegal character U+0023 '#'"), }, @@ -237,7 +237,7 @@ func anotherFunction() bool { args: args{ fileContent: existingContent, functionName: "anotherFunction", - functions: []FunctionOptions{InsideCall("FooFunction", "baz", 0)}, + functions: []FunctionOptions{AppendInsideFuncCall("FooFunction", "baz", 0)}, }, err: errors.New("function calls not found: map[FooFunction:[{FooFunction baz 0}]]"), }, @@ -246,7 +246,7 @@ func anotherFunction() bool { args: args{ fileContent: existingContent, functionName: "anotherFunction", - functions: []FunctionOptions{InsideCall("NewParam", "9#.(c", 0)}, + functions: []FunctionOptions{AppendInsideFuncCall("NewParam", "9#.(c", 0)}, }, err: errors.New("format.Node internal error (13:21: illegal character U+0023 '#' (and 2 more errors))"), }, @@ -255,7 +255,7 @@ func anotherFunction() bool { args: args{ fileContent: existingContent, functionName: "anotherFunction", - functions: []FunctionOptions{InsideCall("NewParam", "baz", 1)}, + functions: []FunctionOptions{AppendInsideFuncCall("NewParam", "baz", 1)}, }, err: errors.New("function call index 1 out of range"), }, diff --git a/ignite/pkg/xast/global.go b/ignite/pkg/xast/global.go index 99457e03e0..86cc4170cc 100644 --- a/ignite/pkg/xast/global.go +++ b/ignite/pkg/xast/global.go @@ -102,7 +102,7 @@ func InsertGlobal(fileContent string, globalType GlobalType, globals ...GlobalOp // Create a value expression if provided. var valueExpr ast.Expr if global.value != "" { - valueExpr, err = parser.ParseExpr(global.value) + valueExpr, err = parser.ParseExprFrom(fileSet, "", []byte(global.value), parser.ParseComments) if err != nil { return "", err } @@ -138,3 +138,43 @@ func InsertGlobal(fileContent string, globalType GlobalType, globals ...GlobalOp // Return the modified content. return buf.String(), nil } + +// AppendFunction appends a new function to the end of the Go source code content. +func AppendFunction(fileContent string, function string) (modifiedContent string, err error) { + fileSet := token.NewFileSet() + + // Parse the function body as a separate file. + funcFile, err := parser.ParseFile(fileSet, "", "package main\n"+function, parser.AllErrors) + if err != nil { + return "", err + } + + // Extract the first declaration, assuming it's a function declaration. + var funcDecl *ast.FuncDecl + for _, decl := range funcFile.Decls { + if fDecl, ok := decl.(*ast.FuncDecl); ok { + funcDecl = fDecl + break + } + } + if funcDecl == nil { + return "", errors.Errorf("no function declaration found in the provided function body") + } + + // Parse the Go source code content. + f, err := parser.ParseFile(fileSet, "", fileContent, parser.ParseComments) + if err != nil { + return "", err + } + + // Append the function declaration to the file's declarations. + f.Decls = append(f.Decls, funcDecl) + + // Format the modified AST. + var buf bytes.Buffer + if err := format.Node(&buf, fileSet, f); err != nil { + return "", err + } + + return buf.String(), nil +} diff --git a/ignite/pkg/xast/global_test.go b/ignite/pkg/xast/global_test.go index 66e15b0b11..5c6796cb0e 100644 --- a/ignite/pkg/xast/global_test.go +++ b/ignite/pkg/xast/global_test.go @@ -196,3 +196,157 @@ var fooVar foo = 42 }) } } + +func TestAppendFunction(t *testing.T) { + type args struct { + fileContent string + function string + } + tests := []struct { + name string + args args + want string + err error + }{ + { + name: "Append a function after the package declaration", + args: args{ + fileContent: `package main`, + function: `func add(a, b int) int { + return a + b +}`, + }, + want: `package main + +func add(a, b int) int { + return a + b +} +`, + }, + { + name: "Append a function after a var", + args: args{ + fileContent: `package main + +import ( + "fmt" +) + +var myIntVar int = 42 +`, + function: `func add(a, b int) int { + return a + b +}`, + }, + want: `package main + +import ( + "fmt" +) + +var myIntVar int = 42 + +func add(a, b int) int { + return a + b +} +`, + }, + { + name: "Append a function after the import", + args: args{ + fileContent: `package main + +import ( + "fmt" +) +`, + function: `func add(a, b int) int { + return a + b +}`, + }, + want: `package main + +import ( + "fmt" +) + +func add(a, b int) int { + return a + b +} +`, + }, + { + name: "Append a function after another function", + args: args{ + fileContent: `package main + +import ( + "fmt" +) + +var myIntVar int = 42 + +func myFunction() int { + return 42 +} +`, + function: `func add(a, b int) int { + return a + b +}`, + }, + want: `package main + +import ( + "fmt" +) + +var myIntVar int = 42 + +func myFunction() int { + return 42 +} +func add(a, b int) int { + return a + b +} +`, + }, + { + name: "Append a function in an empty file", + args: args{ + fileContent: ``, + function: `func add(a, b int) int { + return a + b +}`, + }, + err: errors.New("1:1: expected 'package', found 'EOF'"), + }, + { + name: "Append a empty function", + args: args{ + fileContent: `package main`, + function: ``, + }, + err: errors.New("no function declaration found in the provided function body"), + }, + { + name: "Append an invalid function", + args: args{ + fileContent: `package main`, + function: `@,.l.e,`, + }, + err: errors.New("2:1: illegal character U+0040 '@'"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := AppendFunction(tt.args.fileContent, tt.args.function) + if tt.err != nil { + require.Error(t, err) + require.Equal(t, tt.err.Error(), err.Error()) + return + } + require.NoError(t, err) + require.Equal(t, tt.want, got) + }) + } +} diff --git a/ignite/services/chain/lint_test.go b/ignite/services/chain/lint_test.go index 840d12223a..dc1f90b240 100644 --- a/ignite/services/chain/lint_test.go +++ b/ignite/services/chain/lint_test.go @@ -3,5 +3,4 @@ package chain_test import "testing" func TestChainLint(t *testing.T) { - } diff --git a/ignite/services/scaffolder/params.go b/ignite/services/scaffolder/params.go index ddbafe3e62..02407d1936 100644 --- a/ignite/services/scaffolder/params.go +++ b/ignite/services/scaffolder/params.go @@ -61,7 +61,7 @@ func (s Scaffolder) CreateParams( AppPath: s.path, } - g, err := modulecreate.NewModuleParam(tracer, opts) + g, err := modulecreate.NewModuleParam(opts) if err != nil { return sm, err } diff --git a/ignite/templates/module/create/params.go b/ignite/templates/module/create/params.go index 81c7f43151..bdad058709 100644 --- a/ignite/templates/module/create/params.go +++ b/ignite/templates/module/create/params.go @@ -7,17 +7,15 @@ import ( "github.com/gobuffalo/genny/v2" "github.com/ignite/cli/v28/ignite/pkg/errors" - "github.com/ignite/cli/v28/ignite/pkg/placeholder" "github.com/ignite/cli/v28/ignite/pkg/protoanalysis/protoutil" "github.com/ignite/cli/v28/ignite/pkg/xast" - "github.com/ignite/cli/v28/ignite/templates/module" ) // NewModuleParam returns the generator to scaffold a new parameter inside a module. -func NewModuleParam(replacer placeholder.Replacer, opts ParamsOptions) (*genny.Generator, error) { +func NewModuleParam(opts ParamsOptions) (*genny.Generator, error) { g := genny.New() g.RunFn(paramsProtoModify(opts)) - g.RunFn(paramsTypesModify(replacer, opts)) + g.RunFn(paramsTypesModify(opts)) return g, nil } @@ -51,7 +49,7 @@ func paramsProtoModify(opts ParamsOptions) genny.RunFn { } } -func paramsTypesModify(replacer placeholder.Replacer, opts ParamsOptions) genny.RunFn { +func paramsTypesModify(opts ParamsOptions) genny.RunFn { return func(r *genny.Runner) error { path := filepath.Join(opts.AppPath, "x", opts.ModuleName, "types/params.go") f, err := r.Disk.Find(path) @@ -59,89 +57,82 @@ func paramsTypesModify(replacer placeholder.Replacer, opts ParamsOptions) genny. return err } - content := f.String() - - globalOpts := make([]xast.GlobalOptions, len(opts.Params)) - modifyNewParams := make([]xast.FunctionOptions, len(opts.Params)) - modifyDefaultParams := make([]xast.FunctionOptions, len(opts.Params)) - modifyValidate := make([]xast.FunctionOptions, len(opts.Params)) - //xast.AppendCode() - for _, param := range opts.Params { - // param key and default value. - globalOpts = append( - globalOpts, - xast.WithGlobal(fmt.Sprintf("Default%s", param.Name.UpperCamel), param.DataType(), param.Value()), - ) - + var ( + content = f.String() + globalOpts = make([]xast.GlobalOptions, len(opts.Params)) + newParamsModifier = make([]xast.FunctionOptions, 0) + defaultParamsModifier = make([]xast.FunctionOptions, len(opts.Params)) + validateModifier = make([]xast.FunctionOptions, len(opts.Params)) + ) + for i, param := range opts.Params { // param key and default value. - content, err = xast.ModifyFunction( - content, - "", - xast.GlobalTypeConst, - xast.WithGlobal(fmt.Sprintf("Default%s", param.Name.UpperCamel), param.DataType(), param.Value()), - ) - if err != nil { - return err - } - - // add parameter to the new method. - templateNewParam := "%[2]v %[3]v,\n%[1]v" - replacementNewParam := fmt.Sprintf( - templateNewParam, - module.PlaceholderParamsNewParam, - param.Name.LowerCamel, + globalOpts[i] = xast.WithGlobal( + fmt.Sprintf("Default%s", param.Name.UpperCamel), param.DataType(), + param.Value(), ) - content = replacer.Replace(content, module.PlaceholderParamsNewParam, replacementNewParam) // add parameter to the struct into the new method. - templateNewStruct := "%[2]v: %[3]v,\n%[1]v" - replacementNewStruct := fmt.Sprintf( - templateNewStruct, - module.PlaceholderParamsNewStruct, - param.Name.UpperCamel, - param.Name.LowerCamel, + newParamsModifier = append( + newParamsModifier, + xast.AppendFuncParams(param.Name.LowerCamel, param.DataType(), -1), + xast.AppendInsideFuncStruct( + "Params", + param.Name.UpperCamel, + param.Name.LowerCamel, + -1, + ), ) - content = replacer.Replace(content, module.PlaceholderParamsNewStruct, replacementNewStruct) // add default parameter. - templateDefault := `Default%[2]v, -%[1]v` - replacementDefault := fmt.Sprintf( - templateDefault, - module.PlaceholderParamsDefault, - param.Name.UpperCamel, + defaultParamsModifier[i] = xast.AppendInsideFuncCall( + "NewParams", + fmt.Sprintf("Default%s", param.Name.UpperCamel), + -1, ) - content = replacer.Replace(content, module.PlaceholderParamsDefault, replacementDefault) // add param field to the validate method. - templateValidate := `if err := validate%[2]v(p.%[2]v); err != nil { - return err - } - %[1]v` replacementValidate := fmt.Sprintf( - templateValidate, - module.PlaceholderParamsValidate, + `if err := validate%[1]v(p.%[1]v); err != nil { return err }`, param.Name.UpperCamel, ) - content = replacer.Replace(content, module.PlaceholderParamsValidate, replacementValidate) + validateModifier[i] = xast.AppendFuncCode(replacementValidate) // add param field to the validate method. - templateValidation := `// validate%[2]v validates the %[2]v parameter. -func validate%[2]v(v %[3]v) error { + templateValidation := `// validate%[1]v validates the %[1]v parameter. +func validate%[1]v(v %[2]v) error { // TODO implement validation return nil -} - -%[1]v` - replacementValidation := fmt.Sprintf( +}` + validationFunc := fmt.Sprintf( templateValidation, - module.PlaceholderParamsValidation, param.Name.UpperCamel, param.DataType(), ) - content = replacer.Replace(content, module.PlaceholderParamsValidation, replacementValidation) + content, err = xast.AppendFunction(content, validationFunc) + if err != nil { + return err + } + } + + content, err = xast.InsertGlobal(content, xast.GlobalTypeConst, globalOpts...) + if err != nil { + return err + } + content, err = xast.ModifyFunction(content, "NewParams", newParamsModifier...) + if err != nil { + return err + } + + content, err = xast.ModifyFunction(content, "DefaultParams", defaultParamsModifier...) + if err != nil { + return err + } + + content, err = xast.ModifyFunction(content, "Validate", validateModifier...) + if err != nil { + return err } newFile := genny.NewFileS(path, content) From f229ede87b7f5c7a83ce871e02b3033366d55d2c Mon Sep 17 00:00:00 2001 From: Pantani Date: Tue, 20 Feb 2024 15:49:37 -0300 Subject: [PATCH 31/35] add support to add new struct params --- ignite/pkg/xast/function.go | 118 +++++++++++++++++++++++-------- ignite/pkg/xast/function_test.go | 46 ++++++++++++ ignite/pkg/xast/global.go | 2 +- ignite/pkg/xast/import.go | 4 +- 4 files changed, 135 insertions(+), 35 deletions(-) diff --git a/ignite/pkg/xast/function.go b/ignite/pkg/xast/function.go index 3d7e9ad780..c5a6fd417e 100644 --- a/ignite/pkg/xast/function.go +++ b/ignite/pkg/xast/function.go @@ -188,6 +188,17 @@ func ModifyFunction(fileContent, functionName string, functions ...FunctionOptio callMapCheck[c.name] = append(calls, c) } + structMap := make(map[string][]str) + structMapCheck := make(map[string][]str) + for _, s := range opts.insideStruct { + structs, ok := structMap[s.structName] + if !ok { + structs = []str{} + } + structMap[s.structName] = append(structs, s) + structMapCheck[s.structName] = append(structs, s) + } + // Parse the Go code to insert. var ( found bool @@ -285,42 +296,83 @@ func ModifyFunction(fileContent, functionName string, functions ...FunctionOptio // Add new code to the function callers. ast.Inspect(funcDecl, func(n ast.Node) bool { - callExpr, ok := n.(*ast.CallExpr) - if !ok { - return true - } - // Check if the call expression matches the function call name - name := "" - switch exp := callExpr.Fun.(type) { - case *ast.Ident: - name = exp.Name - case *ast.SelectorExpr: - name = exp.Sel.Name - default: - return true - } + switch expr := n.(type) { + case *ast.CallExpr: // Add a new parameter to a function call. + // Check if the call expression matches the function call name. + name := "" + switch exp := expr.Fun.(type) { + case *ast.Ident: + name = exp.Name + case *ast.SelectorExpr: + name = exp.Sel.Name + default: + return true + } - calls, ok := callMap[name] - if !ok { - return true - } + calls, ok := callMap[name] + if !ok { + return true + } - // Construct the new argument to be added - for _, c := range calls { - newArg := &ast.Ident{Name: c.code} - switch { - case c.index == -1: - // Append the new argument to the end - callExpr.Args = append(callExpr.Args, newArg) - case c.index >= 0 && c.index <= len(callExpr.Args): - // Insert the new argument at the specified index - callExpr.Args = append(callExpr.Args[:c.index], append([]ast.Expr{newArg}, callExpr.Args[c.index:]...)...) + // Construct the new argument to be added + for _, c := range calls { + newArg := ast.NewIdent(c.code) + switch { + case c.index == -1: + // Append the new argument to the end + expr.Args = append(expr.Args, newArg) + case c.index >= 0 && c.index <= len(expr.Args): + // Insert the new argument at the specified index + expr.Args = append(expr.Args[:c.index], append([]ast.Expr{newArg}, expr.Args[c.index:]...)...) + default: + errInspect = errors.Errorf("function call index %d out of range", c.index) + return false // Stop the inspection, an error occurred + } + } + delete(callMapCheck, name) + case *ast.CompositeLit: // Add a new parameter to a literal struct. + // Check if the call expression matches the function call name. + name := "" + switch exp := expr.Type.(type) { + case *ast.Ident: + name = exp.Name + case *ast.SelectorExpr: + name = exp.Sel.Name default: - errInspect = errors.Errorf("function call index %d out of range", c.index) - return false // Stop the inspection, an error occurred + return true + } + + structs, ok := structMap[name] + if !ok { + return true + } + + // Construct the new argument to be added + for _, s := range structs { + var newArg ast.Expr = ast.NewIdent(s.code) + if s.paramName != "" { + newArg = &ast.KeyValueExpr{ + Key: ast.NewIdent(s.paramName), + Value: ast.NewIdent(s.code), + } + } + + switch { + case s.index == -1: + // Append the new argument to the end + expr.Elts = append(expr.Elts, newArg) + case s.index >= 0 && s.index <= len(expr.Elts): + // Insert the new argument at the specified index + expr.Elts = append(expr.Elts[:s.index], append([]ast.Expr{newArg}, expr.Elts[s.index:]...)...) + default: + errInspect = errors.Errorf("function call index %d out of range", s.index) + return false // Stop the inspection, an error occurred + } } + delete(structMapCheck, name) + default: + return true } - delete(callMapCheck, name) return true // Continue the inspection for duplicated calls }) if errInspect != nil { @@ -330,6 +382,10 @@ func ModifyFunction(fileContent, functionName string, functions ...FunctionOptio errInspect = errors.Errorf("function calls not found: %v", callMapCheck) return false } + if len(structMapCheck) > 0 { + errInspect = errors.Errorf("function structs not found: %v", structMapCheck) + return false + } // everything is ok, mark as found and stop the inspect found = true diff --git a/ignite/pkg/xast/function_test.go b/ignite/pkg/xast/function_test.go index 00815e3fd6..41cd714c30 100644 --- a/ignite/pkg/xast/function_test.go +++ b/ignite/pkg/xast/function_test.go @@ -50,10 +50,12 @@ func anotherFunction() bool { AppendFuncAtLine(`fmt.Println("Appended at line 0.")`, 0), AppendFuncAtLine(`SimpleCall(foo, bar)`, 1), AppendFuncCode(`fmt.Println("Appended code.")`), + AppendFuncCode(`Param{Baz: baz, Foo: foo}`), NewFuncReturn("1"), AppendInsideFuncCall("SimpleCall", "baz", 0), AppendInsideFuncCall("SimpleCall", "bla", -1), AppendInsideFuncCall("Println", strconv.Quote("test"), -1), + AppendInsideFuncStruct("Param", "Bar", strconv.Quote("bar"), -1), }, }, want: `package main @@ -71,6 +73,7 @@ func anotherFunction(param1 string) bool { fmt.Println("Appended at line 0.", "test") SimpleCall(baz, foo, bar, bla) fmt.Println("Appended code.", "test") + Param{Baz: baz, Foo: foo, Bar: "bar"} return 1 } `, @@ -167,6 +170,40 @@ func anotherFunction() bool { p.CallSomething("test2", "Another call", "test1") return true } +`, + }, + { + name: "add inside struct modifications", + args: args{ + fileContent: `package main + +import ( + "fmt" +) + +func anotherFunction() bool { + Param{Baz: baz, Foo: foo} + Client{baz, foo} + return true +}`, + functionName: "anotherFunction", + functions: []FunctionOptions{ + AppendInsideFuncStruct("Param", "Bar", "bar", -1), + AppendInsideFuncStruct("Param", "Bla", "bla", 1), + AppendInsideFuncStruct("Client", "", "bar", 0), + }, + }, + want: `package main + +import ( + "fmt" +) + +func anotherFunction() bool { + Param{Baz: baz, Bla: bla, Foo: foo, Bar: bar} + Client{bar, baz, foo} + return true +} `, }, { @@ -259,6 +296,15 @@ func anotherFunction() bool { }, err: errors.New("function call index 1 out of range"), }, + { + name: "empty modifications", + args: args{ + fileContent: existingContent, + functionName: "anotherFunction", + functions: []FunctionOptions{}, + }, + want: existingContent + "\n", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/ignite/pkg/xast/global.go b/ignite/pkg/xast/global.go index 86cc4170cc..6053b529b5 100644 --- a/ignite/pkg/xast/global.go +++ b/ignite/pkg/xast/global.go @@ -97,7 +97,7 @@ func InsertGlobal(fileContent string, globalType GlobalType, globals ...GlobalOp // Create global variable/constant declarations. for _, global := range opts.globals { // Create an identifier for the global. - ident := &ast.Ident{Name: global.name} + ident := ast.NewIdent(global.name) // Create a value expression if provided. var valueExpr ast.Expr diff --git a/ignite/pkg/xast/import.go b/ignite/pkg/xast/import.go index 5a3e728b3e..2473311c4a 100644 --- a/ignite/pkg/xast/import.go +++ b/ignite/pkg/xast/import.go @@ -98,9 +98,7 @@ func AppendImports(fileContent string, imports ...ImportOptions) (string, error) } // Create a new import spec. spec := &ast.ImportSpec{ - Name: &ast.Ident{ - Name: importStmt.name, - }, + Name: ast.NewIdent(importStmt.name), Path: &ast.BasicLit{ Kind: token.STRING, Value: path, From 75cf0f82d285461dcdddd501f9e6dc9493df9e02 Mon Sep 17 00:00:00 2001 From: Pantani Date: Tue, 20 Feb 2024 15:54:25 -0300 Subject: [PATCH 32/35] fix some lint issues --- ignite/pkg/xast/global.go | 7 ++++--- ignite/pkg/xast/import.go | 5 +++-- ignite/services/chain/lint.go | 3 ++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/ignite/pkg/xast/global.go b/ignite/pkg/xast/global.go index 6053b529b5..08b3abf07f 100644 --- a/ignite/pkg/xast/global.go +++ b/ignite/pkg/xast/global.go @@ -86,11 +86,12 @@ func InsertGlobal(fileContent string, globalType GlobalType, globals ...GlobalOp // Determine the declaration type based on GlobalType. var declType string - if globalType == GlobalTypeVar { + switch globalType { + case GlobalTypeVar: declType = "var" - } else if globalType == GlobalTypeConst { + case GlobalTypeConst: declType = "const" - } else { + default: return "", errors.Errorf("unsupported global type: %d", globalType) } diff --git a/ignite/pkg/xast/import.go b/ignite/pkg/xast/import.go index 2473311c4a..08942c5bb5 100644 --- a/ignite/pkg/xast/import.go +++ b/ignite/pkg/xast/import.go @@ -2,12 +2,13 @@ package xast import ( "bytes" - "fmt" "go/ast" "go/format" "go/parser" "go/token" "strconv" + + "github.com/ignite/cli/v28/ignite/pkg/errors" ) type ( @@ -113,7 +114,7 @@ func AppendImports(fileContent string, imports ...ImportOptions) (string, error) // Insert the new argument at the specified index importDecl.Specs = append(importDecl.Specs[:importStmt.index], append([]ast.Spec{spec}, importDecl.Specs[importStmt.index:]...)...) default: - return "", fmt.Errorf("index out of range") // Stop the inspection, an error occurred + return "", errors.Errorf("index out of range") // Stop the inspection, an error occurred } } diff --git a/ignite/services/chain/lint.go b/ignite/services/chain/lint.go index 7e33bd6e97..5ad44fbf71 100644 --- a/ignite/services/chain/lint.go +++ b/ignite/services/chain/lint.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/ignite/cli/v28/ignite/pkg/cmdrunner/exec" + "github.com/ignite/cli/v28/ignite/pkg/errors" ) var golangCiLintVersion = "latest" @@ -13,7 +14,7 @@ var golangCiLintVersion = "latest" // It uses golangci-lint to lint the chain's codebase. func (c *Chain) Lint(ctx context.Context) error { if err := exec.Exec(ctx, []string{"go", "install", fmt.Sprintf("github.com/golangci/golangci-lint/cmd/golangci-lint@%s", golangCiLintVersion)}); err != nil { - return fmt.Errorf("failed to install golangci-lint: %w", err) + return errors.Errorf("failed to install golangci-lint: %w", err) } return exec.Exec(ctx, []string{"golangci-lint", "run", "./...", "--out-format=tab"}, exec.IncludeStdLogsToError()) } From e7976343dd6468c23fbeacf71f6f3a4b6ef63d8e Mon Sep 17 00:00:00 2001 From: Pantani Date: Tue, 20 Feb 2024 16:39:04 -0300 Subject: [PATCH 33/35] add small fixes and improve code readbility --- ignite/pkg/xast/function.go | 4 +++- ignite/pkg/xast/global.go | 34 +++++++++++++++++----------------- ignite/pkg/xast/global_test.go | 6 +++--- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/ignite/pkg/xast/function.go b/ignite/pkg/xast/function.go index c5a6fd417e..79d1511d1b 100644 --- a/ignite/pkg/xast/function.go +++ b/ignite/pkg/xast/function.go @@ -213,7 +213,7 @@ func ModifyFunction(fileContent, functionName string, functions ...FunctionOptio for _, p := range opts.newParams { fieldParam := &ast.Field{ Names: []*ast.Ident{ast.NewIdent(p.name)}, - Type: &ast.Ident{Name: p.varType}, + Type: ast.NewIdent(p.varType), } switch { case p.index == -1: @@ -317,6 +317,7 @@ func ModifyFunction(fileContent, functionName string, functions ...FunctionOptio // Construct the new argument to be added for _, c := range calls { newArg := ast.NewIdent(c.code) + newArg.NamePos = token.Pos(c.index) switch { case c.index == -1: // Append the new argument to the end @@ -353,6 +354,7 @@ func ModifyFunction(fileContent, functionName string, functions ...FunctionOptio if s.paramName != "" { newArg = &ast.KeyValueExpr{ Key: ast.NewIdent(s.paramName), + Colon: token.Pos(s.index), Value: ast.NewIdent(s.code), } } diff --git a/ignite/pkg/xast/global.go b/ignite/pkg/xast/global.go index 08b3abf07f..4be31ff807 100644 --- a/ignite/pkg/xast/global.go +++ b/ignite/pkg/xast/global.go @@ -24,12 +24,12 @@ type ( } // GlobalType represents the global type. - GlobalType uint8 + GlobalType string ) const ( - GlobalTypeVar GlobalType = iota - GlobalTypeConst + GlobalTypeVar GlobalType = "var" + GlobalTypeConst GlobalType = "const" ) // WithGlobal add a new global. @@ -84,17 +84,6 @@ func InsertGlobal(fileContent string, globalType GlobalType, globals ...GlobalOp } } - // Determine the declaration type based on GlobalType. - var declType string - switch globalType { - case GlobalTypeVar: - declType = "var" - case GlobalTypeConst: - declType = "const" - default: - return "", errors.Errorf("unsupported global type: %d", globalType) - } - // Create global variable/constant declarations. for _, global := range opts.globals { // Create an identifier for the global. @@ -111,22 +100,33 @@ func InsertGlobal(fileContent string, globalType GlobalType, globals ...GlobalOp // Create a declaration based on the global type. var spec ast.Spec - if declType == "var" { + switch globalType { + case GlobalTypeVar: spec = &ast.ValueSpec{ Names: []*ast.Ident{ident}, Type: ast.NewIdent(global.varType), Values: []ast.Expr{valueExpr}, } - } else if declType == "const" { + case GlobalTypeConst: spec = &ast.ValueSpec{ Names: []*ast.Ident{ident}, Type: ast.NewIdent(global.varType), Values: []ast.Expr{valueExpr}, } + default: + return "", errors.Errorf("unsupported global type: %s", string(globalType)) } // Insert the declaration after the import section or package declaration if no imports. - f.Decls = append(f.Decls[:insertIndex], append([]ast.Decl{&ast.GenDecl{Tok: token.Lookup(declType), Specs: []ast.Spec{spec}}}, f.Decls[insertIndex:]...)...) + f.Decls = append( + f.Decls[:insertIndex], + append([]ast.Decl{ + &ast.GenDecl{ + TokPos: 1, + Tok: token.Lookup(string(globalType)), + Specs: []ast.Spec{spec}, + }, + }, f.Decls[insertIndex:]...)...) insertIndex++ } diff --git a/ignite/pkg/xast/global_test.go b/ignite/pkg/xast/global_test.go index 5c6796cb0e..5ea4d9f259 100644 --- a/ignite/pkg/xast/global_test.go +++ b/ignite/pkg/xast/global_test.go @@ -175,12 +175,12 @@ var fooVar foo = 42 name: "Insert an invalid type", args: args{ fileContent: `package main`, - globalType: 102, + globalType: "invalid", globals: []GlobalOptions{ - WithGlobal("myInvalidVar", "invalid", "AEF#3fa."), + WithGlobal("fooVar", "foo", "42"), }, }, - err: errors.New("unsupported global type: 102"), + err: errors.New("unsupported global type: invalid"), }, } for _, tt := range tests { From df6f165a6e47d3c0a9aaaa092682e9fcc7aa82f6 Mon Sep 17 00:00:00 2001 From: Pantani Date: Tue, 20 Feb 2024 16:44:07 -0300 Subject: [PATCH 34/35] fix append code parser --- ignite/pkg/xast/function.go | 5 +++-- ignite/pkg/xast/function_test.go | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ignite/pkg/xast/function.go b/ignite/pkg/xast/function.go index 79d1511d1b..3469a32f68 100644 --- a/ignite/pkg/xast/function.go +++ b/ignite/pkg/xast/function.go @@ -159,11 +159,12 @@ func ModifyFunction(fileContent, functionName string, functions ...FunctionOptio // Parse the content of the append code an ast. appendCode := make([]ast.Stmt, 0) for _, codeToInsert := range opts.appendCode { - insertionExpr, err := parser.ParseExprFrom(fileSet, "", []byte(codeToInsert), parser.ParseComments) + newFuncContent := fmt.Sprintf("package p; func _() { %s }", strings.TrimSpace(codeToInsert)) + newContent, err := parser.ParseFile(fileSet, "", newFuncContent, parser.ParseComments) if err != nil { return "", err } - appendCode = append(appendCode, &ast.ExprStmt{X: insertionExpr}) + appendCode = append(appendCode, newContent.Decls[0].(*ast.FuncDecl).Body.List...) } // Parse the content of the return vars into an ast. diff --git a/ignite/pkg/xast/function_test.go b/ignite/pkg/xast/function_test.go index 41cd714c30..1b40988bae 100644 --- a/ignite/pkg/xast/function_test.go +++ b/ignite/pkg/xast/function_test.go @@ -258,7 +258,7 @@ func anotherFunction() bool { functionName: "anotherFunction", functions: []FunctionOptions{AppendFuncCode("9#.(c")}, }, - err: errors.New("1:2: illegal character U+0023 '#'"), + err: errors.New("1:24: illegal character U+0023 '#'"), }, { name: "invalid new return", From 5798289a1edd462f3cfa6a902786c7bd1967d7ea Mon Sep 17 00:00:00 2001 From: Pantani Date: Tue, 20 Feb 2024 19:08:54 -0300 Subject: [PATCH 35/35] fix changelog --- changelog.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/changelog.md b/changelog.md index 85a5212944..c9c7af18fd 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,7 @@ ### Features - [#3977](https://github.com/ignite/cli/pull/3977) Add `chain lint` command to lint the chain's codebase using `golangci-lint` +- [#3770](https://github.com/ignite/cli/pull/3770) Add `scaffold configs` and `scaffold params` commands ### Changes @@ -39,10 +40,6 @@ - [#3905](https://github.com/ignite/cli/pull/3905) Fix `ignite completion` - [#3931](https://github.com/ignite/cli/pull/3931) Fix `app update` command and duplicated apps -### Changes - -- [#3770](https://github.com/ignite/cli/pull/3770) Add `scaffold configs` and `scaffold params` commands - ## [`v28.1.1`](https://github.com/ignite/cli/releases/tag/v28.1.1) ### Fixes