github.com/editorconfig-checker/editorconfig-checker@v0.0.0-20231102090242-ddae3e68851e/pkg/validation/validators/validators.go (about) 1 // Package validators provides functions to validate if the rules of the `.editorconfig` are respected 2 package validators 3 4 import ( 5 "errors" 6 "fmt" 7 "regexp" 8 "strings" 9 "unicode/utf8" 10 11 "github.com/editorconfig-checker/editorconfig-checker/pkg/config" 12 "github.com/editorconfig-checker/editorconfig-checker/pkg/utils" 13 ) 14 15 // Indentation validates a files indentation 16 func Indentation(line string, indentStyle string, indentSize int, config config.Config) error { 17 if indentStyle == "space" { 18 return Space(line, indentSize, config) 19 } else if indentStyle == "tab" { 20 return Tab(line, config) 21 } 22 23 // if no indentStyle is given it should be valid 24 return nil 25 } 26 27 // Space validates if a line is indented correctly respecting the indentSize 28 func Space(line string, indentSize int, config config.Config) error { 29 if len(line) > 0 && indentSize > 0 { 30 if !config.Disable.IndentSize { 31 // match recurring spaces indentSize times - this can be recurring or never 32 // match either a space followed by a * and maybe a space (block-comments) 33 // or match everything despite a space or tab-character 34 regexpPattern := fmt.Sprintf("^( {%d})*( \\* ?|[^ \t]|$)", indentSize) 35 matched, _ := regexp.MatchString(regexpPattern, line) 36 37 if !matched { 38 return fmt.Errorf("Wrong amount of left-padding spaces(want multiple of %d)", indentSize) 39 } 40 } else { 41 // match recurring spaces and everything except tab characters 42 regexpPattern := `^( )*([^ \t]|$)` 43 matched, _ := regexp.MatchString(regexpPattern, line) 44 45 if !matched { 46 return fmt.Errorf("Wrong indent style found (tabs instead of spaces)") 47 } 48 } 49 } 50 51 return nil 52 } 53 54 // Tab validates if a line is indented with only tabs 55 func Tab(line string, config config.Config) error { 56 if len(line) > 0 { 57 // match starting with one or more tabs followed by a non-whitespace char 58 // OR 59 // match starting with one or more tabs, followed by one space and followed by at least one non-whitespace character 60 // OR 61 // match starting with a space followed by at least one non-whitespace character 62 63 regexpPattern := "^(\t)*( \\* ?|[^ \t]|$)" 64 65 if config.SpacesAftertabs { 66 regexpPattern = "(^(\t)*\\S)|(^(\t)+( )*\\S)|(^ \\S)" 67 } 68 69 matched, _ := regexp.MatchString(regexpPattern, line) 70 71 if !matched { 72 return errors.New("Wrong indentation type(spaces instead of tabs)") 73 } 74 75 } 76 77 return nil 78 } 79 80 // TrailingWhitespace validates if a line has trailing whitespace 81 func TrailingWhitespace(line string, trimTrailingWhitespace bool) error { 82 if trimTrailingWhitespace { 83 regexpPattern := "^.*[ \t]+$" 84 matched, _ := regexp.MatchString(regexpPattern, line) 85 86 if matched { 87 return errors.New("Trailing whitespace") 88 } 89 } 90 91 return nil 92 } 93 94 // FinalNewline validates if a file has a final and correct newline 95 func FinalNewline(fileContent string, insertFinalNewline string, endOfLine string) error { 96 if endOfLine != "" && insertFinalNewline == "true" { 97 expectedEolChar := utils.GetEolChar(endOfLine) 98 if !strings.HasSuffix(fileContent, expectedEolChar) || (expectedEolChar == "\n" && strings.HasSuffix(fileContent, "\r\n")) { 99 return errors.New("Wrong line endings or no final newline") 100 } 101 } else { 102 regexpPattern := "(\n|\r|\r\n)$" 103 hasFinalNewline, _ := regexp.MatchString(regexpPattern, fileContent) 104 105 if insertFinalNewline == "false" && hasFinalNewline { 106 return errors.New("No final newline expected") 107 } 108 109 if insertFinalNewline == "true" && !hasFinalNewline { 110 return errors.New("Final newline expected") 111 } 112 } 113 114 return nil 115 } 116 117 // LineEnding validates if a file uses the correct line endings 118 func LineEnding(fileContent string, endOfLine string) error { 119 if endOfLine != "" { 120 expectedEolChar := utils.GetEolChar(endOfLine) 121 expectedEols := len(strings.Split(fileContent, expectedEolChar)) 122 lfEols := len(strings.Split(fileContent, "\n")) 123 crEols := len(strings.Split(fileContent, "\r")) 124 crlfEols := len(strings.Split(fileContent, "\r\n")) 125 126 switch endOfLine { 127 case "lf": 128 if !(expectedEols == lfEols && crEols == 1 && crlfEols == 1) { 129 return errors.New("Not all lines have the correct end of line character") 130 } 131 case "cr": 132 if !(expectedEols == crEols && lfEols == 1 && crlfEols == 1) { 133 return errors.New("Not all lines have the correct end of line character") 134 } 135 case "crlf": 136 // A bit hacky because \r\n matches \r and \n 137 if !(expectedEols == crlfEols && lfEols == expectedEols && crEols == expectedEols) { 138 return errors.New("Not all lines have the correct end of line character") 139 } 140 } 141 } 142 143 return nil 144 } 145 146 func MaxLineLength(line string, maxLineLength int, charSet string) error { 147 var length int 148 if charSet == "utf-8" || charSet == "utf-8-bom" { 149 if charSet == "utf-8-bom" && strings.HasPrefix(line, "\xEF\xBB\xBF") { 150 line = line[3:] // strip BOM 151 } 152 length = utf8.RuneCountInString(line) 153 } else { 154 // TODO: Handle utf-16be and utf-16le properly. Unfortunately, Go doesn't provide a utf16.RuneCountinString() function 155 // Just go with byte count 156 length = len(line) 157 } 158 159 if length > maxLineLength { 160 return fmt.Errorf("Line too long (%d instead of %d)", length, maxLineLength) 161 } 162 163 return nil 164 }