github.com/nozzle/golangci-lint@v1.49.0-nz3/pkg/golinters/misspell.go (about)

     1  package golinters
     2  
     3  import (
     4  	"fmt"
     5  	"go/token"
     6  	"strings"
     7  	"sync"
     8  
     9  	"github.com/golangci/misspell"
    10  	"golang.org/x/tools/go/analysis"
    11  
    12  	"github.com/golangci/golangci-lint/pkg/config"
    13  	"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
    14  	"github.com/golangci/golangci-lint/pkg/lint/linter"
    15  	"github.com/golangci/golangci-lint/pkg/result"
    16  )
    17  
    18  const misspellName = "misspell"
    19  
    20  func NewMisspell(settings *config.MisspellSettings) *goanalysis.Linter {
    21  	var mu sync.Mutex
    22  	var resIssues []goanalysis.Issue
    23  
    24  	analyzer := &analysis.Analyzer{
    25  		Name: misspellName,
    26  		Doc:  goanalysis.TheOnlyanalyzerDoc,
    27  		Run:  goanalysis.DummyRun,
    28  	}
    29  
    30  	return goanalysis.NewLinter(
    31  		misspellName,
    32  		"Finds commonly misspelled English words in comments",
    33  		[]*analysis.Analyzer{analyzer},
    34  		nil,
    35  	).WithContextSetter(func(lintCtx *linter.Context) {
    36  		replacer, ruleErr := createMisspellReplacer(settings)
    37  
    38  		analyzer.Run = func(pass *analysis.Pass) (interface{}, error) {
    39  			if ruleErr != nil {
    40  				return nil, ruleErr
    41  			}
    42  
    43  			issues, err := runMisspell(lintCtx, pass, replacer)
    44  			if err != nil {
    45  				return nil, err
    46  			}
    47  
    48  			if len(issues) == 0 {
    49  				return nil, nil
    50  			}
    51  
    52  			mu.Lock()
    53  			resIssues = append(resIssues, issues...)
    54  			mu.Unlock()
    55  
    56  			return nil, nil
    57  		}
    58  	}).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue {
    59  		return resIssues
    60  	}).WithLoadMode(goanalysis.LoadModeSyntax)
    61  }
    62  
    63  func runMisspell(lintCtx *linter.Context, pass *analysis.Pass, replacer *misspell.Replacer) ([]goanalysis.Issue, error) {
    64  	fileNames := getFileNames(pass)
    65  
    66  	var issues []goanalysis.Issue
    67  	for _, filename := range fileNames {
    68  		lintIssues, err := runMisspellOnFile(lintCtx, filename, replacer)
    69  		if err != nil {
    70  			return nil, err
    71  		}
    72  		for i := range lintIssues {
    73  			issues = append(issues, goanalysis.NewIssue(&lintIssues[i], pass))
    74  		}
    75  	}
    76  
    77  	return issues, nil
    78  }
    79  
    80  func createMisspellReplacer(settings *config.MisspellSettings) (*misspell.Replacer, error) {
    81  	replacer := &misspell.Replacer{
    82  		Replacements: misspell.DictMain,
    83  	}
    84  
    85  	// Figure out regional variations
    86  	switch strings.ToUpper(settings.Locale) {
    87  	case "":
    88  		// nothing
    89  	case "US":
    90  		replacer.AddRuleList(misspell.DictAmerican)
    91  	case "UK", "GB":
    92  		replacer.AddRuleList(misspell.DictBritish)
    93  	case "NZ", "AU", "CA":
    94  		return nil, fmt.Errorf("unknown locale: %q", settings.Locale)
    95  	}
    96  
    97  	if len(settings.IgnoreWords) != 0 {
    98  		replacer.RemoveRule(settings.IgnoreWords)
    99  	}
   100  
   101  	// It can panic.
   102  	replacer.Compile()
   103  
   104  	return replacer, nil
   105  }
   106  
   107  func runMisspellOnFile(lintCtx *linter.Context, filename string, replacer *misspell.Replacer) ([]result.Issue, error) {
   108  	var res []result.Issue
   109  	fileContent, err := lintCtx.FileCache.GetFileBytes(filename)
   110  	if err != nil {
   111  		return nil, fmt.Errorf("can't get file %s contents: %s", filename, err)
   112  	}
   113  
   114  	// use r.Replace, not r.ReplaceGo because r.ReplaceGo doesn't find
   115  	// issues inside strings: it searches only inside comments. r.Replace
   116  	// searches all words: it treats input as a plain text. A standalone misspell
   117  	// tool uses r.Replace by default.
   118  	_, diffs := replacer.Replace(string(fileContent))
   119  	for _, diff := range diffs {
   120  		text := fmt.Sprintf("`%s` is a misspelling of `%s`", diff.Original, diff.Corrected)
   121  		pos := token.Position{
   122  			Filename: filename,
   123  			Line:     diff.Line,
   124  			Column:   diff.Column + 1,
   125  		}
   126  		replacement := &result.Replacement{
   127  			Inline: &result.InlineFix{
   128  				StartCol:  diff.Column,
   129  				Length:    len(diff.Original),
   130  				NewString: diff.Corrected,
   131  			},
   132  		}
   133  
   134  		res = append(res, result.Issue{
   135  			Pos:         pos,
   136  			Text:        text,
   137  			FromLinter:  misspellName,
   138  			Replacement: replacement,
   139  		})
   140  	}
   141  
   142  	return res, nil
   143  }