From b222a448333627c5342aada32c59c2d2080eda71 Mon Sep 17 00:00:00 2001 From: GogaCoder Date: Sun, 29 Dec 2024 23:49:46 +0700 Subject: [PATCH] structs for models --- common/model.go | 22 ++++++++ common/normalizeStructTag.go | 9 +++ common/nullSafetyCheck.go | 6 +- nullSafetyCheck/nullSafetyCheck.go | 4 +- referencesCheck/referencesCheck.go | 91 ++++++++++++++++++++++++++++++ 5 files changed, 127 insertions(+), 5 deletions(-) diff --git a/common/model.go b/common/model.go index 805d0c7..16d6577 100644 --- a/common/model.go +++ b/common/model.go @@ -1 +1,23 @@ package common + +import ( + "go/ast" + "go/token" +) + +type Field struct { + Name string + Type ast.Expr + Tags *string + Options []string // contains options like "autoCreateTime" or "null" + Params []string // contains params like "foreignKey:CustomerId" or "constrain:OnDelete:Cascade" + Position token.Pos + Comment string +} + +type Model struct { + Name string + Fields map[string]Field + Position token.Pos + Comment string +} diff --git a/common/normalizeStructTag.go b/common/normalizeStructTag.go index 805d0c7..b825b49 100644 --- a/common/normalizeStructTag.go +++ b/common/normalizeStructTag.go @@ -1 +1,10 @@ package common + +import "strings" + +func NormalizeStructTags(tags string) string { + // todo: process case with check with ';' literal + tagWithoutQuotes := tags[1 : len(tags)-1] + tagWithoutSemicolons := strings.ReplaceAll(tagWithoutQuotes, ";", ",") + return tagWithoutSemicolons +} diff --git a/common/nullSafetyCheck.go b/common/nullSafetyCheck.go index 1306589..5838fe7 100644 --- a/common/nullSafetyCheck.go +++ b/common/nullSafetyCheck.go @@ -21,12 +21,12 @@ func isGormValueNullable(tags *structtag.Tags) (*bool, error) { return nil, nil } - gormTag.Options = append([]string{gormTag.Name}, gormTag.Options...) - if err != nil { return nil, nil } + gormTag.Options = append([]string{gormTag.Name}, gormTag.Options...) + nullTagExist := gormTag.HasOption("null") notNullTagExist := gormTag.HasOption("not null") @@ -47,8 +47,8 @@ func CheckFieldNullConsistency(field ast.Field, structName string, structTags st tags, err := structtag.Parse(structTags) if err != nil { return errors.New(fmt.Sprintf("Invalid structure tag: %s", err)) - } + if tags == nil { return nil } diff --git a/nullSafetyCheck/nullSafetyCheck.go b/nullSafetyCheck/nullSafetyCheck.go index 0960784..a4e9573 100644 --- a/nullSafetyCheck/nullSafetyCheck.go +++ b/nullSafetyCheck/nullSafetyCheck.go @@ -1,4 +1,4 @@ -package analyzers +package nullSafetyCheck import ( "go/ast" @@ -7,6 +7,7 @@ import ( "strings" ) +// NullSafetyAnalyzer todo: add URL for null safety analyzer rules var NullSafetyAnalyzer = &analysis.Analyzer{ Name: "gormNullSafety", Doc: "reports problems with nullable fields with unsatisfied tag", @@ -24,7 +25,6 @@ func run(pass *analysis.Pass) (any, error) { if !ok { return true } - pass.Fset.Position(structure.Pos()) if err := common.CheckUnnamedModel(*typeSpec); err != nil { pass.Reportf(structure.Pos(), err.Error()) diff --git a/referencesCheck/referencesCheck.go b/referencesCheck/referencesCheck.go index 66fa8e8..f1eb462 100644 --- a/referencesCheck/referencesCheck.go +++ b/referencesCheck/referencesCheck.go @@ -1 +1,92 @@ package referencesCheck + +import ( + "github.com/fatih/structtag" + "go/ast" + "golang.org/x/tools/go/analysis" + "gormlint/common" + "strings" +) + +// ReferenceAnalyzer todo: add URL for rule +var ReferenceAnalyzer = &analysis.Analyzer{ + Name: "gormReferencesCheck", + Doc: "report about invalid references in models", + Run: run, +} + +var models map[string]common.Model + +func init() { + models = make(map[string]common.Model) +} + +func run(pass *analysis.Pass) (any, error) { + for _, file := range pass.Files { + ast.Inspect(file, func(node ast.Node) bool { + typeSpec, ok := node.(*ast.TypeSpec) + if !ok { + return true + } + structure, ok := typeSpec.Type.(*ast.StructType) + if !ok { + return true + } + + if err := common.CheckUnnamedModel(*typeSpec); err != nil { + pass.Reportf(structure.Pos(), err.Error()) + return false + } + + var model common.Model + model.Name = typeSpec.Name.Name + model.Comment = typeSpec.Comment.Text() + model.Position = structure.Pos() + model.Fields = make(map[string]common.Field) + + for _, field := range structure.Fields.List { + var structField common.Field + if err := common.CheckUnnamedField(typeSpec.Name.Name, *field); err != nil { + pass.Reportf(field.Pos(), err.Error()) + return false + } + structField.Name = field.Names[0].Name + structField.Position = field.Pos() + structField.Comment = field.Comment.Text() + structField.Type = field.Type + if field.Tag != nil { + structField.Tags = &field.Tag.Value + + tags, err := structtag.Parse(common.NormalizeStructTags(field.Tag.Value)) + if err != nil { + pass.Reportf(field.Pos(), "Invalid structure tag: %s\n", err) + return false + } + if tags != nil { + gormTag, parseErr := tags.Get("gorm") + if gormTag != nil && parseErr == nil { + gormTag.Options = append([]string{gormTag.Name}, gormTag.Options...) + for _, opt := range gormTag.Options { + if strings.Contains(opt, ":") { + structField.Params = append(structField.Options, opt) + } else { + structField.Options = append(structField.Options, opt) + } + } + } + if parseErr != nil { + pass.Reportf(field.Pos(), "Invalid structure tag: %s\n", parseErr) + return false + } + } + + model.Fields[structField.Name] = structField + } + + models[model.Name] = model + } + return false + }) + } + return nil, nil +}