Compare commits

...

6 Commits

Author SHA1 Message Date
b37121a218 rename: module 2025-03-18 03:45:42 +07:00
ffbeccc4fd feat: delete constraint for belongs to and has one 2025-03-18 03:29:00 +07:00
4536be4d10 feat: more clear output 2025-03-18 03:23:15 +07:00
6e5e676e84 fix: logic 2025-03-18 01:36:42 +07:00
4089f0b084 feat: has one 2025-03-18 00:23:52 +07:00
fefc7a701b fix: invalid relations detect 2025-03-18 00:04:23 +07:00
15 changed files with 121 additions and 22 deletions

29
.idea/watcherTasks.xml generated Normal file
View 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

Binary file not shown.

View File

@@ -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() {

2
go.mod
View File

@@ -1,4 +1,4 @@
module gormlint module github.com/kuzgoga/gormlint
go 1.23.2 go 1.23.2

View File

@@ -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"
) )

View File

@@ -1,17 +1,17 @@
package relationsCheck package relationsCheck
import ( import (
"gormlint/common" "github.com/kuzgoga/gormlint/common"
) )
func IsBelongsTo(field common.Field, model common.Model, relatedModel common.Model) bool { func IsBelongsTo(field common.Field, model common.Model, relatedModel common.Model) bool {
foreignKey := field.Tags.GetParamOr("foreignKey", "Id") references := field.Tags.GetParamOr("references", "Id")
references := field.Tags.GetParamOr("references", relatedModel.Name+"Id") foreignKey := field.Tags.GetParamOr("foreignKey", field.Name+"Id")
if !model.HasField(references) { if !model.HasField(foreignKey) {
return false return false
} }
if !relatedModel.HasField(foreignKey) { if !relatedModel.HasField(references) {
return false return false
} }

View 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
}

View File

@@ -1,10 +1,10 @@
package relationsCheck package relationsCheck
import ( import (
"github.com/kuzgoga/gormlint/common"
"go/token" "go/token"
"go/types" "go/types"
"golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis"
"gormlint/common"
"strings" "strings"
) )

View File

@@ -2,9 +2,9 @@ package relationsCheck
import ( import (
"fmt" "fmt"
"github.com/kuzgoga/gormlint/common"
"go/types" "go/types"
"golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis"
"gormlint/common"
) )
func findBackReferenceInOneToMany(model common.Model, relatedModel common.Model) *common.Field { func findBackReferenceInOneToMany(model common.Model, relatedModel common.Model) *common.Field {

View File

@@ -1,14 +1,14 @@
package relationsCheck package relationsCheck
import ( import (
"github.com/kuzgoga/gormlint/common"
"golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis"
"gormlint/common"
"strings" "strings"
) )
func CheckCascadeDelete(pass *analysis.Pass, field common.Field) bool { func CheckCascadeDelete(pass *analysis.Pass, field common.Field) bool {
if !field.Tags.HasParam("constraint") { if !field.Tags.HasParam("constraint") {
pass.Reportf(field.Pos, "field %s should have a constraint", field.Name) pass.Reportf(field.Pos, "field %s should have a delete constraint", field.Name)
return true return true
} }
constraintValue := field.Tags.GetParam("constraint").Value constraintValue := field.Tags.GetParam("constraint").Value

View File

@@ -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"
) )
@@ -98,6 +98,9 @@ func CheckOneToMany(pass *analysis.Pass, models map[string]common.Model) {
if common.IsSlice(field.Type) { if common.IsSlice(field.Type) {
continue continue
} }
if field.Tags.HasParam("many2many") {
continue
}
baseType := common.ResolveBaseType(field.Type) baseType := common.ResolveBaseType(field.Type)
if baseType == nil { if baseType == nil {
@@ -114,10 +117,22 @@ func CheckOneToMany(pass *analysis.Pass, models map[string]common.Model) {
fmt.Printf("Found 1:M relation in model `%s` with model `%s`\n", model.Name, *baseType) 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 !foundOneToMany {
foundBelongsTo := IsBelongsTo(field, model, *relatedModel)
if foundBelongsTo { if foundBelongsTo {
fmt.Printf("Found belongs to relation in model `%s` with model `%s`\n", model.Name, *baseType) 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)
} }
} }
} }

View File

@@ -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")
} }

View File

@@ -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")
} }

View File

@@ -50,3 +50,28 @@ type File struct {
PostId uint PostId uint
Post Post 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
}

View File

@@ -26,8 +26,8 @@ type Kuzbass struct {
} }
type City struct { type City struct {
Id uint `gorm:"primaryKey"` Id uint `gorm:"primaryKey"`
Kuzbass Kuzbass Kuzbass Kuzbass // want "Invalid relation in field `Kuzbass`"
} }
type Federation struct { // want "Id field should be presented model \"Federation\"" type Federation struct { // want "Id field should be presented model \"Federation\""
@@ -38,3 +38,17 @@ type Land struct {
Id uint `gorm:"primaryKey"` Id uint `gorm:"primaryKey"`
FederationId uint 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
}