diff --git a/.golangci.reference.yml b/.golangci.reference.yml index 1f6b8a26a768..7766f2909782 100644 --- a/.golangci.reference.yml +++ b/.golangci.reference.yml @@ -1361,9 +1361,20 @@ linters-settings: # Setting locale to US will correct the British spelling of 'colour' to 'color'. # Default is to use a neutral variety of English. locale: US + # Typos to ignore. + # Should be in lower case. # Default: [] ignore-words: - someword + # Extra word corrections. + # `typo` and `correction` should only contain letters. + # The words are case-insensitive. + # Default: [] + extra-words: + - typo: "iff" + correction: "if" + - typo: "cancelation" + correction: "cancellation" # Mode of the analysis: # - default: checks all the file content. # - restricted: checks only comments. diff --git a/pkg/config/linters_settings.go b/pkg/config/linters_settings.go index db121883bd3d..701beb2241b0 100644 --- a/pkg/config/linters_settings.go +++ b/pkg/config/linters_settings.go @@ -663,12 +663,18 @@ type MalignedSettings struct { } type MisspellSettings struct { - Mode string `mapstructure:"mode"` - Locale string + Mode string `mapstructure:"mode"` + Locale string `mapstructure:"locale"` + ExtraWords []MisspellExtraWords `mapstructure:"extra-words"` // TODO(ldez): v2 the option must be renamed to `IgnoredRules`. IgnoreWords []string `mapstructure:"ignore-words"` } +type MisspellExtraWords struct { + Typo string `mapstructure:"typo"` + Correction string `mapstructure:"correction"` +} + type MustTagSettings struct { Functions []struct { Name string `mapstructure:"name"` diff --git a/pkg/golinters/misspell.go b/pkg/golinters/misspell.go index 0f69cdb8701e..8a97534c58ff 100644 --- a/pkg/golinters/misspell.go +++ b/pkg/golinters/misspell.go @@ -5,6 +5,7 @@ import ( "go/token" "strings" "sync" + "unicode" "github.com/golangci/misspell" "golang.org/x/tools/go/analysis" @@ -95,6 +96,11 @@ func createMisspellReplacer(settings *config.MisspellSettings) (*misspell.Replac return nil, fmt.Errorf("unknown locale: %q", settings.Locale) } + err := appendExtraWords(replacer, settings.ExtraWords) + if err != nil { + return nil, fmt.Errorf("process extra words: %w", err) + } + if len(settings.IgnoreWords) != 0 { replacer.RemoveRule(settings.IgnoreWords) } @@ -153,3 +159,30 @@ func runMisspellOnFile(lintCtx *linter.Context, filename string, replacer *missp return res, nil } + +func appendExtraWords(replacer *misspell.Replacer, extraWords []config.MisspellExtraWords) error { + if len(extraWords) == 0 { + return nil + } + + extra := make([]string, 0, len(extraWords)*2) + + for _, word := range extraWords { + if word.Typo == "" || word.Correction == "" { + return fmt.Errorf("typo (%q) and correction (%q) fields should not be empty", word.Typo, word.Correction) + } + + if strings.ContainsFunc(word.Typo, func(r rune) bool { return !unicode.IsLetter(r) }) { + return fmt.Errorf("the word %q in the 'typo' field should only contain letters", word.Typo) + } + if strings.ContainsFunc(word.Correction, func(r rune) bool { return !unicode.IsLetter(r) }) { + return fmt.Errorf("the word %q in the 'correction' field should only contain letters", word.Correction) + } + + extra = append(extra, strings.ToLower(word.Typo), strings.ToLower(word.Correction)) + } + + replacer.AddRuleList(extra) + + return nil +} diff --git a/pkg/golinters/misspell_test.go b/pkg/golinters/misspell_test.go new file mode 100644 index 000000000000..29e0862f9b83 --- /dev/null +++ b/pkg/golinters/misspell_test.go @@ -0,0 +1,94 @@ +package golinters + +import ( + "testing" + + "github.com/golangci/misspell" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/golangci/golangci-lint/pkg/config" +) + +func Test_appendExtraWords(t *testing.T) { + extraWords := []config.MisspellExtraWords{ + { + Typo: "iff", + Correction: "if", + }, + { + Typo: "canCELation", + Correction: "canceLLaTION", + }, + } + + replacer := &misspell.Replacer{} + + err := appendExtraWords(replacer, extraWords) + require.NoError(t, err) + + expected := []string{"iff", "if", "cancelation", "cancellation"} + + assert.Equal(t, replacer.Replacements, expected) +} + +func Test_appendExtraWords_error(t *testing.T) { + testCases := []struct { + desc string + extraWords []config.MisspellExtraWords + expected string + }{ + { + desc: "empty fields", + extraWords: []config.MisspellExtraWords{{ + Typo: "", + Correction: "", + }}, + expected: `typo ("") and correction ("") fields should not be empty`, + }, + { + desc: "empty typo", + extraWords: []config.MisspellExtraWords{{ + Typo: "", + Correction: "if", + }}, + expected: `typo ("") and correction ("if") fields should not be empty`, + }, + { + desc: "empty correction", + extraWords: []config.MisspellExtraWords{{ + Typo: "iff", + Correction: "", + }}, + expected: `typo ("iff") and correction ("") fields should not be empty`, + }, + { + desc: "invalid characters in typo", + extraWords: []config.MisspellExtraWords{{ + Typo: "i'ff", + Correction: "if", + }}, + expected: `the word "i'ff" in the 'typo' field should only contain letters`, + }, + { + desc: "invalid characters in correction", + extraWords: []config.MisspellExtraWords{{ + Typo: "iff", + Correction: "i'f", + }}, + expected: `the word "i'f" in the 'correction' field should only contain letters`, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + replacer := &misspell.Replacer{} + + err := appendExtraWords(replacer, test.extraWords) + require.EqualError(t, err, test.expected) + }) + } +} diff --git a/test/testdata/configs/misspell_custom.yml b/test/testdata/configs/misspell_custom.yml new file mode 100644 index 000000000000..c0f3a27cfddb --- /dev/null +++ b/test/testdata/configs/misspell_custom.yml @@ -0,0 +1,7 @@ +linters-settings: + misspell: + extra-words: + - typo: "iff" + correction: "if" + - typo: "cancelation" + correction: "cancellation" diff --git a/test/testdata/misspell_custom.go b/test/testdata/misspell_custom.go new file mode 100644 index 000000000000..32362fb636a1 --- /dev/null +++ b/test/testdata/misspell_custom.go @@ -0,0 +1,10 @@ +//golangcitest:args -Emisspell +//golangcitest:config_path testdata/configs/misspell_custom.yml +package testdata + +func Misspell() { + // comment with incorrect spelling: occured // want "`occured` is a misspelling of `occurred`" +} + +// the word iff should be reported here // want "\\`iff\\` is a misspelling of \\`if\\`" +// the word cancelation should be reported here // want "\\`cancelation\\` is a misspelling of \\`cancellation\\`"