Add a fixer command.

This commit is contained in:
Amy Boyd
2016-12-22 14:02:44 +00:00
parent 948f51d2a7
commit 67e51c079a
10 changed files with 126 additions and 24 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
/build/
/symfony/

2
bin/run-fix Executable file
View File

@@ -0,0 +1,2 @@
#!/usr/bin/env bash
go run main.go fix symfony

View File

@@ -1,6 +1,8 @@
* Test on Windows.
* Implement fixers and a `fix` command.
* Add a fixer for the 'indent_style' rule.
* Add a non-standard rule to ignore directories.
* Optimize speed and memory usage.
@@ -11,3 +13,5 @@
* Add an example of how to use the `check` command in a continuous integration build or in a Git
pre-commit hook.

View File

@@ -3,7 +3,6 @@ package editorconfig
import (
"fmt"
"github.com/codegangsta/cli"
"io/ioutil"
"strconv"
"strings"
)
@@ -26,11 +25,7 @@ func CheckCommand(c *cli.Context) error {
continue
}
fileContentInBytes, err := ioutil.ReadFile(f)
if err != nil {
ExitBecauseOfInternalError("Could not read file: " + f)
}
fileContent := string(fileContentInBytes)
fileContent := MustGetFileAsString(f)
// Run full-file checkers.
for ruleName, ruleValue := range rules {

View File

@@ -30,6 +30,12 @@ func CreateCliApp() *cli.App {
Action: CheckCommand,
ArgsUsage: "[PATH1] [PATH2...]",
},
{
Name: "fix",
Usage: "Fix invalid files",
Action: FixCommand,
ArgsUsage: "[PATH1] [PATH2...]",
},
}
return app

View File

@@ -0,0 +1,78 @@
package editorconfig
import (
"fmt"
"github.com/codegangsta/cli"
"os"
"strconv"
"strings"
)
func FixCommand(c *cli.Context) error {
files, err := FindSourceFiles(c.Args())
if err != nil {
return err
}
if len(files) == 0 {
ExitBecauseOfInternalError("No files to check in " + strings.Join(c.Args(), ", "))
}
configs := FindConfigFiles(files)
for _, f := range files {
rules := GetRulesToApplyToSourcePath(f, configs)
if len(rules) == 0 {
continue
}
fileContent := MustGetFileAsString(f)
hasChanged := false
// Run full-file checkers and fixers.
for ruleName, ruleValue := range rules {
if fullFileChecker, ok := fullFileCheckers[ruleName]; ok {
result := fullFileChecker(ruleValue, fileContent)
if result.isOk {
continue
}
if result.fixer != nil {
fileContent = result.fixer(ruleValue, fileContent)
hasChanged = true
fmt.Println(f + ": " + ruleName + ": fixed")
} else {
fmt.Println(f + ": " + ruleName + ": cannot fix automatically")
}
}
}
// Run line checkers and fixers.
lines := SplitIntoLines(fileContent)
lineNo := 1
for _, line := range lines {
for ruleName, ruleValue := range rules {
if lineChecker, ok := lineCheckers[ruleName]; ok {
result := lineChecker(ruleValue, line)
if !result.isOk {
fmt.Println(f + ": line " + strconv.Itoa(lineNo) + ": " + ruleName + ": " + result.messageIfNotOk)
// Don't show more than 1 error per line.
break
}
}
}
lineNo++
}
if hasChanged {
fileHandler, err := os.Open(f)
if err != nil {
fmt.Println("Could not write to " + f)
}
fileHandler.WriteString(fileContent)
fmt.Println("Wrote to " + f)
}
}
return nil
}

View File

