Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Remove gomonkey dependency from formatter #1192

Merged
merged 3 commits into from Apr 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 4 additions & 4 deletions Makefile
Expand Up @@ -13,7 +13,7 @@ GOPATH:=$(shell $(GOCMD) env GOPATH)
u := $(if $(update),-u)

BINARY_NAME:=swag
PACKAGES:=$(shell $(GOLIST) github.com/swaggo/swag github.com/swaggo/swag/cmd/swag github.com/swaggo/swag/gen)
PACKAGES:=$(shell $(GOLIST) github.com/swaggo/swag github.com/swaggo/swag/cmd/swag github.com/swaggo/swag/gen github.com/swaggo/swag/format)
GOFILES:=$(shell find . -name "*.go" -type f)

export GO111MODULE := on
Expand Down Expand Up @@ -63,9 +63,9 @@ deps:
$(GOGET) golang.org/x/tools/go/loader

.PHONY: devel-deps
devel-deps:
devel-deps:
GO111MODULE=off $(GOGET) -v -u \
golang.org/x/lint/golint
golang.org/x/lint/golint

.PHONY: lint
lint: devel-deps
Expand All @@ -91,4 +91,4 @@ fmt-check:
.PHONY: view-covered
view-covered:
$(GOTEST) -coverprofile=cover.out $(TARGET)
$(GOCMD) tool cover -html=cover.out
$(GOCMD) tool cover -html=cover.out
102 changes: 95 additions & 7 deletions format/format.go
@@ -1,30 +1,118 @@
package format

import (
"log"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"

"github.com/swaggo/swag"
)

