Add line checker functions.

This commit is contained in:
Amy Boyd
2016-06-24 17:14:02 +01:00
parent 31aa4fb5d1
commit e2712a4c82
2 changed files with 249 additions and 0 deletions

View File

@@ -0,0 +1,144 @@
package editorconfig
import (
"regexp"
"strconv"
"strings"
)
// tab_width, charset, end_of_line, insert_final_newline and root do not have any affect on our line
// checkers (they apply to the full files, not lines).
var lineCheckers = map[string]LineChecker{
"indent_style": CheckIndentStyleRule,
"indent_size": CheckIndentSizeRule,
"trim_trailing_whitespace": CheckTrimTrailingWhitespaceRule,
}
type LineChecker func(ruleValue string, line string) *LineCheckResult
// @todo - add fixers to each instance of LineCheckResult.
type LineCheckResult struct {
isOk bool
messageIfNotOk string
}
var hasIndentationRegexp = regexp.MustCompile(`^[\t ]`)
var hasNoIndentationRegexp = regexp.MustCompile(`^([^\t ]|$)`)
var indentedWithMixedTabsAndSpacesRegexp = regexp.MustCompile(`^(\t+ +| +\t+)`)
var indentedWithTabsRegexp = regexp.MustCompile(`^\t+`)
var indentedWithTabsThenCommentLineRegexp = regexp.MustCompile(`^\t+ \*`)
var indentedWithSpacesRegexp = regexp.MustCompile(`^ +`)
func HasIndentation(s string) bool {
return hasIndentationRegexp.MatchString(s)
}
func HasNoIndentation(s string) bool {
return hasNoIndentationRegexp.MatchString(s)
}
func IsIndentedWithMixedTabsAndSpaces(s string) bool {
return indentedWithMixedTabsAndSpacesRegexp.MatchString(s)
}
func IsIndentedWithTabs(s string) bool {
return indentedWithTabsRegexp.MatchString(s)
}
// This allows comments like /**\n\t *\n\t */
func IsIndentedWithTabsThenCommentLine(s string) bool {
return indentedWithTabsThenCommentLineRegexp.MatchString(s)
}
func IsIndentedWithSpaces(s string) bool {
return indentedWithSpacesRegexp.MatchString(s)
}
func CheckIndentStyleRule(ruleValue string, line string) *LineCheckResult {
if HasNoIndentation(line) {
return &LineCheckResult{isOk: true}
}
if strings.ToLower(ruleValue) == "tab" {
if IsIndentedWithSpaces(line) {
return &LineCheckResult{isOk: false, messageIfNotOk: "starts with space instead of tab"}
} else if IsIndentedWithMixedTabsAndSpaces(line) && !IsIndentedWithTabsThenCommentLine(line) {
return &LineCheckResult{isOk: false, messageIfNotOk: "indented with mix of tabs and spaces instead of just tabs"}
} else {
return &LineCheckResult{isOk: true}
}
}
if strings.ToLower(ruleValue) == "space" {
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"}
} else {
return &LineCheckResult{isOk: true}
}
}
return &LineCheckResult{isOk: false, messageIfNotOk: "invalid value for indent_style: " + ruleValue}
}
func CheckIndentSizeRule(ruleValue string, line string) *LineCheckResult {
if ruleValue == "tab" {
return &LineCheckResult{isOk: true}
}
if HasNoIndentation(line) {
return &LineCheckResult{isOk: true}
}
ruleValueInt, err := strconv.Atoi(ruleValue)
if err != nil {
return &LineCheckResult{isOk: false, messageIfNotOk: "value is not an integer: " + ruleValue}
}
if ruleValueInt < 1 {
return &LineCheckResult{isOk: false, messageIfNotOk: "number of spaces must be 1 or more, is: " + ruleValue}
}
if strings.HasPrefix(line, "\t") {
return &LineCheckResult{isOk: false, messageIfNotOk: "should be indented with spaces but is indented with tabs"}
}
// Indented with spaces. Ensure the number of spaces is divisible by the rule value, but also
// allow an extra space followed by * to allow for comments like /**\n *\n */ (note the
// extra space before the * on the 2nd and 3rd lines).
trimmedLine := line
for strings.HasPrefix(trimmedLine, strings.Repeat(" ", ruleValueInt)) {
trimmedLine = (trimmedLine)[ruleValueInt:]
}
if strings.HasPrefix(trimmedLine, " *") {
return &LineCheckResult{isOk: true}
}
if IsIndentedWithTabs(trimmedLine) {
return &LineCheckResult{isOk: false, messageIfNotOk: "indented with mix of spaces and tabs instead of just spaces"}
}
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}
}
return &LineCheckResult{isOk: true}
}
func CheckTrimTrailingWhitespaceRule(ruleValue string, line string) *LineCheckResult {
if strings.ToLower(ruleValue) == "false" {
return &LineCheckResult{isOk: true}
}
if strings.ToLower(ruleValue) != "true" {
return &LineCheckResult{isOk: false, messageIfNotOk: "value must be true or false, but is: " + ruleValue}
}
trimmed := strings.TrimRight(line, " \t")
if len(line) != len(trimmed) {
return &LineCheckResult{isOk: false, messageIfNotOk: "line has trailing whitespace"}
}
return &LineCheckResult{isOk: true}
}

