github.com/blend/go-sdk@v1.20240719.1/profanity/generics.go (about)

     1  /*
     2  
     3  Copyright (c) 2024 - 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  	"go/ast"
    12  	"go/parser"
    13  	"go/token"
    14  	"path/filepath"
    15  )
    16  
    17  var (
    18  	_ Rule = (*NoGenericDecls)(nil)
    19  )
    20  
    21  // NoGenericDecls returns a profanity error if a generic function or type declaration exists
    22  type NoGenericDecls struct {
    23  	Enabled bool `yaml:"enabled"`
    24  }
    25  
    26  // Validate implements validation for the rule.
    27  func (ngd NoGenericDecls) Validate() error {
    28  	return nil
    29  }
    30  
    31  // Check implements Rule.
    32  func (ngd NoGenericDecls) Check(filename string, contents []byte) RuleResult {
    33  	if !ngd.Enabled {
    34  		return RuleResult{OK: true}
    35  	}
    36  	if filepath.Ext(filename) != ".go" {
    37  		return RuleResult{OK: true}
    38  	}
    39  
    40  	fset := token.NewFileSet()
    41  	fileAst, err := parser.ParseFile(fset, filename, contents, parser.AllErrors|parser.ParseComments)
    42  	if err != nil {
    43  		return RuleResult{Err: err}
    44  	}
    45  
    46  	var result *RuleResult
    47  	ast.Inspect(fileAst, func(n ast.Node) bool {
    48  		if n == nil {
    49  			return false
    50  		}
    51  		if result != nil {
    52  			return false
    53  		}
    54  		switch nt := n.(type) {
    55  		case *ast.FuncType:
    56  			if nt.TypeParams != nil {
    57  				result = &RuleResult{
    58  					File:    filename,
    59  					Line:    fset.Position(nt.TypeParams.Pos()).Line,
    60  					Message: "Type params present on function declaration",
    61  				}
    62  				return false
    63  			}
    64  		case *ast.TypeSpec:
    65  			if nt.TypeParams != nil {
    66  				result = &RuleResult{
    67  					File:    filename,
    68  					Line:    fset.Position(nt.TypeParams.Pos()).Line,
    69  					Message: "Type params present on type declaration",
    70  				}
    71  				return false
    72  			}
    73  		case *ast.InterfaceType:
    74  			if nt.Methods != nil {
    75  				for _, i := range nt.Methods.List {
    76  					if i == nil {
    77  						continue
    78  					}
    79  					ast.Inspect(i, func(n ast.Node) bool {
    80  						if n == nil {
    81  							return false
    82  						}
    83  						if result != nil {
    84  							return false
    85  						}
    86  						switch nt := n.(type) {
    87  						case *ast.BinaryExpr:
    88  							if nt.Op == token.OR {
    89  								result = &RuleResult{
    90  									File:    filename,
    91  									Line:    fset.Position(nt.Pos()).Line,
    92  									Message: "Union present in interface",
    93  								}
    94  								return false
    95  							}
    96  						case *ast.UnaryExpr:
    97  							if nt.Op == token.TILDE {
    98  								result = &RuleResult{
    99  									File:    filename,
   100  									Line:    fset.Position(nt.Pos()).Line,
   101  									Message: "Underlying type operator present in interface",
   102  								}
   103  								return false
   104  							}
   105  						}
   106  						return true
   107  					})
   108  				}
   109  			}
   110  			return false
   111  		}
   112  		return true
   113  	})
   114  	if result != nil {
   115  		return *result
   116  	}
   117  	return RuleResult{OK: true}
   118  }
   119  
   120  // Strings implements fmt.Stringer.
   121  func (ngd NoGenericDecls) String() string {
   122  	return "generic declarations"
   123  }