Some checks failed
Docs Deploy / build_and_deploy (push) Has been cancelled
Generate Docs / cli (push) Has been cancelled
Generate Config Doc / cli (push) Has been cancelled
Go formatting / go-formatting (push) Has been cancelled
Check links / markdown-link-check (push) Has been cancelled
Integration / pre-test (push) Has been cancelled
Integration / test on (push) Has been cancelled
Integration / status (push) Has been cancelled
Lint / Lint Go code (push) Has been cancelled
Test / test (ubuntu-latest) (push) Has been cancelled
274 lines
7.2 KiB
Go
274 lines
7.2 KiB
Go
package scaffolder
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"git.cw.tr/mukan-network/mukan-ignite/ignite/pkg/errors"
|
|
"git.cw.tr/mukan-network/mukan-ignite/ignite/pkg/multiformatname"
|
|
"git.cw.tr/mukan-network/mukan-ignite/ignite/pkg/protoanalysis"
|
|
"git.cw.tr/mukan-network/mukan-ignite/ignite/templates/field/datatype"
|
|
)
|
|
|
|
const (
|
|
componentType = "type"
|
|
componentMessage = "message"
|
|
componentQuery = "query"
|
|
componentPacket = "packet"
|
|
)
|
|
|
|
// checkComponentValidity performs various checks common to all components to verify if it can be scaffolded.
|
|
func checkComponentValidity(appPath, moduleName string, compName multiformatname.Name, noMessage bool) error {
|
|
ok, err := moduleExists(appPath, moduleName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !ok {
|
|
return errors.Errorf("the module %s doesn't exist", moduleName)
|
|
}
|
|
|
|
// Ensure the name is valid, otherwise it would generate an incorrect code
|
|
if err := checkForbiddenComponentName(compName); err != nil {
|
|
return errors.Errorf("%s can't be used as a component name: %w", compName.LowerCamel, err)
|
|
}
|
|
|
|
// Check component name is not already used
|
|
return checkComponentCreated(appPath, moduleName, compName, noMessage)
|
|
}
|
|
|
|
// checkComponentCreated checks if the component has been already created with Ignite in the project.
|
|
func checkComponentCreated(appPath, moduleName string, compName multiformatname.Name, noMessage bool) (err error) {
|
|
// associate the type to check with the component that scaffold this type
|
|
typesToCheck := map[string]string{
|
|
compName.UpperCamel: componentType,
|
|
fmt.Sprintf("queryall%srequest", compName.LowerCase): componentType,
|
|
fmt.Sprintf("queryall%sresponse", compName.LowerCase): componentType,
|
|
fmt.Sprintf("queryget%srequest", compName.LowerCase): componentType,
|
|
fmt.Sprintf("queryget%sresponse", compName.LowerCase): componentType,
|
|
fmt.Sprintf("query%srequest", compName.LowerCase): componentQuery,
|
|
fmt.Sprintf("query%sresponse", compName.LowerCase): componentQuery,
|
|
fmt.Sprintf("%spacketdata", compName.LowerCase): componentPacket,
|
|
}
|
|
|
|
if !noMessage {
|
|
typesToCheck[fmt.Sprintf("msgcreate%s", compName.LowerCase)] = componentType
|
|
typesToCheck[fmt.Sprintf("msgupdate%s", compName.LowerCase)] = componentType
|
|
typesToCheck[fmt.Sprintf("msgdelete%s", compName.LowerCase)] = componentType
|
|
typesToCheck[fmt.Sprintf("msg%s", compName.LowerCase)] = componentMessage
|
|
typesToCheck[fmt.Sprintf("msgsend%s", compName.LowerCase)] = componentPacket
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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 {
|
|
return true
|
|
}
|
|
|
|
// Check if the parsed type is from a scaffolded component with the name
|
|
if compType, ok := typesToCheck[strings.ToLower(typeSpec.Name.Name)]; ok {
|
|
err = errors.Errorf("component %s with name %s is already created (type %s exists)",
|
|
compType,
|
|
compName.Original,
|
|
typeSpec.Name.Name,
|
|
)
|
|
return false
|
|
}
|
|
|
|
return true
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
// checkTypeProtoCreated checks if the proto type already exists in the module proto package.
|
|
func checkTypeProtoCreated(
|
|
ctx context.Context,
|
|
appPath, appName, protoDir, moduleName string,
|
|
compName multiformatname.Name,
|
|
) error {
|
|
path := filepath.Join(appPath, protoDir, appName, moduleName)
|
|
pkgs, err := protoanalysis.Parse(ctx, protoanalysis.NewCache(), path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, pkg := range pkgs {
|
|
for _, msg := range pkg.Messages {
|
|
if !strings.EqualFold(msg.Name, compName.PascalCase) {
|
|
continue
|
|
}
|
|
|
|
return errors.Errorf("component %s with name %s is already created (type %s exists)",
|
|
componentType,
|
|
compName.Original,
|
|
msg.Name,
|
|
)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// checkCustomTypes returns error if one of the types is invalid.
|
|
func checkCustomTypes(ctx context.Context, appPath, appName, protoDir, module string, fields []string) error {
|
|
path := filepath.Join(appPath, protoDir, appName, module)
|
|
customFieldTypes := make([]string, 0)
|
|
for _, field := range fields {
|
|
ft, ok := fieldType(field)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
customType, ok := customFieldType(ft)
|
|
if ok {
|
|
customFieldTypes = append(customFieldTypes, customType)
|
|
}
|
|
}
|
|
return protoanalysis.HasMessages(ctx, path, customFieldTypes...)
|
|
}
|
|
|
|
// checkForbiddenComponentName returns true if the name is forbidden as a component name.
|
|
func checkForbiddenComponentName(name multiformatname.Name) error {
|
|
// Check with names already used from the scaffolded code
|
|
switch name.LowerCase {
|
|
case
|
|
"logger",
|
|
"keeper",
|
|
"query",
|
|
"genesis",
|
|
"types",
|
|
"tx",
|
|
datatype.TypeCustom:
|
|
return errors.Errorf("%s is used by Ignite scaffolder", name.LowerCamel)
|
|
}
|
|
|
|
if strings.HasSuffix(name.LowerCase, "test") {
|
|
return errors.New(`name cannot end with "test"`)
|
|
}
|
|
|
|
return checkGoReservedWord(name.LowerCamel)
|
|
}
|
|
|
|
// checkGoReservedWord checks if the name can't be used because it is a go reserved keyword.
|
|
func checkGoReservedWord(name string) error {
|
|
// Check keyword or literal
|
|
if token.Lookup(name).IsKeyword() {
|
|
return errors.Errorf("%s is a Go keyword", name)
|
|
}
|
|
|
|
// Check with builtin identifier
|
|
switch name {
|
|
case
|
|
"panic",
|
|
"recover",
|
|
"append",
|
|
"bool",
|
|
"byte",
|
|
"cap",
|
|
"close",
|
|
"complex",
|
|
"complex64",
|
|
"complex128",
|
|
"uint16",
|
|
"copy",
|
|
"false",
|
|
"float32",
|
|
"float64",
|
|
"imag",
|
|
"int",
|
|
"int8",
|
|
"int16",
|
|
"uint32",
|
|
"int32",
|
|
"int64",
|
|
"iota",
|
|
"len",
|
|
"make",
|
|
"new",
|
|
"nil",
|
|
"uint64",
|
|
"print",
|
|
"println",
|
|
"real",
|
|
"string",
|
|
"true",
|
|
"uint",
|
|
"uint8",
|
|
"uintptr":
|
|
return errors.Errorf("%s is a Go built-in identifier", name)
|
|
}
|
|
return checkMaxLength(name)
|
|
}
|
|
|
|
// containsCustomTypes returns true if the list of fields contains at least one custom type.
|
|
func containsCustomTypes(fields []string) bool {
|
|
for _, field := range fields {
|
|
ft, ok := fieldType(field)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
if _, ok := customFieldType(ft); ok {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// checks if a field is given. Returns type if true.
|
|
func fieldType(field string) (fieldType string, isCustom bool) {
|
|
fieldSplit := strings.Split(field, datatype.Separator)
|
|
if len(fieldSplit) <= 1 {
|
|
return "", false
|
|
}
|
|
|
|
return fieldSplit[1], true
|
|
}
|
|
|
|
// customFieldType checks whether a field type is a custom type and returns its normalized message name.
|
|
func customFieldType(fieldType string) (name string, isCustom bool) {
|
|
if _, ok := datatype.IsSupportedType(datatype.Name(fieldType)); ok {
|
|
return "", false
|
|
}
|
|
|
|
if strings.HasPrefix(fieldType, datatype.ArrayPrefix) {
|
|
return normalizeCustomTypeName(strings.TrimPrefix(fieldType, datatype.ArrayPrefix)), true
|
|
}
|
|
|
|
return normalizeCustomTypeName(fieldType), true
|
|
}
|
|
|
|
func normalizeCustomTypeName(customType string) string {
|
|
name, err := multiformatname.NewName(customType)
|
|
if err != nil {
|
|
return customType
|
|
}
|
|
|
|
return name.UpperCamel
|
|
}
|