View File

@@ -0,0 +1,105 @@
package editorconfig
import (
"testing"
)
func ExpectPass(line string, ruleValue string, lineChecker LineChecker, t *testing.T) {
result := lineChecker(ruleValue, line)
if !result.isOk {
t.Error("Expected line to pass, but it failed: \"" + line + "\" for rule value \"" + ruleValue + "\", had error message: " + result.messageIfNotOk)
}
}
func ExpectFail(line string, ruleValue string, lineChecker LineChecker, t *testing.T, expectedError string) {
result := lineChecker(ruleValue, line)
if result.isOk {
t.Error("Expected line to fail, but it passed: \"" + line + "\" for rule value \"" + ruleValue + "\"")
return
}
if !result.isOk && result.messageIfNotOk != expectedError {
t.Error("Line \"" + line + "\" failed with error message \"" + result.messageIfNotOk + "\" but had expected \"" + expectedError + "\"")
return
}
}
func TestCheckIndentStyleRule(t *testing.T) {
f := CheckIndentStyleRule
ExpectPass("", "space", f, t)
ExpectPass("line", "space", f, 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, "starts with tab instead of space")
ExpectFail("\t line", "space", f, t, "starts with tab instead of space")
ExpectPass("", "tab", f, t)
ExpectPass("line", "tab", f, t)
ExpectPass("\n", "tab", f, t)
ExpectPass("\tline", "tab", f, t)
ExpectPass("\t\tline", "tab", f, t)
ExpectFail(" \tline", "tab", f, t, "starts with space instead of tab")
ExpectFail("\t line", "tab", f, t, "indented with mix of tabs and spaces instead of just tabs")
ExpectFail("\t ", "tab", f, t, "indented with mix of tabs and spaces instead of just tabs")
// Allow comments like /**\n\t *\n\t */
ExpectPass("\t *line", "tab", f, t)
ExpectFail(" line", "dinosaurs", f, t, "invalid value for indent_style: dinosaurs")
}
func TestCheckIndentSizeRule(t *testing.T) {
f := CheckIndentSizeRule
// 'indent_size=tab' can never fail this rule.
ExpectPass("line", "tab", f, t)
ExpectPass(" line", "tab", f, t)
ExpectPass(" line", "tab", f, t)
ExpectPass("", "2", f, t)
ExpectPass("line", "2", f, t)
ExpectPass(" line", "2", f, t)
ExpectPass(" line", "2", f, t)
ExpectPass(" line", "2", f, t)
ExpectFail(" line", "2", f, t, "starts with 1 spaces which does not divide by 2")
ExpectFail(" line", "2", f, t, "starts with 3 spaces which does not divide by 2")
ExpectFail(" line", "2", f, t, "starts with 5 spaces which does not divide by 2")
ExpectFail("\tline", "2", f, t, "should be indented with spaces but is indented with tabs")
ExpectFail("\t\tline", "2", f, t, "should be indented with spaces but is indented with tabs")
ExpectFail(" \tline", "2", f, t, "indented with mix of spaces and tabs instead of just spaces")
// Allow comments like /**\n *\n */ (note the extra space before the * on the 2nd and 3rd lines)
ExpectPass(" * line", "2", f, t)
ExpectFail(" ^ line", "2", f, t, "starts with 3 spaces which does not divide by 2")
ExpectPass("", "3", f, t)
ExpectPass("line", "3", f, t)
ExpectPass(" line", "3", f, t)
ExpectFail(" line", "3", f, t, "starts with 5 spaces which does not divide by 3")
ExpectFail(" anything", "0", f, t, "number of spaces must be 1 or more, is: 0")
ExpectFail(" anything", "-1", f, t, "number of spaces must be 1 or more, is: -1")
ExpectFail(" anything", "asd", f, t, "value is not an integer: asd")
}
func TestCheckTrimTrailingWhitespaceRule(t *testing.T) {
f := CheckTrimTrailingWhitespaceRule
// 'trim_trailing_whitespace=false' can never fail this rule.
ExpectPass("line", "false", f, t)
ExpectPass("line ", "false", f, t)
ExpectPass("line\t", "false", f, t)
ExpectPass("line \t \t \t", "false", f, t)
ExpectPass("line", "true", f, t)
ExpectPass("line line", "true", f, t)
ExpectPass("line line", "true", f, t)
ExpectFail("line line ", "true", f, t, "line has trailing whitespace")
ExpectFail("line line\t", "true", f, t, "line has trailing whitespace")
ExpectFail("line line ", "true", f, t, "line has trailing whitespace")
ExpectFail("line line \t", "true", f, t, "line has trailing whitespace")
}