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 }