github.com/blend/go-sdk@v1.20220411.3/profanity/rule_spec.go (about)

     1  /*
     2  
     3  Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file.
     5  
     6  */
     7  
     8  package profanity
     9  
    10  import (
    11  	"fmt"
    12  	"strings"
    13  
    14  	"github.com/blend/go-sdk/ex"
    15  	"github.com/blend/go-sdk/validate"
    16  )
    17  
    18  var (
    19  	_ Rule = (*RuleSpec)(nil)
    20  )
    21  
    22  // Errors
    23  const (
    24  	ErrRuleSpecMultipleRules ex.Class = "rule spec invalid; multiple rule types specified"
    25  	ErrRuleSpecRuleMissing   ex.Class = "rule spec invalid; at least one rule type is required"
    26  )
    27  
    28  // RuleSpec is a serialized rule.
    29  type RuleSpec struct {
    30  	// ID is a unique identifier for the rule.
    31  	ID string `yaml:"id"`
    32  	// SourceFile is the rules file path the rule came from.
    33  	SourceFile string `yaml:"-"`
    34  	// Description is a descriptive message for the rule.
    35  	Description string `yaml:"description,omitempty"`
    36  	// Files is the glob filter for inclusion and exclusion'
    37  	// for this specific rule spec.
    38  	Files GlobFilter `yaml:"files,omitempty"`
    39  	// RuleSpecRules are the rules for the rule spec.
    40  	RuleSpecRules `yaml:",inline"`
    41  }
    42  
    43  // Validate validates the RuleSpec.
    44  func (r RuleSpec) Validate() error {
    45  	if err := validate.String(&r.ID).Required()(); err != nil {
    46  		return validate.Error(validate.ErrCause(err), r)
    47  	}
    48  	if err := r.Files.Validate(); err != nil {
    49  		return validate.Error(validate.ErrCause(err), r)
    50  	}
    51  	if err := r.RuleSpecRules.Validate(); err != nil {
    52  		return validate.Error(validate.ErrCause(err), r)
    53  	}
    54  	return nil
    55  
    56  }
    57  
    58  // String returns a string representation of the rule.
    59  func (r RuleSpec) String() string {
    60  	var tokens []string
    61  
    62  	if len(r.ID) > 0 {
    63  		tokens = append(tokens, r.ID)
    64  	}
    65  	if len(r.Description) > 0 {
    66  		tokens = append(tokens, "`"+r.Description+"`")
    67  	}
    68  	tokens = append(tokens, r.Files.String())
    69  	tokens = append(tokens, r.RuleSpecRules.String())
    70  	return fmt.Sprintf("[%s]", strings.Join(tokens, " "))
    71  }
    72  
    73  // RuleSpecRules are the specific rules for a given RuleSpec.
    74  //
    75  // The usage of this should be that only _one_ of these rules
    76  // should be set for a given rule spec.
    77  type RuleSpecRules struct {
    78  	// Contains implies we should fail if a file contains a given string.
    79  	Contents *Contents `yaml:"contents,omitempty"`
    80  	// GoImportsContain enforces that a given list of imports are used.
    81  	GoImports *GoImports `yaml:"goImports,omitempty"`
    82  	// GoCalls enforces that a given list of imports are used.
    83  	GoCalls *GoCalls `yaml:"goCalls,omitempty"`
    84  }
    85  
    86  // Rules returns the rules from the spec.
    87  //
    88  // Note: you should add new rule types here and on the type itself.
    89  func (r RuleSpecRules) Rules() (output []Rule) {
    90  	if r.Contents != nil {
    91  		output = append(output, r.Contents)
    92  	}
    93  	if r.GoImports != nil {
    94  		output = append(output, r.GoImports)
    95  	}
    96  	if r.GoCalls != nil {
    97  		output = append(output, r.GoCalls)
    98  	}
    99  	return
   100  }
   101  
   102  // Rule returns the active rule from the spec.
   103  func (r RuleSpecRules) Rule() Rule {
   104  	if rules := r.Rules(); len(rules) > 0 {
   105  		return rules[0]
   106  	}
   107  	return nil
   108  }
   109  
   110  // Check applies the rule.
   111  func (r RuleSpecRules) Check(filename string, contents []byte) (result RuleResult) {
   112  	if result = r.Rule().Check(filename, contents); !result.OK {
   113  		return
   114  	}
   115  	result = RuleResult{
   116  		OK:   true,
   117  		File: filename,
   118  	}
   119  	return
   120  }
   121  
   122  // Validate validates the rule spec rules.
   123  func (r RuleSpecRules) Validate() error {
   124  	if len(r.Rules()) > 1 {
   125  		return validate.Error(ErrRuleSpecMultipleRules, nil)
   126  	}
   127  	if len(r.Rules()) == 0 {
   128  		return validate.Error(ErrRuleSpecRuleMissing, nil)
   129  	}
   130  	if typed, ok := r.Rule().(interface {
   131  		Validate() error
   132  	}); ok {
   133  		if err := typed.Validate(); err != nil {
   134  			return err
   135  		}
   136  	}
   137  	return nil
   138  }
   139  
   140  // String implements fmt.Stringer.
   141  func (r RuleSpecRules) String() string {
   142  	var tokens []string
   143  	for _, rule := range r.Rules() {
   144  		if typed, ok := rule.(fmt.Stringer); ok {
   145  			tokens = append(tokens, typed.String())
   146  		}
   147  	}
   148  	return strings.Join(tokens, " ")
   149  }