type Fmt struct {
// Format implements `fmt` command for formatting swag comments in Go source
// files.
type Format struct {
formatter *swag.Formatter

// exclude exclude dirs and files in SearchDir
exclude map[string]bool
}

func New() *Fmt {
return &Fmt{}
// New creates a new Format instance
func New() *Format {
return &Format{
exclude: map[string]bool{},
formatter: swag.NewFormatter(),
}
}

// Config specifies configuration for a format run
type Config struct {
// SearchDir the swag would be parse
SearchDir string

// excludes dirs and files in SearchDir,comma separated
Excludes string

// MainFile (DEPRECATED)
MainFile string
}

func (f *Fmt) Build(config *Config) error {
log.Println("Formating code.... ")
var defaultExcludes = []string{"docs", "vendor"}

// Build runs formatter according to configuration in config
func (f *Format) Build(config *Config) error {
searchDirs := strings.Split(config.SearchDir, ",")
for _, searchDir := range searchDirs {
if _, err := os.Stat(searchDir); os.IsNotExist(err) {
return fmt.Errorf("fmt: %w", err)
}
for _, d := range defaultExcludes {
f.exclude[filepath.Join(searchDir, d)] = true
}
}
for _, fi := range strings.Split(config.Excludes, ",") {
if fi = strings.TrimSpace(fi); fi != "" {
f.exclude[filepath.Clean(fi)] = true
}
}
for _, searchDir := range searchDirs {
err := filepath.Walk(searchDir, f.visit)
if err != nil {
return err
}
}
return nil
}

func (f *Format) visit(path string, fileInfo os.FileInfo, err error) error {
if fileInfo.IsDir() && f.excludeDir(path) {
return filepath.SkipDir
}
if f.excludeFile(path) {
return nil
}
if err := f.format(path); err != nil {
return fmt.Errorf("fmt: %w", err)
}
return nil
}

func (f *Format) excludeDir(path string) bool {
return f.exclude[path] ||
filepath.Base(path)[0] == '.' && len(filepath.Base(path)) > 1 // exclude hidden folders
}

func (f *Format) excludeFile(path string) bool {
return f.exclude[path] ||
strings.HasSuffix(strings.ToLower(path), "_test.go") ||
filepath.Ext(path) != ".go"
}

func (f *Format) format(path string) error {
contents, err := ioutil.ReadFile(path)
if err != nil {
return err
}
formatted, err := f.formatter.Format(path, contents)
if err != nil {
return err
}
return write(path, formatted)
}

return swag.NewFormatter().FormatAPI(config.SearchDir, config.Excludes, config.MainFile)
func write(path string, contents []byte) error {
f, err := ioutil.TempFile(filepath.Split(path))
if err != nil {
return err
}
defer os.Remove(f.Name())
if _, err := f.Write(contents); err != nil {
return err
}
if err := f.Close(); err != nil {
return err
}
return os.Rename(f.Name(), path)
}
131 changes: 131 additions & 0 deletions format/format_test.go
@@ -0,0 +1,131 @@
package format

import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
)

func TestFormat_Format(t *testing.T) {
fx := setup(t)
assert.NoError(t, New().Build(&Config{SearchDir: fx.basedir}))
assert.True(t, fx.isFormatted("main.go"))
assert.True(t, fx.isFormatted("api/api.go"))
}

func TestFormat_ExcludeDir(t *testing.T) {
fx := setup(t)
assert.NoError(t, New().Build(&Config{
SearchDir: fx.basedir,
Excludes: filepath.Join(fx.basedir, "api"),
}))
assert.False(t, fx.isFormatted("api/api.go"))
}

func TestFormat_ExcludeFile(t *testing.T) {
fx := setup(t)
assert.NoError(t, New().Build(&Config{
SearchDir: fx.basedir,
Excludes: filepath.Join(fx.basedir, "main.go"),
}))
assert.False(t, fx.isFormatted("main.go"))
}

func TestFormat_DefaultExcludes(t *testing.T) {
fx := setup(t)
assert.NoError(t, New().Build(&Config{SearchDir: fx.basedir}))
assert.False(t, fx.isFormatted("api/api_test.go"))
assert.False(t, fx.isFormatted("docs/docs.go"))
}

func TestFormat_ParseError(t *testing.T) {
fx := setup(t)
ioutil.WriteFile(filepath.Join(fx.basedir, "parse_error.go"), []byte(`package main
func invalid() {`), 0644)
assert.Error(t, New().Build(&Config{SearchDir: fx.basedir}))
}

func TestFormat_ReadError(t *testing.T) {
fx := setup(t)
os.Chmod(filepath.Join(fx.basedir, "main.go"), 0)
assert.Error(t, New().Build(&Config{SearchDir: fx.basedir}))
}

func TestFormat_WriteError(t *testing.T) {
fx := setup(t)
os.Chmod(fx.basedir, 0555)
assert.Error(t, New().Build(&Config{SearchDir: fx.basedir}))
os.Chmod(fx.basedir, 0755)
}

func TestFormat_InvalidSearchDir(t *testing.T) {
formatter := New()
assert.Error(t, formatter.Build(&Config{SearchDir: "no_such_dir"}))
}

type fixture struct {
t *testing.T
basedir string
}

func setup(t *testing.T) *fixture {
fx := &fixture{
t: t,
basedir: t.TempDir(),
}
for filename, contents := range testFiles {
fullpath := filepath.Join(fx.basedir, filepath.Clean(filename))
if err := os.MkdirAll(filepath.Dir(fullpath), 0755); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(fullpath, contents, 0644); err != nil {
t.Fatal(err)
}
}
return fx
}

func (fx *fixture) isFormatted(file string) bool {
contents, err := ioutil.ReadFile(filepath.Join(fx.basedir, filepath.Clean(file)))
if err != nil {
fx.t.Fatal(err)
}
return !bytes.Equal(testFiles[file], contents)
}

var testFiles = map[string][]byte{
"api/api.go": []byte(`package api

import "net/http"

// @Summary Add a new pet to the store
// @Description get string by ID
func GetStringByInt(w http.ResponseWriter, r *http.Request) {
//write your code
}`),
"api/api_test.go": []byte(`package api
// @Summary API Test
// @Description Should not be formatted
func TestApi(t *testing.T) {}`),
"docs/docs.go": []byte(`package docs
// @Summary Documentation package
// @Description Should not be formatted`),
"main.go": []byte(`package main

import (
"net/http"

"github.com/swaggo/swag/format/testdata/api"
)

// @title Swagger Example API
// @version 1.0
func main() {
http.HandleFunc("/testapi/get-string-by-int/", api.GetStringByInt)
}`),
"README.md": []byte(`# Format test`),
}