@@ -13,10 +13,10 @@ var fullFileCheckers = map[string]FullFileChecker{
type FullFileChecker func(ruleValue string, fileContent string) *FullFileCheckResult
// @todo - add fixers to each instance of FullFileCheckResult.
type FullFileCheckResult struct {
isOk bool
messageIfNotOk string
fixer FullFileFixer
}
func CheckEndOfLineRule(ruleValue string, fileContent string) *FullFileCheckResult {
@@ -29,29 +29,29 @@ func CheckEndOfLineRule(ruleValue string, fileContent string) *FullFileCheckResu
if ruleValueLowercase == "lf" {
if crlfRegexp.MatchString(fileContent) {
return &FullFileCheckResult{isOk: false, messageIfNotOk: "should use LF for new lines but contains CRLF"}
return &FullFileCheckResult{isOk: false, messageIfNotOk: "should use LF for new lines but contains CRLF", fixer: FixEndOfLineRule}
}
if crRegexp.MatchString(fileContent) {
return &FullFileCheckResult{isOk: false, messageIfNotOk: "should use LF for new lines but contains CR"}
return &FullFileCheckResult{isOk: false, messageIfNotOk: "should use LF for new lines but contains CR", fixer: FixEndOfLineRule}
}
}
if ruleValueLowercase == "cr" {
if crlfRegexp.MatchString(fileContent) {
return &FullFileCheckResult{isOk: false, messageIfNotOk: "should use CR for new lines but contains CRLF"}
return &FullFileCheckResult{isOk: false, messageIfNotOk: "should use CR for new lines but contains CRLF", fixer: FixEndOfLineRule}
}
if lfRegexp.MatchString(fileContent) {
return &FullFileCheckResult{isOk: false, messageIfNotOk: "should use CR for new lines but contains LF"}
return &FullFileCheckResult{isOk: false, messageIfNotOk: "should use CR for new lines but contains LF", fixer: FixEndOfLineRule}
}
}
if ruleValueLowercase == "crlf" {
fileContent := crlfRegexp.ReplaceAllString(fileContent, "")
if lfRegexp.MatchString(fileContent) {
return &FullFileCheckResult{isOk: false, messageIfNotOk: "should use CRLF for new lines but contains LF"}
return &FullFileCheckResult{isOk: false, messageIfNotOk: "should use CRLF for new lines but contains LF", fixer: FixEndOfLineRule}
}
if crRegexp.MatchString(fileContent) {
return &FullFileCheckResult{isOk: false, messageIfNotOk: "should use CRLF for new lines but contains CR"}
return &FullFileCheckResult{isOk: false, messageIfNotOk: "should use CRLF for new lines but contains CR", fixer: FixEndOfLineRule}
}
}
@@ -74,7 +74,7 @@ func CheckInsertFinalNewLineRule(ruleValue string, fileContent string) *FullFile
if endsWithFinalNewLineRegexp.MatchString(fileContent) {
return &FullFileCheckResult{isOk: true}
} else {
return &FullFileCheckResult{isOk: false, messageIfNotOk: "should end with an empty line but it does not"}
return &FullFileCheckResult{isOk: false, messageIfNotOk: "should end with an empty line but it does not", fixer: FixInsertFinalNewLineRule}
}
}
@@ -82,7 +82,7 @@ func CheckInsertFinalNewLineRule(ruleValue string, fileContent string) *FullFile
if !endsWithFinalNewLineRegexp.MatchString(fileContent) {
return &FullFileCheckResult{isOk: true}
} else {
return &FullFileCheckResult{isOk: false, messageIfNotOk: "should not end with an empty line but it does"}
return &FullFileCheckResult{isOk: false, messageIfNotOk: "should not end with an empty line but it does", fixer: FixInsertFinalNewLineRule}
}
}

View File

