Compare commits
9 Commits
4b5580b1c8
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| b37121a218 | |||
| ffbeccc4fd | |||
| 4536be4d10 | |||
| 6e5e676e84 | |||
| 4089f0b084 | |||
| fefc7a701b | |||
| 9d14fa7c57 | |||
| db9ef0f935 | |||
| 60e0a64a24 |
29
.idea/watcherTasks.xml
generated
Normal file
29
.idea/watcherTasks.xml
generated
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectTasksOptions">
|
||||||
|
<TaskOptions isEnabled="true">
|
||||||
|
<option name="arguments" value="run --disable=typecheck $FileDir$" />
|
||||||
|
<option name="checkSyntaxErrors" value="true" />
|
||||||
|
<option name="description" />
|
||||||
|
<option name="exitCodeBehavior" value="ERROR" />
|
||||||
|
<option name="fileExtension" value="go" />
|
||||||
|
<option name="immediateSync" value="false" />
|
||||||
|
<option name="name" value="golangci-lint" />
|
||||||
|
<option name="output" value="" />
|
||||||
|
<option name="outputFilters">
|
||||||
|
<array />
|
||||||
|
</option>
|
||||||
|
<option name="outputFromStdout" value="false" />
|
||||||
|
<option name="program" value="golangci-lint" />
|
||||||
|
<option name="runOnExternalChanges" value="false" />
|
||||||
|
<option name="scopeName" value="Project Files" />
|
||||||
|
<option name="trackOnlyRoot" value="true" />
|
||||||
|
<option name="workingDir" value="$ProjectFileDir$" />
|
||||||
|
<envs>
|
||||||
|
<env name="GOROOT" value="$GOROOT$" />
|
||||||
|
<env name="GOPATH" value="$GOPATH$" />
|
||||||
|
<env name="PATH" value="$GoBinDirs$" />
|
||||||
|
</envs>
|
||||||
|
</TaskOptions>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
BIN
cmd/gormlint/gormlint
Executable file
BIN
cmd/gormlint/gormlint
Executable file
Binary file not shown.
@@ -1,9 +1,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/kuzgoga/gormlint/nullSafetyCheck"
|
||||||
|
"github.com/kuzgoga/gormlint/relationsCheck"
|
||||||
"golang.org/x/tools/go/analysis/multichecker"
|
"golang.org/x/tools/go/analysis/multichecker"
|
||||||
"gormlint/nullSafetyCheck"
|
|
||||||
"gormlint/relationsCheck"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -8,3 +8,21 @@ func (model *Model) HasPrimaryKey() bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (model *Model) HasField(name string) bool {
|
||||||
|
for _, field := range model.Fields {
|
||||||
|
if field.Name == name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (model *Model) PrimaryKey() bool {
|
||||||
|
for _, field := range model.Fields {
|
||||||
|
if field.Tags.HasOption("primaryKey") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -64,6 +64,11 @@ func ParseModels(pass *analysis.Pass, models *map[string]Model) {
|
|||||||
model.Fields[structField.Name] = structField
|
model.Fields[structField.Name] = structField
|
||||||
(*models)[model.Name] = model
|
(*models)[model.Name] = model
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, exist := model.Fields["Id"]; !exist {
|
||||||
|
pass.Reportf(model.Pos, "Id field should be presented model \"%s\"", model.Name)
|
||||||
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -1,4 +1,4 @@
|
|||||||
module gormlint
|
module github.com/kuzgoga/gormlint
|
||||||
|
|
||||||
go 1.23.2
|
go 1.23.2
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package nullSafetyCheck
|
package nullSafetyCheck
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/kuzgoga/gormlint/common"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"golang.org/x/tools/go/analysis"
|
"golang.org/x/tools/go/analysis"
|
||||||
"gormlint/common"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
19
relationsCheck/checkBelongsTo.go
Normal file
19
relationsCheck/checkBelongsTo.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package relationsCheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kuzgoga/gormlint/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func IsBelongsTo(field common.Field, model common.Model, relatedModel common.Model) bool {
|
||||||
|
references := field.Tags.GetParamOr("references", "Id")
|
||||||
|
foreignKey := field.Tags.GetParamOr("foreignKey", field.Name+"Id")
|
||||||
|
|
||||||
|
if !model.HasField(foreignKey) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !relatedModel.HasField(references) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
18
relationsCheck/checkHasOne.go
Normal file
18
relationsCheck/checkHasOne.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package relationsCheck
|
||||||
|
|
||||||
|
import "github.com/kuzgoga/gormlint/common"
|
||||||
|
|
||||||
|
func IsHasOne(field common.Field, model common.Model, relatedModel common.Model) bool {
|
||||||
|
foreignKey := field.Tags.GetParamOr("foreignKey", model.Name+"Id")
|
||||||
|
references := field.Tags.GetParamOr("references", "Id")
|
||||||
|
|
||||||
|
if !relatedModel.HasField(foreignKey) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !model.HasField(references) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
70
relationsCheck/checkManyToOne.go
Normal file
70
relationsCheck/checkManyToOne.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package relationsCheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kuzgoga/gormlint/common"
|
||||||
|
"go/token"
|
||||||
|
"go/types"
|
||||||
|
"golang.org/x/tools/go/analysis"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var alreadyReported = make(map[token.Pos]bool)
|
||||||
|
|
||||||
|
func checkManyToOne(pass *analysis.Pass, nestedField common.Field, model common.Model, relatedModel common.Model) bool {
|
||||||
|
/* Return true, if found problems */
|
||||||
|
|
||||||
|
foreignKey := nestedField.Tags.GetParamOr("foreignKey", model.Name+"Id")
|
||||||
|
references := nestedField.Tags.GetParamOr("references", "Id")
|
||||||
|
|
||||||
|
if alreadyReported[nestedField.Pos] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !relatedModel.HasField(foreignKey) {
|
||||||
|
pass.Reportf(
|
||||||
|
nestedField.Pos,
|
||||||
|
"Expected foreignKey `%s` in model `%s` for 1:M relation with model `%s`",
|
||||||
|
foreignKey,
|
||||||
|
relatedModel.Name,
|
||||||
|
model.Name,
|
||||||
|
)
|
||||||
|
alreadyReported[nestedField.Pos] = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !model.HasField(references) {
|
||||||
|
pass.Reportf(
|
||||||
|
nestedField.Pos,
|
||||||
|
"Expected references `%s` in model `%s` for 1:M relation with model `%s`",
|
||||||
|
references,
|
||||||
|
model.Name,
|
||||||
|
relatedModel.Name,
|
||||||
|
)
|
||||||
|
alreadyReported[nestedField.Pos] = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
foreignKeyField := relatedModel.Fields[foreignKey]
|
||||||
|
referencesField := model.Fields[references]
|
||||||
|
foreignKeyType := types.ExprString(foreignKeyField.Type)
|
||||||
|
referencesType := types.ExprString(referencesField.Type)
|
||||||
|
|
||||||
|
if alreadyReported[foreignKeyField.Pos] || alreadyReported[referencesField.Pos] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(foreignKeyType, "int") && !alreadyReported[foreignKeyField.Pos] {
|
||||||
|
// TODO: process UUID as foreign key type
|
||||||
|
pass.Reportf(foreignKeyField.Pos, "Foreign key `%s` has invalid type", foreignKeyType)
|
||||||
|
alreadyReported[foreignKeyField.Pos] = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(referencesType, "int") && !alreadyReported[referencesField.Pos] {
|
||||||
|
pass.Reportf(referencesField.Pos, "References key `%s` has invalid type", referencesType)
|
||||||
|
alreadyReported[referencesField.Pos] = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
42
relationsCheck/checkOneToMany.go
Normal file
42
relationsCheck/checkOneToMany.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package relationsCheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/kuzgoga/gormlint/common"
|
||||||
|
"go/types"
|
||||||
|
"golang.org/x/tools/go/analysis"
|
||||||
|
)
|
||||||
|
|
||||||
|
func findBackReferenceInOneToMany(model common.Model, relatedModel common.Model) *common.Field {
|
||||||
|
for _, field := range relatedModel.Fields {
|
||||||
|
if !common.IsSlice(field.Type) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if field.Tags.HasParam("many2many") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
baseType := common.ResolveBaseType(field.Type)
|
||||||
|
if baseType == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if *baseType == model.Name {
|
||||||
|
return &field
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isOneToMany(pass *analysis.Pass, model common.Model, relatedModel common.Model) bool {
|
||||||
|
backReference := findBackReferenceInOneToMany(model, relatedModel)
|
||||||
|
if backReference == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
fmt.Println("Found back reference")
|
||||||
|
fmt.Printf("Backref type: %s\n", types.ExprString(backReference.Type))
|
||||||
|
fmt.Printf("Model: %s\n", model.Name)
|
||||||
|
fmt.Printf("Related model: %s\n", relatedModel.Name)
|
||||||
|
if checkManyToOne(pass, *backReference, relatedModel, model) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
29
relationsCheck/constraints.go
Normal file
29
relationsCheck/constraints.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package relationsCheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kuzgoga/gormlint/common"
|
||||||
|
"golang.org/x/tools/go/analysis"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CheckCascadeDelete(pass *analysis.Pass, field common.Field) bool {
|
||||||
|
if !field.Tags.HasParam("constraint") {
|
||||||
|
pass.Reportf(field.Pos, "field %s should have a delete constraint", field.Name)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
constraintValue := field.Tags.GetParam("constraint").Value
|
||||||
|
pair := strings.Split(constraintValue, ":")
|
||||||
|
trigger, value := pair[0], pair[1]
|
||||||
|
if strings.ToLower(trigger) == "OnDelete" && strings.ToUpper(value) != "CASCADE" {
|
||||||
|
pass.Reportf(field.Pos, "field have invalid constraint on `OnDelete trigger`")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if field.Tags.HasParam("OnDelete") {
|
||||||
|
if strings.ToUpper(field.Tags.GetParam("OnDelete").Value) != "CASCADE" {
|
||||||
|
pass.Reportf(field.Pos, "field have invalid constraint on `OnDelete` trigger")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -2,8 +2,8 @@ package relationsCheck
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/kuzgoga/gormlint/common"
|
||||||
"golang.org/x/tools/go/analysis"
|
"golang.org/x/tools/go/analysis"
|
||||||
"gormlint/common"
|
|
||||||
"slices"
|
"slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,76 +14,126 @@ var RelationsAnalyzer = &analysis.Analyzer{
|
|||||||
Run: run,
|
Run: run,
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckTypesOfM2M(pass *analysis.Pass, modelName string, relatedModelName string, relationName string, reference common.Field, backReference common.Field) {
|
func CheckTypesOfM2M(pass *analysis.Pass, modelName string, relatedModelName string, relationName string, reference common.Field, backReference common.Field) bool {
|
||||||
if !common.IsSlice(reference.Type) {
|
if !common.IsSlice(reference.Type) {
|
||||||
pass.Reportf(reference.Pos, "M2M relation `%s` with bad type `%s` (should be a slice)", relationName, reference.Type)
|
pass.Reportf(reference.Pos, "M2M relation `%s` with bad type `%s` (should be a slice)", relationName, reference.Type)
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
if !common.IsSlice(backReference.Type) {
|
if !common.IsSlice(backReference.Type) {
|
||||||
pass.Reportf(backReference.Pos, "M2M relation `%s` with bad type `%s` (should be a slice)", relationName, backReference.Type)
|
pass.Reportf(backReference.Pos, "M2M relation `%s` with bad type `%s` (should be a slice)", relationName, backReference.Type)
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
referenceBaseType := common.ResolveBaseType(reference.Type)
|
referenceBaseType := common.ResolveBaseType(reference.Type)
|
||||||
if referenceBaseType == nil {
|
if referenceBaseType == nil {
|
||||||
pass.Reportf(reference.Pos, "Failed to resolve field type: `%s`", reference.Type)
|
pass.Reportf(reference.Pos, "Failed to resolve field type: `%s`", reference.Type)
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
backReferenceBaseType := common.ResolveBaseType(backReference.Type)
|
backReferenceBaseType := common.ResolveBaseType(backReference.Type)
|
||||||
if backReferenceBaseType == nil {
|
if backReferenceBaseType == nil {
|
||||||
pass.Reportf(reference.Pos, "Failed to resolve type: `%s`", reference.Type)
|
pass.Reportf(reference.Pos, "Failed to resolve type: `%s`", reference.Type)
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if *backReferenceBaseType != modelName {
|
if *backReferenceBaseType != modelName {
|
||||||
pass.Reportf(backReference.Pos, "Invalid type `%s` in M2M relation (use []*%s or self-reference)", *backReferenceBaseType, modelName)
|
pass.Reportf(backReference.Pos, "Invalid type `%s` in M2M relation (use []*%s or self-reference)", *backReferenceBaseType, modelName)
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if *referenceBaseType != relatedModelName {
|
if *referenceBaseType != relatedModelName {
|
||||||
pass.Reportf(reference.Pos, "Invalid type `%s` in M2M relation (use []*%s or self-reference)", *referenceBaseType, relatedModelName)
|
pass.Reportf(reference.Pos, "Invalid type `%s` in M2M relation (use []*%s or self-reference)", *referenceBaseType, relatedModelName)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckMany2Many(pass *analysis.Pass, models map[string]common.Model) {
|
func CheckMany2Many(pass *analysis.Pass, models map[string]common.Model) {
|
||||||
// TODO: unexpected duplicated relations
|
var processedRelations []string
|
||||||
var knownModels []string
|
|
||||||
for _, model := range models {
|
for _, model := range models {
|
||||||
for _, field := range model.Fields {
|
for _, field := range model.Fields {
|
||||||
m2mRelation := field.Tags.GetParam("many2many")
|
m2mRelation := field.Tags.GetParam("many2many")
|
||||||
if m2mRelation != nil {
|
if m2mRelation != nil {
|
||||||
|
if slices.Contains(processedRelations, m2mRelation.Value) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
processedRelations = append(processedRelations, m2mRelation.Value)
|
||||||
|
|
||||||
relatedModel := common.GetModelFromType(field.Type, models)
|
relatedModel := common.GetModelFromType(field.Type, models)
|
||||||
if relatedModel == nil {
|
if relatedModel == nil {
|
||||||
pass.Reportf(field.Pos, "Failed to resolve related model type")
|
pass.Reportf(field.Pos, "Failed to resolve related model type")
|
||||||
return
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
backReference := common.FindBackReferenceInM2M(m2mRelation.Value, *relatedModel)
|
backReference := common.FindBackReferenceInM2M(m2mRelation.Value, *relatedModel)
|
||||||
if backReference != nil {
|
if backReference != nil {
|
||||||
if slices.Contains(knownModels, relatedModel.Name) {
|
if CheckTypesOfM2M(pass, model.Name, relatedModel.Name, m2mRelation.Value, field, *backReference) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Проверка каскадного удаления и других параметров
|
||||||
|
if CheckCascadeDelete(pass, field) {
|
||||||
continue
|
continue
|
||||||
} else {
|
|
||||||
knownModels = append(knownModels, model.Name)
|
|
||||||
knownModels = append(knownModels, relatedModel.Name)
|
|
||||||
}
|
}
|
||||||
CheckTypesOfM2M(pass, model.Name, relatedModel.Name, m2mRelation.Value, field, *backReference)
|
|
||||||
// TODO: check foreign key and references
|
|
||||||
fmt.Printf("Found M2M relation between \"%s\" and \"%s\"\n", model.Name, relatedModel.Name)
|
|
||||||
} else {
|
} else {
|
||||||
// Check self-reference
|
// Обработка самоссылки
|
||||||
if model.Name == relatedModel.Name {
|
if model.Name == relatedModel.Name {
|
||||||
CheckTypesOfM2M(pass, model.Name, relatedModel.Name, m2mRelation.Value, field, field)
|
if CheckTypesOfM2M(pass, model.Name, relatedModel.Name, m2mRelation.Value, field, field) {
|
||||||
} else {
|
continue
|
||||||
if !relatedModel.HasPrimaryKey() {
|
|
||||||
fmt.Printf("%#v\n", relatedModel)
|
|
||||||
pass.Reportf(field.Pos, "Can't build M2M relation `%s`, primary key on `%s` model is absont", m2mRelation.Value, relatedModel.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Here you can forbid M2M relations without back-reference
|
|
||||||
// TODO: process m2m without backref
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// TODO: check [] and process m:1
|
pass.Reportf(field.Pos, "M2M relation `%s` missing back-reference in model `%s`", m2mRelation.Value, relatedModel.Name)
|
||||||
|
}
|
||||||
|
if CheckCascadeDelete(pass, field) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckOneToMany(pass *analysis.Pass, models map[string]common.Model) {
|
||||||
|
for _, model := range models {
|
||||||
|
for _, field := range model.Fields {
|
||||||
|
if common.IsSlice(field.Type) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if field.Tags.HasParam("many2many") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
baseType := common.ResolveBaseType(field.Type)
|
||||||
|
if baseType == nil {
|
||||||
|
pass.Reportf(field.Pos, "Failed to resolve field base type: `%s`", field.Type)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
relatedModel := common.GetModelFromType(field.Type, models)
|
||||||
|
if relatedModel == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
foundOneToMany := isOneToMany(pass, model, *relatedModel)
|
||||||
|
if foundOneToMany {
|
||||||
|
fmt.Printf("Found 1:M relation in model `%s` with model `%s`\n", model.Name, *baseType)
|
||||||
|
}
|
||||||
|
|
||||||
|
foundBelongsTo := IsBelongsTo(field, model, *relatedModel)
|
||||||
|
hasOne := IsHasOne(field, model, *relatedModel)
|
||||||
|
|
||||||
|
if !foundOneToMany {
|
||||||
|
if foundBelongsTo {
|
||||||
|
fmt.Printf("`%s` belongs `%s`\n", *baseType, model.Name)
|
||||||
|
if CheckCascadeDelete(pass, field) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if hasOne {
|
||||||
|
fmt.Printf("`%s` has one `%s` \n", model.Name, relatedModel.Name)
|
||||||
|
if CheckCascadeDelete(pass, field) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pass.Reportf(field.Pos, "Invalid relation in field `%s`", field.Name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,6 +143,6 @@ func run(pass *analysis.Pass) (any, error) {
|
|||||||
models := make(map[string]common.Model)
|
models := make(map[string]common.Model)
|
||||||
common.ParseModels(pass, &models)
|
common.ParseModels(pass, &models)
|
||||||
CheckMany2Many(pass, models)
|
CheckMany2Many(pass, models)
|
||||||
|
CheckOneToMany(pass, models)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
package tests
|
package tests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/kuzgoga/gormlint/nullSafetyCheck"
|
||||||
"golang.org/x/tools/go/analysis/analysistest"
|
"golang.org/x/tools/go/analysis/analysistest"
|
||||||
"gormlint/nullSafetyCheck"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNullSafety(t *testing.T) {
|
func TestNullSafety(t *testing.T) {
|
||||||
t.Parallel()
|
|
||||||
analysistest.Run(t, analysistest.TestData(), nullSafetyCheck.NullSafetyAnalyzer, "null_safety")
|
analysistest.Run(t, analysistest.TestData(), nullSafetyCheck.NullSafetyAnalyzer, "null_safety")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
package tests
|
package tests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/kuzgoga/gormlint/relationsCheck"
|
||||||
"golang.org/x/tools/go/analysis/analysistest"
|
"golang.org/x/tools/go/analysis/analysistest"
|
||||||
"gormlint/relationsCheck"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRelationsCheck(t *testing.T) {
|
func TestRelationsCheck(t *testing.T) {
|
||||||
t.Parallel()
|
|
||||||
analysistest.Run(t, analysistest.TestData(), relationsCheck.RelationsAnalyzer, "relations_check")
|
analysistest.Run(t, analysistest.TestData(), relationsCheck.RelationsAnalyzer, "relations_check")
|
||||||
}
|
}
|
||||||
|
|||||||
59
tests/testdata/src/relations_check/negative.go
vendored
59
tests/testdata/src/relations_check/negative.go
vendored
@@ -4,25 +4,74 @@ package relations_check
|
|||||||
|
|
||||||
type Library struct {
|
type Library struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
Books []*Book `gorm:"many2many:library_book;"`
|
Books []*Book `gorm:"many2many:library_book;constraint:OnDelete:CASCADE;"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Book struct {
|
type Book struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
Libraries []*Library `gorm:"many2many:library_book;"`
|
Libraries []*Library `gorm:"many2many:library_book;constraint:OnDelete:CASCADE;"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Employee struct {
|
type Employee struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
Subordinates []*Employee `gorm:"many2many:employee_subordinates;"` // self-reference
|
Subordinates []*Employee `gorm:"many2many:employee_subordinates;constraint:OnDelete:CASCADE;"` // self-reference
|
||||||
}
|
}
|
||||||
|
|
||||||
type Publisher struct {
|
type Publisher struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
Writers []*Writer `gorm:"many2many:publisher_books;"`
|
Writers []*Writer `gorm:"many2many:publisher_books;constraint:OnDelete:CASCADE;"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Writer struct {
|
type Writer struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
Publishers []Publisher `gorm:"many2many:publisher_books;"`
|
Publishers []Publisher `gorm:"many2many:publisher_books;constraint:OnDelete:CASCADE;"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// One-to-many
|
||||||
|
|
||||||
|
type Comment struct {
|
||||||
|
Id uint `gorm:"primaryKey"`
|
||||||
|
CommentatorId uint
|
||||||
|
Commentator Commentator
|
||||||
|
}
|
||||||
|
|
||||||
|
type Commentator struct {
|
||||||
|
Id uint `gorm:"primaryKey"`
|
||||||
|
Comments []Comment `gorm:"foreignKey:CommentatorId;references:Id;constraint:OnDelete:CASCADE;"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Post struct {
|
||||||
|
Id uint `gorm:"primaryKey"`
|
||||||
|
Files []*File `gorm:"constraint:OnDelete:CASCADE;"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type File struct {
|
||||||
|
Id uint `gorm:"primaryKey"`
|
||||||
|
PostId uint
|
||||||
|
Post Post
|
||||||
|
}
|
||||||
|
|
||||||
|
type Consumer struct {
|
||||||
|
Id uint `gorm:"primaryKey"`
|
||||||
|
Name string
|
||||||
|
ShoppingCart ShoppingCart // want "Invalid relation in field `ShoppingCart`"
|
||||||
|
}
|
||||||
|
|
||||||
|
type ShoppingCart struct {
|
||||||
|
Id uint `gorm:"primaryKey"`
|
||||||
|
SerializedItems string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has one
|
||||||
|
|
||||||
|
type Hotel struct {
|
||||||
|
Id uint `gorm:"primaryKey"`
|
||||||
|
Office // want "field Office should have a delete constraint"
|
||||||
|
}
|
||||||
|
|
||||||
|
type Office struct {
|
||||||
|
Id uint `gorm:"primaryKey"`
|
||||||
|
Name string
|
||||||
|
Address string
|
||||||
|
HotelId uint
|
||||||
}
|
}
|
||||||
|
|||||||
41
tests/testdata/src/relations_check/positive.go
vendored
41
tests/testdata/src/relations_check/positive.go
vendored
@@ -2,20 +2,53 @@ package relations_check
|
|||||||
|
|
||||||
type Student struct {
|
type Student struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
Courses []Course `gorm:"many2many:student_courses;"`
|
Courses []Course `gorm:"many2many:student_courses;constraint:OnDelete:CASCADE;"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Course struct {
|
type Course struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
Students []Course `gorm:"many2many:student_courses;"` // want "Invalid type `Course` in M2M relation \\(use \\[\\]\\*Student or self-reference\\)"
|
Students []Course `gorm:"many2many:student_courses;constraint:OnDelete:CASCADE"` // want "Invalid type `Course` in M2M relation \\(use \\[\\]\\*Student or self-reference\\)"
|
||||||
}
|
}
|
||||||
|
|
||||||
type Author struct {
|
type Author struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
Articles []Article `gorm:"many2many:author_articles;"`
|
Articles []Article `gorm:"many2many:author_articles;constraint:OnDelete:CASCADES;"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Article struct {
|
type Article struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
Authors Author `gorm:"many2many:author_articles;"` // want "M2M relation `author_articles` with bad type `Author` \\(should be a slice\\)"
|
Authors Author `gorm:"many2many:author_articles;constraint:OnDelete:CASCADE;"` // want "M2M relation `author_articles` with bad type `Author` \\(should be a slice\\)"
|
||||||
|
}
|
||||||
|
|
||||||
|
type Kuzbass struct {
|
||||||
|
Id uint `gorm:"primaryKey"`
|
||||||
|
Cities []City // want "Expected foreignKey `KuzbassId` in model `City` for 1:M relation with model `Kuzbass`"
|
||||||
|
}
|
||||||
|
|
||||||
|
type City struct {
|
||||||
|
Id uint `gorm:"primaryKey"`
|
||||||
|
Kuzbass Kuzbass // want "Invalid relation in field `Kuzbass`"
|
||||||
|
}
|
||||||
|
|
||||||
|
type Federation struct { // want "Id field should be presented model \"Federation\""
|
||||||
|
Lands []Land `gorm:"constraint:OnDelete:CASCADE;"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Land struct {
|
||||||
|
Id uint `gorm:"primaryKey"`
|
||||||
|
FederationId uint
|
||||||
|
}
|
||||||
|
|
||||||
|
// Belongs to
|
||||||
|
|
||||||
|
type Owner struct {
|
||||||
|
Id uint `gorm:"primaryKey"`
|
||||||
|
Name string
|
||||||
|
CompanyId int
|
||||||
|
Company Company `gorm:"constraint:OnDelete:CASCADE;"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Company struct {
|
||||||
|
Id int
|
||||||
|
Name string
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user