github.com/yoheimuta/protolint@v0.49.8-0.20240515023657-4ecaebb7575d/internal/addon/rules/maxLineLengthRule.go (about)

     1  package rules
     2  
     3  import (
     4  	"bufio"
     5  	"os"
     6  	"strings"
     7  	"unicode/utf8"
     8  
     9  	"github.com/yoheimuta/go-protoparser/v4/parser"
    10  	"github.com/yoheimuta/go-protoparser/v4/parser/meta"
    11  
    12  	"github.com/yoheimuta/protolint/linter/disablerule"
    13  	"github.com/yoheimuta/protolint/linter/report"
    14  	"github.com/yoheimuta/protolint/linter/rule"
    15  )
    16  
    17  const (
    18  	// Keep the line length to 80 characters.
    19  	// See https://developers.google.com/protocol-buffers/docs/style#standard-file-formatting
    20  	defaultMaxChars = 80
    21  
    22  	defaultTabChars = 4
    23  )
    24  
    25  // MaxLineLengthRule enforces a maximum line length to increase code readability and maintainability.
    26  // The length of a line is defined as the number of Unicode characters in the line.
    27  type MaxLineLengthRule struct {
    28  	RuleWithSeverity
    29  	maxChars int
    30  	tabChars int
    31  }
    32  
    33  // NewMaxLineLengthRule creates a new MaxLineLengthRule.
    34  func NewMaxLineLengthRule(
    35  	severity rule.Severity,
    36  	maxChars int,
    37  	tabChars int,
    38  ) MaxLineLengthRule {
    39  	if maxChars == 0 {
    40  		maxChars = defaultMaxChars
    41  	}
    42  	if tabChars == 0 {
    43  		tabChars = defaultTabChars
    44  	}
    45  	return MaxLineLengthRule{
    46  		RuleWithSeverity: RuleWithSeverity{severity: severity},
    47  		maxChars:         maxChars,
    48  		tabChars:         tabChars,
    49  	}
    50  }
    51  
    52  // ID returns the ID of this rule.
    53  func (r MaxLineLengthRule) ID() string {
    54  	return "MAX_LINE_LENGTH"
    55  }
    56  
    57  // Purpose returns the purpose of this rule.
    58  func (r MaxLineLengthRule) Purpose() string {
    59  	return "Enforces a maximum line length."
    60  }
    61  
    62  // IsOfficial decides whether or not this rule belongs to the official guide.
    63  func (r MaxLineLengthRule) IsOfficial() bool {
    64  	return true
    65  }
    66  
    67  // Apply applies the rule to the proto.
    68  func (r MaxLineLengthRule) Apply(proto *parser.Proto) (
    69  	failures []report.Failure,
    70  	err error,
    71  ) {
    72  	fileName := proto.Meta.Filename
    73  	reader, err := os.Open(fileName)
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  	defer func() {
    78  		closeErr := reader.Close()
    79  		if err != nil {
    80  			return
    81  		}
    82  		if closeErr != nil {
    83  			err = closeErr
    84  		}
    85  	}()
    86  
    87  	var lines []string
    88  	scanner := bufio.NewScanner(reader)
    89  	for scanner.Scan() {
    90  		lines = append(lines, scanner.Text())
    91  	}
    92  
    93  	if err := scanner.Err(); err != nil {
    94  		return nil, err
    95  	}
    96  
    97  	disablerule.NewInterpreter(r.ID()).CallEachIfValid(
    98  		lines,
    99  		func(index int, line string) {
   100  			line = strings.Replace(line, "\t", strings.Repeat(" ", r.tabChars), -1)
   101  			lineCount := utf8.RuneCountInString(line)
   102  			if r.maxChars < lineCount {
   103  				failures = append(failures, report.Failuref(
   104  					meta.Position{
   105  						Filename: fileName,
   106  						Line:     index + 1,
   107  						Column:   1,
   108  					},
   109  					r.ID(),
   110  					"The line length is %d, but it must be shorter than %d",
   111  					lineCount,
   112  					r.maxChars,
   113  				))
   114  			}
   115  		},
   116  	)
   117  	return failures, nil
   118  }