Add a fixer command.
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
78
editorconfig/fix_command.go
Normal file
78
editorconfig/fix_command.go
Normal 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
|
||||
}
|
||||
@@ -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}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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(`^ +`)
|
||||
|
||||
Reference in New Issue
Block a user