Skip to content

Commit

Permalink
Put comments behind an option in formatter (#271)
Browse files Browse the repository at this point in the history
* Put comments behind an option in formatter

In #263, we introduced parsing of comments, as well as support for them
in the formatter. In some cases this is surely useful, but in others
it's just bloat. (And as I describe in Khan/genqlient#282, it may even
be a problem in some cases which depended on the fact that formatting
the query didn't include comments.)

In this commit I introduce an option to control whether comments are
formatted. I set the default to false (i.e. restoring the previous
behavior if no options are set), because adding this felt to me
like a breaking change, and because it seems to me like the more common
usage. `WithComments()` restores the behavior added in #263. If others
disagree I'm happy to keep the changed default, and instead provide
`WithoutComments()`. I also added tests both ways (and for the existing
`WithIndent()` option), and checked that the `comments` tests match the
existing ones, and the `default` tests match those from `v2.6.3` (except
for the addition of a few descriptions whose omission seem to have been
a bug).

Comments are still parsed in any case, as adding new struct fields is no
problem; and they are still included in `Dump` since that seems
obviously parallel to struct fields and is more of a debugging thing.

* docs
  • Loading branch information
benjaminjkraft committed Jul 17, 2023
1 parent 9fb1c32 commit 8d1bedc
Show file tree
Hide file tree
Showing 68 changed files with 1,018 additions and 100 deletions.
20 changes: 15 additions & 5 deletions formatter/formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,21 @@ type Formatter interface {
//nolint:revive // Ignore "stuttering" name format.FormatterOption
type FormatterOption func(*formatter)

// WithIndent uses the given string for indenting block bodies in the output,
// instead of the default, `"\t"`.
func WithIndent(indent string) FormatterOption {
return func(f *formatter) {
f.indent = indent
}
}

// WithComments includes comments from the source/AST in the formatted output.
func WithComments() FormatterOption {
return func(f *formatter) {
f.emitComments = true
}
}

func NewFormatter(w io.Writer, options ...FormatterOption) Formatter {
f := &formatter{
indent: "\t",
Expand All @@ -38,9 +47,10 @@ func NewFormatter(w io.Writer, options ...FormatterOption) Formatter {
type formatter struct {
writer io.Writer

indent string
indentSize int
emitBuiltin bool
indent string
indentSize int
emitBuiltin bool
emitComments bool

padNext bool
lineHead bool
Expand Down Expand Up @@ -714,7 +724,7 @@ func (f *formatter) FormatValue(value *ast.Value) {
}

func (f *formatter) FormatCommentGroup(group *ast.CommentGroup) {
if group == nil {
if !f.emitComments || group == nil {
return
}
for _, comment := range group.List {
Expand All @@ -723,7 +733,7 @@ func (f *formatter) FormatCommentGroup(group *ast.CommentGroup) {
}

func (f *formatter) FormatComment(comment *ast.Comment) {
if comment == nil {
if !f.emitComments || comment == nil {
return
}
f.WriteString("#").WriteString(comment.Text()).WriteNewline()
Expand Down
218 changes: 123 additions & 95 deletions formatter/formatter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"flag"
"os"
"path"
"path/filepath"
"testing"
"unicode/utf8"

Expand All @@ -17,118 +18,145 @@ import (

var update = flag.Bool("u", false, "update golden files")

var optionSets = []struct {
name string
opts []formatter.FormatterOption
}{
{"default", nil},
{"spaceIndent", []formatter.FormatterOption{formatter.WithIndent(" ")}},
{"comments", []formatter.FormatterOption{formatter.WithComments()}},
}

func TestFormatter_FormatSchema(t *testing.T) {
const testSourceDir = "./testdata/source/schema"
const testBaselineDir = "./testdata/baseline/FormatSchema"

executeGoldenTesting(t, &goldenConfig{
SourceDir: testSourceDir,
BaselineFileName: func(cfg *goldenConfig, f os.DirEntry) string {
return path.Join(testBaselineDir, f.Name())
},
Run: func(t *testing.T, cfg *goldenConfig, f os.DirEntry) []byte {
// load stuff
schema, gqlErr := gqlparser.LoadSchema(&ast.Source{
Name: f.Name(),
Input: mustReadFile(path.Join(testSourceDir, f.Name())),
for _, optionSet := range optionSets {
testBaselineDir := filepath.Join(testBaselineDir, optionSet.name)
opts := optionSet.opts
t.Run(optionSet.name, func(t *testing.T) {
executeGoldenTesting(t, &goldenConfig{
SourceDir: testSourceDir,
BaselineFileName: func(cfg *goldenConfig, f os.DirEntry) string {
return path.Join(testBaselineDir, f.Name())
},
Run: func(t *testing.T, cfg *goldenConfig, f os.DirEntry) []byte {
// load stuff
schema, gqlErr := gqlparser.LoadSchema(&ast.Source{
Name: f.Name(),
Input: mustReadFile(path.Join(testSourceDir, f.Name())),
})
if gqlErr != nil {
t.Fatal(gqlErr)
}

// exec format
var buf bytes.Buffer
formatter.NewFormatter(&buf, opts...).FormatSchema(schema)

// validity check
_, gqlErr = gqlparser.LoadSchema(&ast.Source{
Name: f.Name(),
Input: buf.String(),
})
if gqlErr != nil {
t.Log(buf.String())
t.Fatal(gqlErr)
}

return buf.Bytes()
},
})
if gqlErr != nil {
t.Fatal(gqlErr)
}

// exec format
var buf bytes.Buffer
formatter.NewFormatter(&buf).FormatSchema(schema)

// validity check
_, gqlErr = gqlparser.LoadSchema(&ast.Source{
Name: f.Name(),
Input: buf.String(),
})
if gqlErr != nil {
t.Log(buf.String())
t.Fatal(gqlErr)
}

return buf.Bytes()
},
})
})
}
}

func TestFormatter_FormatSchemaDocument(t *testing.T) {
const testSourceDir = "./testdata/source/schema"
const testBaselineDir = "./testdata/baseline/FormatSchemaDocument"

executeGoldenTesting(t, &goldenConfig{
SourceDir: testSourceDir,
BaselineFileName: func(cfg *goldenConfig, f os.DirEntry) string {
return path.Join(testBaselineDir, f.Name())
},
Run: func(t *testing.T, cfg *goldenConfig, f os.DirEntry) []byte {
// load stuff
doc, gqlErr := parser.ParseSchema(&ast.Source{
Name: f.Name(),
Input: mustReadFile(path.Join(testSourceDir, f.Name())),
})
if gqlErr != nil {
t.Fatal(gqlErr)
}

// exec format
var buf bytes.Buffer
formatter.NewFormatter(&buf).FormatSchemaDocument(doc)

// validity check
_, gqlErr = parser.ParseSchema(&ast.Source{
Name: f.Name(),
Input: buf.String(),
for _, optionSet := range optionSets {
testBaselineDir := filepath.Join(testBaselineDir, optionSet.name)
opts := optionSet.opts
t.Run(optionSet.name, func(t *testing.T) {
executeGoldenTesting(t, &goldenConfig{
SourceDir: testSourceDir,
BaselineFileName: func(cfg *goldenConfig, f os.DirEntry) string {
return path.Join(testBaselineDir, f.Name())
},
Run: func(t *testing.T, cfg *goldenConfig, f os.DirEntry) []byte {
// load stuff
doc, gqlErr := parser.ParseSchema(&ast.Source{
Name: f.Name(),
Input: mustReadFile(path.Join(testSourceDir, f.Name())),
})
if gqlErr != nil {
t.Fatal(gqlErr)
}

// exec format
var buf bytes.Buffer
formatter.NewFormatter(&buf, opts...).FormatSchemaDocument(doc)

// validity check
_, gqlErr = parser.ParseSchema(&ast.Source{
Name: f.Name(),
Input: buf.String(),
})
if gqlErr != nil {
t.Log(buf.String())
t.Fatal(gqlErr)
}

return buf.Bytes()
},
})
if gqlErr != nil {
t.Log(buf.String())
t.Fatal(gqlErr)
}

return buf.Bytes()
},
})
})
}
}

func TestFormatter_FormatQueryDocument(t *testing.T) {
const testSourceDir = "./testdata/source/query"
const testBaselineDir = "./testdata/baseline/FormatQueryDocument"

executeGoldenTesting(t, &goldenConfig{
SourceDir: testSourceDir,
BaselineFileName: func(cfg *goldenConfig, f os.DirEntry) string {
return path.Join(testBaselineDir, f.Name())
},
Run: func(t *testing.T, cfg *goldenConfig, f os.DirEntry) []byte {
// load stuff
doc, gqlErr := parser.ParseQuery(&ast.Source{
Name: f.Name(),
Input: mustReadFile(path.Join(testSourceDir, f.Name())),
for _, optionSet := range optionSets {
testBaselineDir := filepath.Join(testBaselineDir, optionSet.name)
opts := optionSet.opts
t.Run(optionSet.name, func(t *testing.T) {
executeGoldenTesting(t, &goldenConfig{
SourceDir: testSourceDir,
BaselineFileName: func(cfg *goldenConfig, f os.DirEntry) string {
return path.Join(testBaselineDir, f.Name())
},
Run: func(t *testing.T, cfg *goldenConfig, f os.DirEntry) []byte {
// load stuff
doc, gqlErr := parser.ParseQuery(&ast.Source{
Name: f.Name(),
Input: mustReadFile(path.Join(testSourceDir, f.Name())),
})
if gqlErr != nil {
t.Fatal(gqlErr)
}

// exec format
var buf bytes.Buffer
formatter.NewFormatter(&buf, opts...).FormatQueryDocument(doc)

// validity check
_, gqlErr = parser.ParseQuery(&ast.Source{
Name: f.Name(),
Input: buf.String(),
})
if gqlErr != nil {
t.Log(buf.String())
t.Fatal(gqlErr)
}

return buf.Bytes()
},
})
if gqlErr != nil {
t.Fatal(gqlErr)
}

// exec format
var buf bytes.Buffer
formatter.NewFormatter(&buf).FormatQueryDocument(doc)

// validity check
_, gqlErr = parser.ParseQuery(&ast.Source{
Name: f.Name(),
Input: buf.String(),
})
if gqlErr != nil {
t.Log(buf.String())
t.Fatal(gqlErr)
}

return buf.Bytes()
},
})
})
}
}

type goldenConfig struct {
Expand Down Expand Up @@ -178,11 +206,11 @@ func executeGoldenTesting(t *testing.T, cfg *goldenConfig) {

expected, err := os.ReadFile(expectedFilePath)
if os.IsNotExist(err) {
err = os.MkdirAll(path.Dir(expectedFilePath), 0755)
err = os.MkdirAll(path.Dir(expectedFilePath), 0o755)
if err != nil {
t.Fatal(err)
}
err = os.WriteFile(expectedFilePath, result, 0444)
err = os.WriteFile(expectedFilePath, result, 0o444)
if err != nil {
t.Fatal(err)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
query FooBarQuery ($after: String!) {
fizzList(first: 100, after: $after) {
nodes {
id
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
query {
bar: foo
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
query FooBarQuery ($after: String!) {
fizzList(first: 100, after: $after) {
nodes {
id
... FooFragment
... on Foo {
id
}
... {
id
}
name
}
}
}
fragment FooFragment on Foo {
id
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
query ($first: Int = 30, $after: String!) {
searchCats(first: $first, after: $after) {
nodes {
id
name
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
query FooBarQuery ($after: String!) {
fizzList(first: 100, after: $after) {
nodes {
id
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
query {
bar: foo
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
query FooBarQuery ($after: String!) {
fizzList(first: 100, after: $after) {
nodes {
id
... FooFragment
... on Foo {
id
}
... {
id
}
name
}
}
}
fragment FooFragment on Foo {
id
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
query ($first: Int = 30, $after: String!) {
searchCats(first: $first, after: $after) {
nodes {
id
name
}
}
}
Empty file.

0 comments on commit 8d1bedc

Please sign in to comment.