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  }