@@ -16,10 +16,10 @@ var lineCheckers = map[string]LineChecker{
type LineChecker func(ruleValue string, line string) *LineCheckResult
// @todo - add fixers to each instance of LineCheckResult.
type LineCheckResult struct {
isOk bool
messageIfNotOk string
fixer LineFixer
}
func HasIndentation(s string) bool {
@@ -66,7 +66,7 @@ func CheckIndentStyleRule(ruleValue string, line string) *LineCheckResult {
if IsIndentedWithTabs(line) {
return &LineCheckResult{isOk: false, messageIfNotOk: "starts with tab instead of space"}
} else if IsIndentedWithMixedTabsAndSpaces(line) {
return &LineCheckResult{isOk: false, messageIfNotOk: "indented with mix of tabs and spaces instead of just tabs"}
return &LineCheckResult{isOk: false, messageIfNotOk: "indented with mix of tabs and spaces instead of just spaces"}
} else {
return &LineCheckResult{isOk: true}
}
@@ -94,7 +94,11 @@ func CheckIndentSizeRule(ruleValue string, line string) *LineCheckResult {
}
if strings.HasPrefix(line, "\t") {
return &LineCheckResult{isOk: false, messageIfNotOk: "should be indented with spaces but is indented with tabs"}
return &LineCheckResult{
isOk: false,
messageIfNotOk: "should be indented with spaces but is indented with tabs",
fixer: FixTabIndentationToSpaces,
}
}
// Indented with spaces. Ensure the number of spaces is divisible by the rule value, but also
@@ -108,11 +112,19 @@ func CheckIndentSizeRule(ruleValue string, line string) *LineCheckResult {
return &LineCheckResult{isOk: true}
}
if IsIndentedWithTabs(trimmedLine) {
return &LineCheckResult{isOk: false, messageIfNotOk: "indented with mix of spaces and tabs instead of just spaces"}
return &LineCheckResult{
isOk: false,
messageIfNotOk: "indented with mix of spaces and tabs instead of just spaces",
fixer: FixMixedIndentationToSpaces,
}
}
if HasIndentation(trimmedLine) {
leftSpaces := len(line) - len(strings.TrimLeft(line, " "))
return &LineCheckResult{isOk: false, messageIfNotOk: "starts with " + strconv.Itoa(leftSpaces) + " spaces which does not divide by " + ruleValue}
leftSpaces := GetNumberOfLeftSpaces(line)
return &LineCheckResult{
isOk: false,
messageIfNotOk: "starts with " + strconv.Itoa(leftSpaces) + " spaces which does not divide by " + ruleValue,
fixer: FixUndividableIndentationToNearestSpacesAmount,
}
}
return &LineCheckResult{isOk: true}
@@ -129,7 +141,7 @@ func CheckTrimTrailingWhitespaceRule(ruleValue string, line string) *LineCheckRe
trimmed := strings.TrimRight(line, " \t")
if len(line) != len(trimmed) {
return &LineCheckResult{isOk: false, messageIfNotOk: "line has trailing whitespace"}
return &LineCheckResult{isOk: false, messageIfNotOk: "line has trailing whitespace", fixer: FixTrimTrailingWhitespaceRule}
}
return &LineCheckResult{isOk: true}

View File

@@ -32,7 +32,7 @@ func TestCheckIndentStyleRule(t *testing.T) {
ExpectPass(" line", "space", f, t)
ExpectPass(" ", "space", f, t)
ExpectPass(" line", "space", f, t)
ExpectFail(" \tline", "space", f, t, "indented with mix of tabs and spaces instead of just tabs")
ExpectFail(" \tline", "space", f, t, "indented with mix of tabs and spaces instead of just spaces")
ExpectFail("\tline", "space", f, t, "starts with tab instead of space")
ExpectFail("\t line", "space", f, t, "starts with tab instead of space")

View File

@@ -17,11 +17,15 @@ var lfRegexp = regexp.MustCompile(`\n`)
var crRegexp = regexp.MustCompile(`\r`)
var crlfRegexp = regexp.MustCompile(`\r\n`)
var endsWithTabsAndSpacesRegexp = regexp.MustCompile("[ \t]+$")
var endsWithFinalNewLineRegexp = regexp.MustCompile(`(\n|\r|\r\n)$`)
var hasIndentationRegexp = regexp.MustCompile(`^[\t ]`)
var hasNoIndentationRegexp = regexp.MustCompile(`^([^\t ]|$)`)
// @todo - this doens't match "space tab space" or "tab space tab"
var indentedWithMixedTabsAndSpacesRegexp = regexp.MustCompile(`^(\t+ +| +\t+)`)
var indentedWithTabsRegexp = regexp.MustCompile(`^\t+`)
var indentedWithTabsThenCommentLineRegexp = regexp.MustCompile(`^\t+ \*`)
var indentedWithSpacesRegexp = regexp.MustCompile(`^ +`)