Add line checker functions.
This commit is contained in:
144
editorconfig/line_checkers.go
Normal file
144
editorconfig/line_checkers.go
Normal 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}
|
||||||
|
}
|
||||||
105
editorconfig/line_checkers_test.go
Normal file
105
editorconfig/line_checkers_test.go
Normal 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")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user