Add a fixer command.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
/build/
|
/build/
|
||||||
|
/symfony/
|
||||||
|
|||||||
2
bin/run-fix
Executable file
2
bin/run-fix
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
go run main.go fix symfony
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
* Test on Windows.
|
* 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.
|
* 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
|
* Add an example of how to use the `check` command in a continuous integration build or in a Git
|
||||||
pre-commit hook.
|
pre-commit hook.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package editorconfig
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
"io/ioutil"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -26,11 +25,7 @@ func CheckCommand(c *cli.Context) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
fileContentInBytes, err := ioutil.ReadFile(f)
|
fileContent := MustGetFileAsString(f)
|
||||||
if err != nil {
|
|
||||||
ExitBecauseOfInternalError("Could not read file: " + f)
|
|
||||||
}
|
|
||||||
fileContent := string(fileContentInBytes)
|
|
||||||
|
|
||||||
// Run full-file checkers.
|
// Run full-file checkers.
|
||||||
for ruleName, ruleValue := range rules {
|
for ruleName, ruleValue := range rules {
|
||||||
|
|||||||
@@ -30,6 +30,12 @@ func CreateCliApp() *cli.App {
|
|||||||
Action: CheckCommand,
|
Action: CheckCommand,
|
||||||
ArgsUsage: "[PATH1] [PATH2...]",
|
ArgsUsage: "[PATH1] [PATH2...]",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "fix",
|
||||||
|
Usage: "Fix invalid files",
|
||||||
|
Action: FixCommand,
|
||||||
|
ArgsUsage: "[PATH1] [PATH2...]",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return app
|
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
|
type FullFileChecker func(ruleValue string, fileContent string) *FullFileCheckResult
|
||||||
|
|
||||||
// @todo - add fixers to each instance of FullFileCheckResult.
|
|
||||||
type FullFileCheckResult struct {
|
type FullFileCheckResult struct {
|
||||||
isOk bool
|
isOk bool
|
||||||
messageIfNotOk string
|
messageIfNotOk string
|
||||||
|
fixer FullFileFixer
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckEndOfLineRule(ruleValue string, fileContent string) *FullFileCheckResult {
|
func CheckEndOfLineRule(ruleValue string, fileContent string) *FullFileCheckResult {
|
||||||
@@ -29,29 +29,29 @@ func CheckEndOfLineRule(ruleValue string, fileContent string) *FullFileCheckResu
|
|||||||
|
|
||||||
if ruleValueLowercase == "lf" {
|
if ruleValueLowercase == "lf" {
|
||||||
if crlfRegexp.MatchString(fileContent) {
|
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) {
|
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 ruleValueLowercase == "cr" {
|
||||||
if crlfRegexp.MatchString(fileContent) {
|
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) {
|
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" {
|
if ruleValueLowercase == "crlf" {
|
||||||
fileContent := crlfRegexp.ReplaceAllString(fileContent, "")
|
fileContent := crlfRegexp.ReplaceAllString(fileContent, "")
|
||||||
if lfRegexp.MatchString(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) {
|
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) {
|
if endsWithFinalNewLineRegexp.MatchString(fileContent) {
|
||||||
return &FullFileCheckResult{isOk: true}
|
return &FullFileCheckResult{isOk: true}
|
||||||
} else {
|
} 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) {
|
if !endsWithFinalNewLineRegexp.MatchString(fileContent) {
|
||||||
return &FullFileCheckResult{isOk: true}
|
return &FullFileCheckResult{isOk: true}
|
||||||
} else {
|
} 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
|
type LineChecker func(ruleValue string, line string) *LineCheckResult
|
||||||
|
|
||||||
// @todo - add fixers to each instance of LineCheckResult.
|
|
||||||
type LineCheckResult struct {
|
type LineCheckResult struct {
|
||||||
isOk bool
|
isOk bool
|
||||||
messageIfNotOk string
|
messageIfNotOk string
|
||||||
|
fixer LineFixer
|
||||||
}
|
}
|
||||||
|
|
||||||
func HasIndentation(s string) bool {
|
func HasIndentation(s string) bool {
|
||||||
@@ -66,7 +66,7 @@ func CheckIndentStyleRule(ruleValue string, line string) *LineCheckResult {
|
|||||||
if IsIndentedWithTabs(line) {
|
if IsIndentedWithTabs(line) {
|
||||||
return &LineCheckResult{isOk: false, messageIfNotOk: "starts with tab instead of space"}
|
return &LineCheckResult{isOk: false, messageIfNotOk: "starts with tab instead of space"}
|
||||||
} else if IsIndentedWithMixedTabsAndSpaces(line) {
|
} 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 {
|
} else {
|
||||||
return &LineCheckResult{isOk: true}
|
return &LineCheckResult{isOk: true}
|
||||||
}
|
}
|
||||||
@@ -94,7 +94,11 @@ func CheckIndentSizeRule(ruleValue string, line string) *LineCheckResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(line, "\t") {
|
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
|
// 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}
|
return &LineCheckResult{isOk: true}
|
||||||
}
|
}
|
||||||
if IsIndentedWithTabs(trimmedLine) {
|
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) {
|
if HasIndentation(trimmedLine) {
|
||||||
leftSpaces := len(line) - len(strings.TrimLeft(line, " "))
|
leftSpaces := GetNumberOfLeftSpaces(line)
|
||||||
return &LineCheckResult{isOk: false, messageIfNotOk: "starts with " + strconv.Itoa(leftSpaces) + " spaces which does not divide by " + ruleValue}
|
return &LineCheckResult{
|
||||||
|
isOk: false,
|
||||||
|
messageIfNotOk: "starts with " + strconv.Itoa(leftSpaces) + " spaces which does not divide by " + ruleValue,
|
||||||
|
fixer: FixUndividableIndentationToNearestSpacesAmount,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &LineCheckResult{isOk: true}
|
return &LineCheckResult{isOk: true}
|
||||||
@@ -129,7 +141,7 @@ func CheckTrimTrailingWhitespaceRule(ruleValue string, line string) *LineCheckRe
|
|||||||
|
|
||||||
trimmed := strings.TrimRight(line, " \t")
|
trimmed := strings.TrimRight(line, " \t")
|
||||||
if len(line) != len(trimmed) {
|
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}
|
return &LineCheckResult{isOk: true}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func TestCheckIndentStyleRule(t *testing.T) {
|
|||||||
ExpectPass(" line", "space", f, t)
|
ExpectPass(" line", "space", f, t)
|
||||||
ExpectPass(" ", "space", f, t)
|
ExpectPass(" ", "space", f, t)
|
||||||
ExpectPass(" line", "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("\tline", "space", f, t, "starts with tab instead of space")
|
||||||
ExpectFail("\t line", "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 crRegexp = regexp.MustCompile(`\r`)
|
||||||
var crlfRegexp = regexp.MustCompile(`\r\n`)
|
var crlfRegexp = regexp.MustCompile(`\r\n`)
|
||||||
|
|
||||||
|
var endsWithTabsAndSpacesRegexp = regexp.MustCompile("[ \t]+$")
|
||||||
var endsWithFinalNewLineRegexp = regexp.MustCompile(`(\n|\r|\r\n)$`)
|
var endsWithFinalNewLineRegexp = regexp.MustCompile(`(\n|\r|\r\n)$`)
|
||||||
|
|
||||||
var hasIndentationRegexp = regexp.MustCompile(`^[\t ]`)
|
var hasIndentationRegexp = regexp.MustCompile(`^[\t ]`)
|
||||||
var hasNoIndentationRegexp = 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 indentedWithMixedTabsAndSpacesRegexp = regexp.MustCompile(`^(\t+ +| +\t+)`)
|
||||||
|
|
||||||
var indentedWithTabsRegexp = regexp.MustCompile(`^\t+`)
|
var indentedWithTabsRegexp = regexp.MustCompile(`^\t+`)
|
||||||
var indentedWithTabsThenCommentLineRegexp = regexp.MustCompile(`^\t+ \*`)
|
var indentedWithTabsThenCommentLineRegexp = regexp.MustCompile(`^\t+ \*`)
|
||||||
var indentedWithSpacesRegexp = regexp.MustCompile(`^ +`)
|
var indentedWithSpacesRegexp = regexp.MustCompile(`^ +`)
|
||||||
|
|||||||
Reference in New Issue
Block a user