-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from gqlgo/imp/first
Implement nodecheck
- Loading branch information
Showing
9 changed files
with
267 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
# nodecheck | ||
|
||
[![pkg.go.dev][gopkg-badge]][gopkg] | ||
|
||
**nodecheck** will find any GraphQL schema that is not conform to Node interface. | ||
|
||
```graphql | ||
# Valid | ||
type User implements Node { | ||
id: ID! | ||
} | ||
|
||
# Invalid. not conform to Node | ||
type Community { | ||
name: String! | ||
} | ||
|
||
# Invalid. not conform to Node but id is exist | ||
type Item { | ||
id: ID! | ||
} | ||
``` | ||
|
||
## How to use | ||
|
||
A runnable linter can be created with multichecker package. | ||
You can create own linter with your favorite Analyzers. | ||
And nodecheck has independ flag for allow exclude types of nodecheck. | ||
|
||
Full example | ||
|
||
```go | ||
func main() { | ||
var excludes string | ||
flag.StringVar(&excludes, "excludes", "", "exclude GraphQL types for node check. it can specify multiple values separated by `,` and it can use regex(e.g .+Connection") | ||
flag.Parse() | ||
|
||
analyzer := nodecheck.Analyzer(excludes) | ||
|
||
multichecker.Main( | ||
analyzer, | ||
) | ||
} | ||
``` | ||
|
||
`nodecheck` provides a executable binary. So, you can get cmd tool via `go install` command. | ||
|
||
```sh | ||
$ go install github.com/gqlgo/nodecheck/cmd/nodecheck@latest | ||
``` | ||
|
||
The `nodecheck` command receive two flags, `schema` and `excludes`. `excludes` can specify with regex format and it can receive multiple arguments separated by ','. | ||
|
||
```sh | ||
$ nodecheck -schema="server/graphql/schema/**/*.graphql" -excludes=.+Connection,.+Edge | ||
``` | ||
|
||
## Author | ||
|
||
[![Appify Technologies, Inc.](appify-logo.png)](http://github.com/appify-technologies) | ||
|
||
<!-- links --> | ||
[gopkg]: https://pkg.go.dev/github.com/gqlgo/nodecheck | ||
[gopkg-badge]: https://pkg.go.dev/badge/github.com/gqlgo/nodecheck?status.svg | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package main | ||
|
||
import ( | ||
"flag" | ||
|
||
"github.com/bannzai/nodecheck" | ||
"github.com/gqlgo/gqlanalysis/multichecker" | ||
) | ||
|
||
func main() { | ||
var excludes string | ||
flag.StringVar(&excludes, "excludes", "", "exclude GraphQL types for node check. it can specify multiple values separated by `,` and it can use regex(e.g .+Connection") | ||
flag.Parse() | ||
|
||
analyzer := nodecheck.Analyzer(excludes) | ||
|
||
multichecker.Main( | ||
analyzer, | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
package nodecheck | ||
|
||
import ( | ||
"regexp" | ||
"strings" | ||
|
||
"github.com/vektah/gqlparser/v2/ast" | ||
|
||
"github.com/gqlgo/gqlanalysis" | ||
) | ||
|
||
func Analyzer(excludes string) *gqlanalysis.Analyzer { | ||
return &gqlanalysis.Analyzer{ | ||
Name: "nodecheck", | ||
Doc: "nodecheck will find any GraphQL schema that is not conform to Node interface", | ||
Run: run(excludes), | ||
} | ||
} | ||
|
||
func run(excludes string) func(pass *gqlanalysis.Pass) (interface{}, error) { | ||
return func(pass *gqlanalysis.Pass) (interface{}, error) { | ||
allTypes := map[string]*ast.Definition{} | ||
for _, def := range pass.Schema.Types { | ||
if def.Kind == ast.Object { | ||
allTypes[def.Name] = def | ||
} | ||
} | ||
|
||
allNodeImplements := map[string]*ast.Definition{} | ||
for name, t := range allTypes { | ||
for _, typeInterface := range pass.Schema.Implements[name] { | ||
if typeInterface.Kind == ast.Interface && typeInterface.Name == "Node" { | ||
allNodeImplements[name] = t | ||
} | ||
} | ||
} | ||
|
||
unconformedTypes := map[string]*ast.Definition{} | ||
for k, v := range allTypes { | ||
if _, ok := allNodeImplements[k]; !ok { | ||
unconformedTypes[v.Name] = v | ||
} | ||
} | ||
|
||
needToNodeTypes := []*ast.Definition{} | ||
for k, v := range unconformedTypes { | ||
ok := false | ||
for _, rule := range strings.Split(excludes, ",") { | ||
if len(rule) > 0 { | ||
regex := regexp.MustCompile(rule) | ||
|
||
if ok { | ||
break | ||
} | ||
if regex.MatchString(k) { | ||
ok = true | ||
} | ||
} | ||
} | ||
|
||
if !ok { | ||
needToNodeTypes = append(needToNodeTypes, v) | ||
} | ||
} | ||
|
||
for _, t := range needToNodeTypes { | ||
// Skip private type. e.g) __Directive, __Enum ... | ||
if strings.HasPrefix(t.Name, "__") { | ||
continue | ||
} | ||
pass.Reportf(t.Position, "%+v should conform to Node", t.Name) | ||
} | ||
|
||
return nil, nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package nodecheck_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/bannzai/nodecheck" | ||
"github.com/gqlgo/gqlanalysis/analysistest" | ||
) | ||
|
||
func Test(t *testing.T) { | ||
testdata := analysistest.TestData(t) | ||
analysistest.Run(t, testdata, nodecheck.Analyzer(""), "a") | ||
} | ||
|
||
func TestWithSingleExclusion(t *testing.T) { | ||
testdata := analysistest.TestData(t) | ||
analysistest.Run(t, testdata, nodecheck.Analyzer("Community"), "b") | ||
} | ||
|
||
func TestWithMultipleExclusion(t *testing.T) { | ||
testdata := analysistest.TestData(t) | ||
analysistest.Run(t, testdata, nodecheck.Analyzer("Community,Item"), "c") | ||
} | ||
|
||
func TestWithRegex(t *testing.T) { | ||
testdata := analysistest.TestData(t) | ||
analysistest.Run(t, testdata, nodecheck.Analyzer(".+Payload"), "d") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
interface Node { | ||
id: ID! | ||
} | ||
|
||
type User implements Node { | ||
id: ID! | ||
} | ||
|
||
# Invalid. not conform to Node | ||
type Community { # want "Community should conform to Node" | ||
name: String! | ||
} | ||
|
||
# Invalid. not conform to Node but id is exist | ||
type Item { # want "Item should conform to Node" | ||
id: ID! | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
interface Node { | ||
id: ID! | ||
} | ||
|
||
type User implements Node { | ||
id: ID! | ||
} | ||
|
||
# Invalid. not conform to Node | ||
type Community { | ||
name: String! | ||
} | ||
|
||
# Invalid. not conform to Node but id is exist | ||
type Item { # want "Item should conform to Node" | ||
id: ID! | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
interface Node { | ||
id: ID! | ||
} | ||
|
||
type User implements Node { | ||
id: ID! | ||
} | ||
|
||
# Invalid. not conform to Node | ||
type Community { | ||
name: String! | ||
} | ||
|
||
# Invalid. not conform to Node but id is exist | ||
type Item { | ||
id: ID! | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
interface Node { | ||
id: ID! | ||
} | ||
|
||
type User implements Node { | ||
id: ID! | ||
} | ||
|
||
# Invalid. not conform to Node | ||
type CommunityPayload { | ||
name: String! | ||
} | ||
|
||
# Invalid. not conform to Node but id is exist | ||
type ItemPayload { | ||
id: ID! | ||
} |