Skip to content

Commit f312674

Browse files
authored
Add skip-fmt config option (#40)
1 parent b49073d commit f312674

7 files changed

Lines changed: 107 additions & 70 deletions

File tree

configuration-schema.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@
8282
"filename": {
8383
"type": "string",
8484
"description": "Filename to use if single file output is enabled."
85+
},
86+
"skip-fmt": {
87+
"type": "boolean",
88+
"description": "Skip running gofmt on generated code. Useful for faster generation when formatting will be done separately."
8589
}
8690
},
8791
"required": []

docs/configuration.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,16 @@ output:
101101
filename: "api.gen.go"
102102
```
103103

104+
#### `output.skip-fmt`
105+
**Type:** `boolean` | **Default:** `false`
106+
107+
Skip running `goimports` and `gofmt` on generated code. Only byte-order-mark sanitization is performed. Useful for faster generation or when using custom import handling (e.g., yaml v4 instead of v3).
108+
109+
```yaml
110+
output:
111+
skip-fmt: true
112+
```
113+
104114
### Generation Settings
105115

106116
#### `generate.client`

docs/migrate-from-v2.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ generate: ✅
175175
handler-package: string
176176
compatibility:
177177
output-options: ➡️renamed to output
178-
skip-fmt:
178+
skip-fmt: ➡️ moved to output.skip-fmt
179179
skip-prune: ➡ moved to config root
180180
include-tags: ➡ moved to filter include
181181
exclude-tags: ➡ moved to filter.exclude

examples/api/complete-example/main.go

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,7 @@ func main() {
6161
if cfg.Output.UseSingleFile {
6262
// Single file output
6363
outPath := filepath.Join(cfg.Output.Directory, cfg.Output.Filename)
64-
formatted, err := codegen.FormatCode(codes["all"])
65-
if err != nil {
66-
panic(fmt.Sprintf("formatting code: %v", err))
67-
}
68-
if err := os.WriteFile(outPath, []byte(formatted), 0644); err != nil {
64+
if err := os.WriteFile(outPath, []byte(codes.GetCombined()), 0644); err != nil {
6965
panic(fmt.Sprintf("writing file: %v", err))
7066
}
7167
fmt.Printf("\nGenerated code written to: %s\n", outPath)
@@ -76,11 +72,7 @@ func main() {
7672
continue // Skip the combined output
7773
}
7874
outPath := filepath.Join(cfg.Output.Directory, name+".go")
79-
formatted, err := codegen.FormatCode(code)
80-
if err != nil {
81-
panic(fmt.Sprintf("formatting %s: %v", name, err))
82-
}
83-
if err := os.WriteFile(outPath, []byte(formatted), 0644); err != nil {
75+
if err := os.WriteFile(outPath, []byte(code), 0644); err != nil {
8476
panic(fmt.Sprintf("writing %s: %v", name, err))
8577
}
8678
fmt.Printf("Generated: %s\n", outPath)

pkg/codegen/configuration.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@ type Output struct {
321321
UseSingleFile bool `yaml:"use-single-file"`
322322
Directory string `yaml:"directory"`
323323
Filename string `yaml:"filename"`
324+
SkipFmt bool `yaml:"skip-fmt"`
324325
}
325326

326327
// OverlayOptions specifies OpenAPI Overlay files to apply to the spec before generation.

pkg/codegen/parser.go

Lines changed: 46 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ func (p *Parser) Parse() (GeneratedCode, error) {
216216
}
217217
formatted := out
218218
if !useSingleFile {
219-
formatted, err = FormatCode(out)
219+
formatted, err = p.formatCode(out)
220220
if err != nil {
221221
return nil, err
222222
}
@@ -256,7 +256,7 @@ func (p *Parser) Parse() (GeneratedCode, error) {
256256
if err != nil {
257257
return nil, fmt.Errorf("error generating code for %s: %w", tmpl, err)
258258
}
259-
formatted, err := FormatCode(out)
259+
formatted, err := p.formatCode(out)
260260
if err != nil {
261261
return nil, err
262262
}
@@ -272,7 +272,7 @@ func (p *Parser) Parse() (GeneratedCode, error) {
272272
}
273273
formatted := out
274274
if !useSingleFile {
275-
formatted, err = FormatCode(out)
275+
formatted, err = p.formatCode(out)
276276
if err != nil {
277277
return nil, fmt.Errorf("error formatting %s: %w", tmpl, err)
278278
}
@@ -306,7 +306,7 @@ func (p *Parser) Parse() (GeneratedCode, error) {
306306
}
307307
}
308308
// Scaffold files are always separate files, so always format them
309-
formattedMiddleware, err := FormatCode(middlewareOut)
309+
formattedMiddleware, err := p.formatCode(middlewareOut)
310310
if err != nil {
311311
return nil, fmt.Errorf("error formatting middleware: %w", err)
312312
}
@@ -334,7 +334,7 @@ func (p *Parser) Parse() (GeneratedCode, error) {
334334
}
335335

336336
// Scaffold files are always separate files, so always format them
337-
formatted, err := FormatCode(out)
337+
formatted, err := p.formatCode(out)
338338
if err != nil {
339339
return nil, fmt.Errorf("error formatting service: %w", err)
340340
}
@@ -376,7 +376,7 @@ func (p *Parser) Parse() (GeneratedCode, error) {
376376
}
377377
}
378378

379-
formatted, err := FormatCode(out)
379+
formatted, err := p.formatCode(out)
380380
if err != nil {
381381
return nil, fmt.Errorf("error formatting server: %w", err)
382382
}
@@ -401,7 +401,7 @@ func (p *Parser) Parse() (GeneratedCode, error) {
401401
}
402402
formatted := out
403403
if !useSingleFile {
404-
formatted, err = FormatCode(out)
404+
formatted, err = p.formatCode(out)
405405
if err != nil {
406406
return nil, fmt.Errorf("error formatting MCP tools: %w", err)
407407
}
@@ -419,7 +419,7 @@ func (p *Parser) Parse() (GeneratedCode, error) {
419419
if err != nil {
420420
return nil, fmt.Errorf("error generating code for validator: %w", err)
421421
}
422-
formatted, err := FormatCode(out)
422+
formatted, err := p.formatCode(out)
423423
if err != nil {
424424
return nil, err
425425
}
@@ -438,7 +438,7 @@ func (p *Parser) Parse() (GeneratedCode, error) {
438438
}
439439
formatted := out
440440
if !useSingleFile {
441-
formatted, err = FormatCode(out)
441+
formatted, err = p.formatCode(out)
442442
if err != nil {
443443
return nil, err
444444
}
@@ -484,7 +484,7 @@ func (p *Parser) Parse() (GeneratedCode, error) {
484484
}
485485
formatted := out
486486
if !useSingleFile {
487-
formatted, err = FormatCode(out)
487+
formatted, err = p.formatCode(out)
488488
if err != nil {
489489
return nil, err
490490
}
@@ -508,7 +508,7 @@ func (p *Parser) Parse() (GeneratedCode, error) {
508508
}
509509
formatted := out
510510
if !useSingleFile {
511-
formatted, err = FormatCode(out)
511+
formatted, err = p.formatCode(out)
512512
if err != nil {
513513
return nil, err
514514
}
@@ -540,7 +540,7 @@ func (p *Parser) Parse() (GeneratedCode, error) {
540540
res += code + "\n"
541541
}
542542

543-
formatted, err := FormatCode(res)
543+
formatted, err := p.formatCode(res)
544544
if err != nil {
545545
println(res)
546546
return nil, err
@@ -575,6 +575,40 @@ func (p *Parser) ParseTemplates(templates []string, data any) (string, error) {
575575
return strings.Join(generatedTemplates, "\n"), nil
576576
}
577577

578+
// formatCode formats the provided Go code, respecting the skip-fmt config option.
579+
// When skip-fmt is true, only sanitization is performed (skips import optimization and gofmt).
580+
func (p *Parser) formatCode(src string) (string, error) {
581+
src = strings.Trim(src, "\n") + "\n"
582+
if src == "\n" || src == "" {
583+
return src, nil
584+
}
585+
586+
// remove any byte-order-marks which break Go-Code
587+
// See: https://groups.google.com/forum/#!topic/golang-nuts/OToNIPdfkks
588+
src = strings.ReplaceAll(src, "\uFEFF", "")
589+
590+
if p.cfg.Output != nil && p.cfg.Output.SkipFmt {
591+
return src, nil
592+
}
593+
594+
return FormatCode(src)
595+
}
596+
597+
// FormatCode formats the provided Go code by optimizing imports and running gofmt.
598+
func FormatCode(src string) (string, error) {
599+
res, err := imports.Process("gen.go", []byte(src), nil)
600+
if err != nil {
601+
return "", fmt.Errorf("error optimizing imports: %w", err)
602+
}
603+
604+
res, err = format.Source(res)
605+
if err != nil {
606+
return "", fmt.Errorf("error formatting code: %w", err)
607+
}
608+
609+
return string(res), nil
610+
}
611+
578612
func loadTemplates(cfg Configuration) (*template.Template, error) {
579613
tpl := template.New("templates").Funcs(TemplateFunctions)
580614

@@ -627,43 +661,6 @@ func loadTemplates(cfg Configuration) (*template.Template, error) {
627661
return tpl, nil
628662
}
629663

630-
// FormatCode formats the provided Go code.
631-
// It optimizes imports and formats the code using gofmt.
632-
func FormatCode(src string) (string, error) {
633-
src = strings.Trim(src, "\n") + "\n"
634-
if src == "\n" || src == "" {
635-
return src, nil
636-
}
637-
638-
res, err := optimizeImports([]byte(src))
639-
if err != nil {
640-
return "", fmt.Errorf("error optimizing imports: %w", err)
641-
}
642-
643-
res, err = format.Source(res)
644-
if err != nil {
645-
return "", fmt.Errorf("error formatting code: %w", err)
646-
}
647-
648-
return sanitizeCode(string(res)), nil
649-
}
650-
651-
// sanitizeCode runs sanitizers across the generated Go code to ensure the
652-
// generated code will be able to compile.
653-
func sanitizeCode(src string) string {
654-
// remove any byte-order-marks which break Go-Code
655-
// See: https://groups.google.com/forum/#!topic/golang-nuts/OToNIPdfkks
656-
return strings.ReplaceAll(src, "\uFEFF", "")
657-
}
658-
659-
func optimizeImports(src []byte) ([]byte, error) {
660-
outBytes, err := imports.Process("gen.go", src, nil)
661-
if err != nil {
662-
return nil, err
663-
}
664-
return outBytes, nil
665-
}
666-
667664
func getSpecLocationOutName(specLocation SpecLocation) string {
668665
switch specLocation {
669666
case SpecLocationPath:

pkg/codegen/parser_test.go

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,9 @@ package main
2323
import (
2424
"fmt"
2525
"os"
26-
"path/filepath"
27-
"slices"
28-
"github.com/doordash-oss/oapi-codegen-dd/v3/pkg/codegen"
29-
"github.com/doordash-oss/oapi-codegen-dd/v3/pkg/codegen/ast"
3026
)
3127
func main() {
32-
fmt.Println("Hello, World!")
28+
fmt.Println("Hello, World!")
3329
}
3430
`
3531

