sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/plugins/golint/suggestion/golint_suggestion.go (about)

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package suggestion
    18  
    19  import (
    20  	"fmt"
    21  	"regexp"
    22  	"strings"
    23  
    24  	"github.com/sirupsen/logrus"
    25  	"golang.org/x/lint"
    26  	"golang.org/x/text/cases"
    27  	"golang.org/x/text/language"
    28  )
    29  
    30  var (
    31  	lintErrorfRegex          = regexp.MustCompile(`should replace errors\.New\(fmt\.Sprintf\(\.\.\.\)\) with fmt\.Errorf\(\.\.\.\)`)
    32  	lintNamesUnderscoreRegex = regexp.MustCompile("don't use underscores in Go names; (.*) should be (.*)")
    33  	lintNamesAllCapsRegex    = regexp.MustCompile("don't use ALL_CAPS in Go names; use CamelCase")
    34  	lintStutterRegex         = regexp.MustCompile(`name will be used as [^.]+\.(.*) by other packages, and that stutters; consider calling this (.*)`)
    35  	lintRangesRegex          = regexp.MustCompile(`should omit (?:2nd )?values? from range; this loop is equivalent to \x60(for .*) ...\x60`)
    36  	lintVarDeclRegex         = regexp.MustCompile("should (?:omit type|drop) (.*) from declaration of (?:.*); (?:it will be inferred from the right-hand side|it is the zero value)")
    37  )
    38  
    39  var lintHandlersMap = map[*regexp.Regexp]func(lint.Problem, []string) string{
    40  	lintErrorfRegex:          fixErrorf,
    41  	lintNamesUnderscoreRegex: fixNameUnderscore,
    42  	lintNamesAllCapsRegex:    fixNameAllCaps,
    43  	lintStutterRegex:         fixStutter,
    44  	lintRangesRegex:          fixRanges,
    45  	lintVarDeclRegex:         fixVarDecl,
    46  }
    47  
    48  // SuggestCodeChange returns code suggestions for a given lint.Problem
    49  // Returns empty string if no suggestion can be given
    50  func SuggestCodeChange(p lint.Problem) string {
    51  	var suggestion string
    52  	for regex, handler := range lintHandlersMap {
    53  		matches := regex.FindStringSubmatch(p.Text)
    54  		suggestion = handler(p, matches)
    55  		if suggestion != "" && suggestion != p.LineText {
    56  			return formatSuggestion(suggestion)
    57  		}
    58  	}
    59  	return ""
    60  }
    61  
    62  func fixNameUnderscore(p lint.Problem, matches []string) string {
    63  	if len(matches) < 3 {
    64  		return ""
    65  	}
    66  	underscoreRe := regexp.MustCompile(`[A-Za-z]+(_[A-Za-z0-9]+)+`)
    67  	namesWithUnderscore := underscoreRe.FindStringSubmatch(matches[1])
    68  	suggestion := strings.Replace(p.LineText, namesWithUnderscore[0], matches[2], -1)
    69  	return suggestion
    70  }
    71  
    72  func fixNameAllCaps(p lint.Problem, matches []string) string {
    73  	result := ""
    74  	if len(matches) == 0 {
    75  		return result
    76  	}
    77  	// Identify all caps names
    78  	reAllCaps := regexp.MustCompile(`[A-Z]+(_[A-Z0-9]+)*`)
    79  	result = reAllCaps.ReplaceAllStringFunc(p.LineText, func(oldName string) string {
    80  		return strings.Replace(cases.Title(language.English).String(strings.Replace(strings.ToLower(oldName), "_", " ", -1)), " ", "", -1)
    81  	})
    82  	return result
    83  }
    84  
    85  func fixStutter(p lint.Problem, matches []string) string {
    86  	if len(matches) < 3 {
    87  		return ""
    88  	}
    89  	suggestion := strings.Replace(p.LineText, matches[1], matches[2], -1)
    90  	return suggestion
    91  }
    92  
    93  func fixErrorf(p lint.Problem, matches []string) string {
    94  	if len(matches) != 1 {
    95  		return ""
    96  	}
    97  	parameterText := ""
    98  	count := 0
    99  	parameterTextBeginning := "errors.New(fmt.Sprintf("
   100  	parameterTextBeginningInd := strings.Index(p.LineText, parameterTextBeginning)
   101  	if parameterTextBeginningInd < 0 {
   102  		logrus.Infof("Cannot find \"errors.New(fmt.Sprintf(\" in problem line text %s", p.LineText)
   103  		return ""
   104  	}
   105  	for _, char := range p.LineText[parameterTextBeginningInd+len(parameterTextBeginning):] {
   106  		if char == '(' {
   107  			count++
   108  		}
   109  		if char == ')' {
   110  			count--
   111  			if count < 0 {
   112  				break
   113  			}
   114  		}
   115  		parameterText += string(char)
   116  	}
   117  	if count > 0 {
   118  		return ""
   119  	}
   120  	toReplace := fmt.Sprintf("errors.New(fmt.Sprintf(%s))", parameterText)
   121  	replacement := fmt.Sprintf("fmt.Errorf(%s)", parameterText)
   122  	suggestion := strings.Replace(p.LineText, toReplace, replacement, -1)
   123  	return suggestion
   124  }
   125  
   126  func fixRanges(p lint.Problem, matches []string) string {
   127  	if len(matches) != 2 {
   128  		return ""
   129  	}
   130  	reValuesToOmit := regexp.MustCompile(`for (([ [A-Za-z0-9]+[,]?]?(, _ :?= ))|(_ = )|(_, _ = ))range`)
   131  	valuesToOmit := reValuesToOmit.FindStringSubmatch(p.LineText)
   132  	if len(valuesToOmit) == 0 {
   133  		return ""
   134  	}
   135  	suggestion := strings.Replace(p.LineText, valuesToOmit[0], matches[1], -1)
   136  	return suggestion
   137  }
   138  
   139  func fixVarDecl(p lint.Problem, matches []string) string {
   140  	if len(matches) != 2 {
   141  		return ""
   142  	}
   143  	suggestion := strings.Replace(p.LineText, " "+matches[1], "", -1)
   144  	return suggestion
   145  }
   146  
   147  func formatSuggestion(s string) string {
   148  	return "```suggestion\n" + s + "```\n"
   149  }