Skip to content

Commit

Permalink
Add --fix option.
Browse files Browse the repository at this point in the history
  • Loading branch information
Denis Krivak committed May 16, 2020
1 parent 570b502 commit e954f0a
Show file tree
Hide file tree
Showing 9 changed files with 352 additions and 11 deletions.
1 change: 1 addition & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ issues:
exclude-rules:
- path: _test\.go
linters:
- dupl
- funlen
- path: cmd/godot/main\.go
linters:
Expand Down
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,32 @@ end of the last sentence if needed.
> Comments should begin with the name of the thing being described
> and end in a period
## Install and run
## Install

*NOTE: Godot is available as a part of [GolangCI Lint](https://github.com/golangci/golangci-lint)
(disabled by default).*

Build from source

```sh
go get -u github.com/tetafro/godot/cmd/godot
```

or download binary from [releases page](https://github.com/tetafro/godot/releases).

Run
## Run

```sh
godot ./myproject
```

Autofix flags are also available

```sh
godot -f ./myproject # fix issues and print the result
godot -w ./myproject # fix issues and replace the original file
```

## Examples

Code
Expand All @@ -50,5 +59,5 @@ Top level comment should end in a period: math/math.go:3:1
```

See more examples in test files:
- [for default mode](testdata/example_default.go)
- [for using --all flag](testdata/example_checkall.go)
- [for default mode](testdata/default/check/main.go)
- [for using --all flag](testdata/checkall/check/main.go)
29 changes: 25 additions & 4 deletions cmd/godot/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,17 @@ const usage = `Usage:
godot [OPTION] [FILES]
Options:
-a, --all check all top-level comments (not only declarations)
-f, --fix fix issues, and print fixed version to stdout
-h, --help show this message
-v, --version show version`
-v, --version show version
-w, --write fix issues, and write result to original file`

type arguments struct {
help bool
version bool
all bool
fix bool
write bool
files []string
}

Expand Down Expand Up @@ -69,9 +73,22 @@ func main() {
}

for i := range files {
issues := godot.Run(files[i], fset, settings)
for _, iss := range issues {
fmt.Printf("%s: %s\n", iss.Message, iss.Pos)
switch {
case args.fix:
fixed, err := godot.Fix(args.files[i], files[i], fset, settings)
if err != nil {
fatalf("Failed to autofix file '%s': %v", args.files[i], err)
}
fmt.Print(string(fixed))
case args.write:
if err := godot.Replace(args.files[i], files[i], fset, settings); err != nil {
fatalf("Failed to rewrite file '%s': %v", args.files[i], err)
}
default:
issues := godot.Run(files[i], fset, settings)
for _, iss := range issues {
fmt.Printf("%s: %s\n", iss.Message, iss.Pos)
}
}
}
}
Expand All @@ -94,6 +111,10 @@ func readArgs() (args arguments, err error) {
args.version = true
case "-a", "--all":
args.all = true
case "-f", "--fix":
args.fix = true
case "-w", "--write":
args.write = true
default:
return arguments{}, fmt.Errorf("unknown flag '%s'", arg)
}
Expand Down
55 changes: 55 additions & 0 deletions godot.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
package godot

import (
"fmt"
"go/ast"
"go/token"
"io/ioutil"
"os"
"regexp"
"strings"
)
Expand Down Expand Up @@ -74,6 +77,58 @@ func Run(file *ast.File, fset *token.FileSet, settings Settings) []Issue {
return issues
}

// Fix fixes all issues and return new version of file content.
func Fix(path string, file *ast.File, fset *token.FileSet, settings Settings) ([]byte, error) {
// Read file
content, err := ioutil.ReadFile(path) // nolint: gosec
if err != nil {
return nil, fmt.Errorf("read file: %v", err)
}
if len(content) == 0 {
return nil, nil
}

issues := Run(file, fset, settings)

// slice -> map
m := map[int]Issue{}
for _, iss := range issues {
m[iss.Pos.Line] = iss
}

// Replace lines from issues
fixed := make([]byte, 0, len(content))
for i, line := range strings.Split(string(content), "\n") {
newline := line
if iss, ok := m[i+1]; ok {
newline = iss.Replacement
}
fixed = append(fixed, []byte(newline+"\n")...)
}
fixed = fixed[:len(fixed)-1] // trim last "\n"

return fixed, nil
}

// Replace rewrites original file with it's fixed version.
func Replace(path string, file *ast.File, fset *token.FileSet, settings Settings) error {
info, err := os.Stat(path)
if err != nil {
return fmt.Errorf("check file: %v", err)
}
mode := info.Mode()

fixed, err := Fix(path, file, fset, settings)
if err != nil {
return fmt.Errorf("fix issues: %v", err)
}

if err := ioutil.WriteFile(path, fixed, mode); err != nil {
return fmt.Errorf("write file: %v", err)
}
return nil
}

func check(fset *token.FileSet, group *ast.CommentGroup) (iss Issue, ok bool) {
if group == nil || len(group.List) == 0 {
return Issue{}, true
Expand Down
77 changes: 74 additions & 3 deletions godot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package godot
import (
"go/parser"
"go/token"
"io/ioutil"
"path/filepath"
"strings"
"testing"
Expand Down Expand Up @@ -287,9 +288,9 @@ func TestMakeReplacement(t *testing.T) {
}
}

func TestIntegration(t *testing.T) {
func TestRunIntegration(t *testing.T) {
t.Run("default check", func(t *testing.T) {
var testFile = filepath.Join("testdata", "default", "example.go")
var testFile = filepath.Join("testdata", "default", "check", "main.go")
expected, err := readTestFile(testFile)
if err != nil {
t.Fatalf("Failed to read test file %s: %v", testFile, err)
Expand All @@ -316,7 +317,7 @@ func TestIntegration(t *testing.T) {
})

t.Run("check all", func(t *testing.T) {
var testFile = filepath.Join("testdata", "checkall", "example.go")
var testFile = filepath.Join("testdata", "checkall", "check", "main.go")
expected, err := readTestFile(testFile)
if err != nil {
t.Fatalf("Failed to read test file %s: %v", testFile, err)
Expand Down Expand Up @@ -345,6 +346,76 @@ func TestIntegration(t *testing.T) {
})
}

func TestFixIntegration(t *testing.T) {
t.Run("default check", func(t *testing.T) {
var testFile = filepath.Join("testdata", "default", "check", "main.go")
var expectedFile = filepath.Join("testdata", "default", "result", "main.go")
expected, err := ioutil.ReadFile(expectedFile) // nolint: gosec
if err != nil {
t.Fatalf("Failed to read test file %s: %v", expected, err)
}

fset := token.NewFileSet()
file, err := parser.ParseFile(fset, testFile, nil, parser.ParseComments)
if err != nil {
t.Fatalf("Failed to parse file %s: %v", testFile, err)
}

fixed, err := Fix(testFile, file, fset, Settings{CheckAll: false})

if err != nil {
t.Fatalf("Unexpected error: %v", err)
}

fixedLines := strings.Split(string(fixed), "\n")
expectedLines := strings.Split(string(expected), "\n")
if len(fixedLines) != len(expectedLines) {
t.Fatalf("Invalid number of result lines\n expected: %d\n got: %d",
len(expectedLines), len(fixedLines))
}
for i := range fixedLines {
if fixedLines[i] != expectedLines[i] {
t.Fatalf("Wrong line %d in fixed file\n expected: %s\n got: %s",
i, expectedLines[i], fixedLines[i])
}
}
})

t.Run("check all", func(t *testing.T) {
var testFile = filepath.Join("testdata", "checkall", "check", "main.go")
var expectedFile = filepath.Join("testdata", "checkall", "result", "main.go")
expected, err := ioutil.ReadFile(expectedFile) // nolint: gosec
if err != nil {
t.Fatalf("Failed to read test file %s: %v", expected, err)
}

fset := token.NewFileSet()
file, err := parser.ParseFile(fset, testFile, nil, parser.ParseComments)
if err != nil {
t.Fatalf("Failed to parse file %s: %v", testFile, err)
}

fixed, err := Fix(testFile, file, fset, Settings{CheckAll: true})

if err != nil {
t.Fatalf("Unexpected error: %v", err)
}

fixedLines := strings.Split(string(fixed), "\n")
expectedLines := strings.Split(string(expected), "\n")
if len(fixedLines) != len(expectedLines) {
t.Fatalf("Invalid number of result lines\n expected: %d\n got: %d",
len(expectedLines), len(fixedLines))
}
for i := range fixedLines {
if fixedLines[i] != expectedLines[i] {
t.Fatalf("Wrong line %d in fixed file\n expected: %s\n got: %s",
i, expectedLines[i], fixedLines[i])
}
}
})
}

// readTestFile reads comments from file. If comment contains "PASS",
// it should not be among issues. If comment contains "FAIL", it should
// be among error issues.
Expand Down
File renamed without changes.
92 changes: 92 additions & 0 deletions testdata/checkall/result/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Package comment without a period FAIL.
package example

/*
#include <stdio.h>
#include <stdlib.h>
void myprint(char* s) {
printf("%d\n", s);
}
# PASS
*/
import "C"
import "unsafe"

//args: tagged comment without period PASS

// #tag hashtag comment without period PASS

/*
Multiline comment without a period FAIL.
*/

/*
Multiline comment with a period PASS.
*/

/* One-line comment without a period FAIL. */

/* One-line comment with a period PASS. */

// Single-line comment without a period FAIL.

// Single-line comment with a period PASS.

// Declaration comment without a period FAIL.
type SimpleObject struct {
// Exported field comment - always PASS
Type string
// Unexported field comment - always PASS
secret int
}

// Declaration comment without a period, with an indented code example:
// co := ComplexObject{}
// fmt.Println(co) // PASS
type ComplexObject struct {
// Exported field comment - always PASS
Type string
// Unexported field comment - always PASS
secret int
}

// Declaration comment without a period, with a mixed indented code example:
// co := Message{}
// fmt.Println(co) // PASS
type Message struct {
Type string
}

// Declaration multiline comment
// second line
// third line with a period PASS.
func Sum(a, b int) int {
// Inner comment - always PASS
a++
b++

return a + b // inline comment - always PASS
}

// Declaration multiline comment
// second line
// third line without a period FAIL.
func Mult(a, b int) int {
return a * b
}

//export CgoExportedFunction PASS
func CgoExportedFunction(a, b int) int {
return a + b
}

func noComment() {
cs := C.CString("Hello from stdio\n")
C.myprint(cs)
C.free(unsafe.Pointer(cs))
}

// Comment with a URL - http://example.com/PASS
File renamed without changes.
Loading

0 comments on commit e954f0a

Please sign in to comment.