@@ -48,13 +44,12 @@ func main() {
4844
require.Equal(t, expected, res)
4945
}
5046

51-
func TestOptimizeImports(t *testing.T) {
47+
func Test_formatCode(t *testing.T) {
5248
src := `
5349
package main
5450
import (
5551
"fmt"
56-
"foo"
57-
"bar"
52+
"os"
5853
)
5954
func main() {
6055
fmt.Println("Hello, World!")
@@ -71,9 +66,47 @@ func main() {
7166
fmt.Println("Hello, World!")
7267
}
7368
`
74-
res, err := optimizeImports([]byte(src))
69+
p := &Parser{cfg: Configuration{Output: &Output{SkipFmt: false}}}
70+
res, err := p.formatCode(src)
71+
require.NoError(t, err)
72+
require.Equal(t, expected, res)
73+
}
74+
75+
func Test_formatCode_skip(t *testing.T) {
76+
// Unformatted code with unused imports
77+
src := `
78+
package main
79+
import (
80+
"fmt"
81+
"os"
82+
)
83+
func main() {
84+
fmt.Println("Hello, World!")
85+
}
86+
`
87+
88+
// When SkipFmt=true, no processing is done (no import optimization, no gofmt)
89+
p := &Parser{cfg: Configuration{Output: &Output{SkipFmt: true}}}
90+
res, err := p.formatCode(src)
7591
require.NoError(t, err)
76-
require.Equal(t, expected, string(res))
92+
93+
// Verify unused import "os" is NOT removed (imports not optimized)
94+
require.Contains(t, res, `"os"`)
95+
96+
// Verify the code is returned as-is (gofmt skipped, bad indentation remains)
97+
require.Contains(t, res, "fmt.Println")
98+
require.NotContains(t, res, "\tfmt.Println")
99+
100+
// When SkipFmt=false, full formatting is applied
101+
p = &Parser{cfg: Configuration{Output: &Output{SkipFmt: false}}}
102+
res, err = p.formatCode(src)
103+
require.NoError(t, err)
104+
105+
// Verify unused import "os" IS removed
106+
require.NotContains(t, res, `"os"`)
107+
108+
// gofmt fixes the indentation
109+
require.Contains(t, res, "\tfmt.Println")
77110
}
78111

79112
func TestParser_Parse(t *testing.T) {

0 commit comments

Comments
 (0)