Skip to content

Commit

Permalink
Code
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewloring committed Jan 25, 2020
1 parent 24d1a6b commit 36dc413
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 0 deletions.
10 changes: 10 additions & 0 deletions cmd/validjson/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package main

import (
"github.com/matthewloring/validjson"
"golang.org/x/tools/go/analysis/singlechecker"
)

func main() {
singlechecker.Main(validjson.Analyzer)
}
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/matthewloring/validjson

go 1.13

require golang.org/x/tools v0.0.0-20200124220429-8fe064f891f2
12 changes: 12 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20200124220429-8fe064f891f2 h1:ZNYGPNJ+tKwnFHzxlFH8bZSIj5uyr3xdUscYDsKQJ1k=
golang.org/x/tools v0.0.0-20200124220429-8fe064f891f2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
32 changes: 32 additions & 0 deletions testdata/src/a/a.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// This file contains the test for valid json tags.

package a

type Key struct {
A, B string
}

type StrAlias string

type ChanAlias chan<- int

type ValidJsonTest struct {
A int `json:"A"`
B int `json:"B,omitempty"`
C int `json:",omitempty"`
D int `json:"-"`
E chan<- int `json:"E"` // want "struct field has json tag but non-serializable type chan<- int"
F chan<- int `json:"-"`
G chan<- int `json:"-,"` // want "struct field has json tag but non-serializable type chan<- int"
H ChanAlias `json:"H"` // want "struct field has json tag but non-serializable type a.ChanAlias"
I map[int]int `json:"I"`
J map[int64]int `json:"J"`
K map[Key]int `json:"K"` // want "struct field has json tag but non-serializable type"
L map[struct{ A, B string }]int `json:"L"` // want "struct field has json tag but non-serializable type"
M map[string]int `json:"M"`
N map[StrAlias]int `json:"N"`
O func() `json:"O"` // want "struct field has json tag but non-serializable type func()"
P complex64 `json:"P"` // want "struct field has json tag but non-serializable type complex64"
Q complex128 `json:"Q"` // want "struct field has json tag but non-serializable type complex128"
R StrAlias `json:"R"`
}
75 changes: 75 additions & 0 deletions validjson.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Package validjson defines an Analyzer that checks that struct fields
// with json tags have json serializable types.
package validjson

import (
"go/ast"
"go/types"
"reflect"

"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
)

var Analyzer = &analysis.Analyzer{
Name: "validjson",
Doc: "check that struct fields with json tags have json-compatible types.",
Requires: []*analysis.Analyzer{inspect.Analyzer},
RunDespiteErrors: true,
Run: run,
}

func run(pass *analysis.Pass) (interface{}, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)

nodeFilter := []ast.Node{
(*ast.StructType)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
styp, ok := pass.TypesInfo.Types[n.(*ast.StructType)].Type.(*types.Struct)
// Type information may be incomplete.
if !ok {
return
}
for i := 0; i < styp.NumFields(); i++ {
f := styp.Field(i)
if isNonSkipJSONTag(styp.Tag(i)) && !isJSONSerializable(f.Type()) {
pass.Reportf(f.Pos(), "struct field has json tag but non-serializable type %v", f.Type())
}
}
})
return nil, nil
}

// See: https://blog.golang.org/json-and-go
func isJSONSerializable(t types.Type) bool {
switch fieldType := t.(type) {
case *types.Basic:
return fieldType.Kind() != types.Complex64 && fieldType.Kind() != types.Complex128
case *types.Chan:
return false
case *types.Map:
return isJSONSerializableAsMapKey(fieldType.Key())
case *types.Named:
return isJSONSerializable(fieldType.Underlying())
case *types.Signature:
return false
}
return true
}

func isJSONSerializableAsMapKey(t types.Type) bool {
switch keyType := t.(type) {
case *types.Basic:
return keyType.Info()&types.IsInteger != 0 || keyType.Kind() == types.String
case *types.Named:
return isJSONSerializableAsMapKey(keyType.Underlying())
}
return false
}

func isNonSkipJSONTag(tag string) bool {
val, found := (reflect.StructTag)(tag).Lookup("json")
return found && val != "-"
}
13 changes: 13 additions & 0 deletions validjson_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package validjson_test

import (
"testing"

"github.com/matthewloring/validjson"
"golang.org/x/tools/go/analysis/analysistest"
)

func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, validjson.Analyzer, "a")
}

0 comments on commit 36dc413

Please sign in to comment.