Merge pull request #837 from ashanbrown/asb/nolintlint-internal
Add nolintlint linter as internal linter
This commit is contained in:
commit
eeff3902d4
@ -224,6 +224,17 @@ linters-settings:
|
|||||||
simple: true
|
simple: true
|
||||||
range-loops: true # Report preallocation suggestions on range loops, true by default
|
range-loops: true # Report preallocation suggestions on range loops, true by default
|
||||||
for-loops: false # Report preallocation suggestions on for loops, false by default
|
for-loops: false # Report preallocation suggestions on for loops, false by default
|
||||||
|
nolintlint:
|
||||||
|
# Enable to ensure that nolint directives are all used. Default is true.
|
||||||
|
allow-unused: false
|
||||||
|
# Disable to ensure that nolint directives don't have a leading space. Default is true.
|
||||||
|
allow-leading-space: true
|
||||||
|
# Exclude following linters from requiring an explanation. Default is [].
|
||||||
|
allow-no-explanation: []
|
||||||
|
# Enable to require an explanation after each nolint directive. Default is false.
|
||||||
|
require-explanation: true
|
||||||
|
# Enable to require an explanation after each nolint directive. Default is false.
|
||||||
|
require-specific: true
|
||||||
rowserrcheck:
|
rowserrcheck:
|
||||||
packages:
|
packages:
|
||||||
- github.com/jmoiron/sqlx
|
- github.com/jmoiron/sqlx
|
||||||
|
@ -54,6 +54,11 @@ linters-settings:
|
|||||||
suggest-new: true
|
suggest-new: true
|
||||||
misspell:
|
misspell:
|
||||||
locale: US
|
locale: US
|
||||||
|
nolintlint:
|
||||||
|
allow-leading-space: true # don't require machine-readable nolint directives (i.e. with no leading space)
|
||||||
|
allow-unused: false # report any unused nolint directives
|
||||||
|
require-explanation: false # don't require an explanation for nolint directives
|
||||||
|
require-specific: false # don't require nolint directives to be specific about which linter is being skipped
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
# please, do not use `enable-all`: it's deprecated and will be removed soon.
|
# please, do not use `enable-all`: it's deprecated and will be removed soon.
|
||||||
@ -84,6 +89,7 @@ linters:
|
|||||||
- lll
|
- lll
|
||||||
- misspell
|
- misspell
|
||||||
- nakedret
|
- nakedret
|
||||||
|
- nolintlint
|
||||||
- rowserrcheck
|
- rowserrcheck
|
||||||
- scopelint
|
- scopelint
|
||||||
- staticcheck
|
- staticcheck
|
||||||
|
20
README.md
20
README.md
@ -232,6 +232,7 @@ maligned: Tool to detect Go structs that would take less memory if their fields
|
|||||||
misspell: Finds commonly misspelled English words in comments [fast: true, auto-fix: true]
|
misspell: Finds commonly misspelled English words in comments [fast: true, auto-fix: true]
|
||||||
nakedret: Finds naked returns in functions greater than a specified function length [fast: true, auto-fix: false]
|
nakedret: Finds naked returns in functions greater than a specified function length [fast: true, auto-fix: false]
|
||||||
nestif: Reports deeply nested if statements [fast: true, auto-fix: false]
|
nestif: Reports deeply nested if statements [fast: true, auto-fix: false]
|
||||||
|
nolintlint: Reports ill-formed or insufficient nolint directives [fast: true, auto-fix: false]
|
||||||
prealloc: Finds slice declarations that could potentially be preallocated [fast: true, auto-fix: false]
|
prealloc: Finds slice declarations that could potentially be preallocated [fast: true, auto-fix: false]
|
||||||
rowserrcheck: checks whether Err of rows is checked successfully [fast: true, auto-fix: false]
|
rowserrcheck: checks whether Err of rows is checked successfully [fast: true, auto-fix: false]
|
||||||
scopelint: Scopelint checks for unpinned variables in go programs [fast: true, auto-fix: false]
|
scopelint: Scopelint checks for unpinned variables in go programs [fast: true, auto-fix: false]
|
||||||
@ -498,6 +499,7 @@ golangci-lint help linters
|
|||||||
- [godot](https://github.com/tetafro/godot) - Check if comments end in a period
|
- [godot](https://github.com/tetafro/godot) - Check if comments end in a period
|
||||||
- [testpackage](https://github.com/maratori/testpackage) - linter that makes you use a separate _test package
|
- [testpackage](https://github.com/maratori/testpackage) - linter that makes you use a separate _test package
|
||||||
- [nestif](https://github.com/nakabonne/nestif) - Reports deeply nested if statements
|
- [nestif](https://github.com/nakabonne/nestif) - Reports deeply nested if statements
|
||||||
|
- [nolintlint](https://github.com/golangci-lint/pkg/golinters/nolintlint) - Reports ill-formed or insufficient nolint directives
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
@ -845,6 +847,17 @@ linters-settings:
|
|||||||
simple: true
|
simple: true
|
||||||
range-loops: true # Report preallocation suggestions on range loops, true by default
|
range-loops: true # Report preallocation suggestions on range loops, true by default
|
||||||
for-loops: false # Report preallocation suggestions on for loops, false by default
|
for-loops: false # Report preallocation suggestions on for loops, false by default
|
||||||
|
nolintlint:
|
||||||
|
# Enable to ensure that nolint directives are all used. Default is true.
|
||||||
|
allow-unused: false
|
||||||
|
# Disable to ensure that nolint directives don't have a leading space. Default is true.
|
||||||
|
allow-leading-space: true
|
||||||
|
# Exclude following linters from requiring an explanation. Default is [].
|
||||||
|
allow-no-explanation: []
|
||||||
|
# Enable to require an explanation after each nolint directive. Default is false.
|
||||||
|
require-explanation: true
|
||||||
|
# Enable to require an explanation after each nolint directive. Default is false.
|
||||||
|
require-specific: true
|
||||||
rowserrcheck:
|
rowserrcheck:
|
||||||
packages:
|
packages:
|
||||||
- github.com/jmoiron/sqlx
|
- github.com/jmoiron/sqlx
|
||||||
@ -1034,6 +1047,11 @@ linters-settings:
|
|||||||
suggest-new: true
|
suggest-new: true
|
||||||
misspell:
|
misspell:
|
||||||
locale: US
|
locale: US
|
||||||
|
nolintlint:
|
||||||
|
allow-leading-space: true # don't require machine-readable nolint directives (i.e. with no leading space)
|
||||||
|
allow-unused: false # report any unused nolint directives
|
||||||
|
require-explanation: false # don't require an explanation for nolint directives
|
||||||
|
require-specific: false # don't require nolint directives to be specific about which linter is being skipped
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
# please, do not use `enable-all`: it's deprecated and will be removed soon.
|
# please, do not use `enable-all`: it's deprecated and will be removed soon.
|
||||||
@ -1064,6 +1082,7 @@ linters:
|
|||||||
- lll
|
- lll
|
||||||
- misspell
|
- misspell
|
||||||
- nakedret
|
- nakedret
|
||||||
|
- nolintlint
|
||||||
- rowserrcheck
|
- rowserrcheck
|
||||||
- scopelint
|
- scopelint
|
||||||
- staticcheck
|
- staticcheck
|
||||||
@ -1301,6 +1320,7 @@ Thanks to developers and authors of used linters:
|
|||||||
- [tetafro](https://github.com/tetafro)
|
- [tetafro](https://github.com/tetafro)
|
||||||
- [maratori](https://github.com/maratori)
|
- [maratori](https://github.com/maratori)
|
||||||
- [nakabonne](https://github.com/nakabonne)
|
- [nakabonne](https://github.com/nakabonne)
|
||||||
|
- [golangci-lint](https://github.com/golangci-lint)
|
||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
@ -235,6 +235,7 @@ type LintersSettings struct {
|
|||||||
Godot GodotSettings
|
Godot GodotSettings
|
||||||
Testpackage TestpackageSettings
|
Testpackage TestpackageSettings
|
||||||
Nestif NestifSettings
|
Nestif NestifSettings
|
||||||
|
NoLintLint NoLintLintSettings
|
||||||
|
|
||||||
Custom map[string]CustomLinterSettings
|
Custom map[string]CustomLinterSettings
|
||||||
}
|
}
|
||||||
@ -316,6 +317,14 @@ type GodotSettings struct {
|
|||||||
CheckAll bool `mapstructure:"check-all"`
|
CheckAll bool `mapstructure:"check-all"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NoLintLintSettings struct {
|
||||||
|
RequireExplanation bool `mapstructure:"require-explanation"`
|
||||||
|
AllowLeadingSpace bool `mapstructure:"allow-leading-space"`
|
||||||
|
RequireSpecific bool `mapstructure:"require-specific"`
|
||||||
|
AllowNoExplanation []string `mapstructure:"allow-no-explanation"`
|
||||||
|
AllowUnused bool `mapstructure:"allow-unused"`
|
||||||
|
}
|
||||||
|
|
||||||
type TestpackageSettings struct {
|
type TestpackageSettings struct {
|
||||||
SkipRegexp string `mapstructure:"skip-regexp"`
|
SkipRegexp string `mapstructure:"skip-regexp"`
|
||||||
}
|
}
|
||||||
@ -324,7 +333,6 @@ type NestifSettings struct {
|
|||||||
MinComplexity int `mapstructure:"min-complexity"`
|
MinComplexity int `mapstructure:"min-complexity"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gomnd
|
|
||||||
var defaultLintersSettings = LintersSettings{
|
var defaultLintersSettings = LintersSettings{
|
||||||
Lll: LllSettings{
|
Lll: LllSettings{
|
||||||
LineLength: 120,
|
LineLength: 120,
|
||||||
@ -363,6 +371,12 @@ var defaultLintersSettings = LintersSettings{
|
|||||||
ForceCuddleErrCheckAndAssign: false,
|
ForceCuddleErrCheckAndAssign: false,
|
||||||
ForceCaseTrailingWhitespaceLimit: 0,
|
ForceCaseTrailingWhitespaceLimit: 0,
|
||||||
},
|
},
|
||||||
|
NoLintLint: NoLintLintSettings{
|
||||||
|
RequireExplanation: false,
|
||||||
|
AllowLeadingSpace: true,
|
||||||
|
RequireSpecific: false,
|
||||||
|
AllowUnused: false,
|
||||||
|
},
|
||||||
Testpackage: TestpackageSettings{
|
Testpackage: TestpackageSettings{
|
||||||
SkipRegexp: `(export|internal)_test\.go`,
|
SkipRegexp: `(export|internal)_test\.go`,
|
||||||
},
|
},
|
||||||
|
@ -28,7 +28,7 @@ func NewDeadcode() *goanalysis.Linter {
|
|||||||
}
|
}
|
||||||
res := make([]goanalysis.Issue, 0, len(issues))
|
res := make([]goanalysis.Issue, 0, len(issues))
|
||||||
for _, i := range issues {
|
for _, i := range issues {
|
||||||
res = append(res, goanalysis.NewIssue(&result.Issue{ //nolint:scopelint
|
res = append(res, goanalysis.NewIssue(&result.Issue{
|
||||||
Pos: i.Pos,
|
Pos: i.Pos,
|
||||||
Text: fmt.Sprintf("%s is unused", formatCode(i.UnusedIdentName, nil)),
|
Text: fmt.Sprintf("%s is unused", formatCode(i.UnusedIdentName, nil)),
|
||||||
FromLinter: linterName,
|
FromLinter: linterName,
|
||||||
|
@ -94,7 +94,7 @@ func NewDepguard() *goanalysis.Linter {
|
|||||||
if userSuppliedMsgSuffix != "" {
|
if userSuppliedMsgSuffix != "" {
|
||||||
userSuppliedMsgSuffix = ": " + userSuppliedMsgSuffix
|
userSuppliedMsgSuffix = ": " + userSuppliedMsgSuffix
|
||||||
}
|
}
|
||||||
res = append(res, goanalysis.NewIssue(&result.Issue{ //nolint:scopelint
|
res = append(res, goanalysis.NewIssue(&result.Issue{
|
||||||
Pos: i.Position,
|
Pos: i.Position,
|
||||||
Text: fmt.Sprintf("%s %s%s", formatCode(i.PackageName, lintCtx.Cfg), msgSuffix, userSuppliedMsgSuffix),
|
Text: fmt.Sprintf("%s %s%s", formatCode(i.PackageName, lintCtx.Cfg), msgSuffix, userSuppliedMsgSuffix),
|
||||||
FromLinter: linterName,
|
FromLinter: linterName,
|
||||||
|
@ -57,7 +57,7 @@ func NewDupl() *goanalysis.Linter {
|
|||||||
text := fmt.Sprintf("%d-%d lines are duplicate of %s",
|
text := fmt.Sprintf("%d-%d lines are duplicate of %s",
|
||||||
i.From.LineStart(), i.From.LineEnd(),
|
i.From.LineStart(), i.From.LineEnd(),
|
||||||
formatCode(dupl, lintCtx.Cfg))
|
formatCode(dupl, lintCtx.Cfg))
|
||||||
res = append(res, goanalysis.NewIssue(&result.Issue{ //nolint:scopelint
|
res = append(res, goanalysis.NewIssue(&result.Issue{
|
||||||
Pos: token.Position{
|
Pos: token.Position{
|
||||||
Filename: i.From.Filename(),
|
Filename: i.From.Filename(),
|
||||||
Line: i.From.LineStart(),
|
Line: i.From.LineStart(),
|
||||||
|
@ -59,7 +59,7 @@ func NewErrcheck() *goanalysis.Linter {
|
|||||||
} else {
|
} else {
|
||||||
text = "Error return value is not checked"
|
text = "Error return value is not checked"
|
||||||
}
|
}
|
||||||
issues = append(issues, goanalysis.NewIssue(&result.Issue{ //nolint:scopelint
|
issues = append(issues, goanalysis.NewIssue(&result.Issue{
|
||||||
FromLinter: linterName,
|
FromLinter: linterName,
|
||||||
Text: text,
|
Text: text,
|
||||||
Pos: i.Pos,
|
Pos: i.Pos,
|
||||||
|
@ -42,7 +42,7 @@ func NewFunlen() *goanalysis.Linter {
|
|||||||
|
|
||||||
res := make([]goanalysis.Issue, len(issues))
|
res := make([]goanalysis.Issue, len(issues))
|
||||||
for k, i := range issues {
|
for k, i := range issues {
|
||||||
res[k] = goanalysis.NewIssue(&result.Issue{ //nolint:scopelint
|
res[k] = goanalysis.NewIssue(&result.Issue{
|
||||||
Pos: token.Position{
|
Pos: token.Position{
|
||||||
Filename: i.Pos.Filename,
|
Filename: i.Pos.Filename,
|
||||||
Line: i.Pos.Line,
|
Line: i.Pos.Line,
|
||||||
|
@ -26,4 +26,6 @@ type EncodingIssue struct {
|
|||||||
Pos token.Position
|
Pos token.Position
|
||||||
LineRange *result.Range
|
LineRange *result.Range
|
||||||
Replacement *result.Replacement
|
Replacement *result.Replacement
|
||||||
|
ExpectNoLint bool
|
||||||
|
ExpectedNoLintLinter string
|
||||||
}
|
}
|
||||||
|
@ -328,6 +328,8 @@ func saveIssuesToCache(allPkgs []*packages.Package, pkgsFromCache map[*packages.
|
|||||||
Pos: i.Pos,
|
Pos: i.Pos,
|
||||||
LineRange: i.LineRange,
|
LineRange: i.LineRange,
|
||||||
Replacement: i.Replacement,
|
Replacement: i.Replacement,
|
||||||
|
ExpectNoLint: i.ExpectNoLint,
|
||||||
|
ExpectedNoLintLinter: i.ExpectedNoLintLinter,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -398,6 +400,8 @@ func loadIssuesFromCache(pkgs []*packages.Package, lintCtx *linter.Context,
|
|||||||
LineRange: i.LineRange,
|
LineRange: i.LineRange,
|
||||||
Replacement: i.Replacement,
|
Replacement: i.Replacement,
|
||||||
Pkg: pkg,
|
Pkg: pkg,
|
||||||
|
ExpectNoLint: i.ExpectNoLint,
|
||||||
|
ExpectedNoLintLinter: i.ExpectedNoLintLinter,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
cacheRes.issues = issues
|
cacheRes.issues = issues
|
||||||
|
@ -312,7 +312,7 @@ func (r *runner) analyze(pkgs []*packages.Package, analyzers []*analysis.Analyze
|
|||||||
debugf("There are %d initial and %d total packages", len(initialPkgs), len(loadingPackages))
|
debugf("There are %d initial and %d total packages", len(initialPkgs), len(loadingPackages))
|
||||||
for _, lp := range loadingPackages {
|
for _, lp := range loadingPackages {
|
||||||
if lp.isInitial {
|
if lp.isInitial {
|
||||||
wg.Add(1) //nolint:gomnd
|
wg.Add(1)
|
||||||
go func(lp *loadingPackage) {
|
go func(lp *loadingPackage) {
|
||||||
lp.analyzeRecursive(r.loadMode, loadSem)
|
lp.analyzeRecursive(r.loadMode, loadSem)
|
||||||
wg.Done()
|
wg.Done()
|
||||||
|
@ -49,7 +49,7 @@ func NewGocognit() *goanalysis.Linter {
|
|||||||
break // Break as the stats is already sorted from greatest to least
|
break // Break as the stats is already sorted from greatest to least
|
||||||
}
|
}
|
||||||
|
|
||||||
res = append(res, goanalysis.NewIssue(&result.Issue{ //nolint:scopelint
|
res = append(res, goanalysis.NewIssue(&result.Issue{
|
||||||
Pos: s.Pos,
|
Pos: s.Pos,
|
||||||
Text: fmt.Sprintf("cognitive complexity %d of func %s is high (> %d)",
|
Text: fmt.Sprintf("cognitive complexity %d of func %s is high (> %d)",
|
||||||
s.Complexity, formatCode(s.FuncName, lintCtx.Cfg), lintCtx.Settings().Gocognit.MinComplexity),
|
s.Complexity, formatCode(s.FuncName, lintCtx.Cfg), lintCtx.Settings().Gocognit.MinComplexity),
|
||||||
|
@ -70,7 +70,7 @@ func checkConstants(pass *analysis.Pass, lintCtx *linter.Context) ([]goanalysis.
|
|||||||
} else {
|
} else {
|
||||||
textEnd = fmt.Sprintf(", but such constant %s already exists", formatCode(i.MatchingConst, lintCtx.Cfg))
|
textEnd = fmt.Sprintf(", but such constant %s already exists", formatCode(i.MatchingConst, lintCtx.Cfg))
|
||||||
}
|
}
|
||||||
res = append(res, goanalysis.NewIssue(&result.Issue{ //nolint:scopelint
|
res = append(res, goanalysis.NewIssue(&result.Issue{
|
||||||
Pos: i.Pos,
|
Pos: i.Pos,
|
||||||
Text: textBegin + textEnd,
|
Text: textBegin + textEnd,
|
||||||
FromLinter: goconstName,
|
FromLinter: goconstName,
|
||||||
|
@ -49,7 +49,7 @@ func NewGocyclo() *goanalysis.Linter {
|
|||||||
break // Break as the stats is already sorted from greatest to least
|
break // Break as the stats is already sorted from greatest to least
|
||||||
}
|
}
|
||||||
|
|
||||||
res = append(res, goanalysis.NewIssue(&result.Issue{ //nolint:scopelint
|
res = append(res, goanalysis.NewIssue(&result.Issue{
|
||||||
Pos: s.Pos,
|
Pos: s.Pos,
|
||||||
Text: fmt.Sprintf("cyclomatic complexity %d of func %s is high (> %d)",
|
Text: fmt.Sprintf("cyclomatic complexity %d of func %s is high (> %d)",
|
||||||
s.Complexity, formatCode(s.FuncName, lintCtx.Cfg), lintCtx.Settings().Gocyclo.MinComplexity),
|
s.Complexity, formatCode(s.FuncName, lintCtx.Cfg), lintCtx.Settings().Gocyclo.MinComplexity),
|
||||||
|
@ -41,7 +41,7 @@ func NewGodox() *goanalysis.Linter {
|
|||||||
|
|
||||||
res := make([]goanalysis.Issue, len(issues))
|
res := make([]goanalysis.Issue, len(issues))
|
||||||
for k, i := range issues {
|
for k, i := range issues {
|
||||||
res[k] = goanalysis.NewIssue(&result.Issue{ //nolint:scopelint
|
res[k] = goanalysis.NewIssue(&result.Issue{
|
||||||
Pos: token.Position{
|
Pos: token.Position{
|
||||||
Filename: i.Pos.Filename,
|
Filename: i.Pos.Filename,
|
||||||
Line: i.Pos.Line,
|
Line: i.Pos.Line,
|
||||||
|
@ -59,7 +59,6 @@ func (p *hunkChangesParser) parseDiffLines(h *diffpkg.Hunk) {
|
|||||||
|
|
||||||
lineStr := string(line)
|
lineStr := string(line)
|
||||||
|
|
||||||
//nolint:gocritic
|
|
||||||
if strings.HasPrefix(lineStr, "-") {
|
if strings.HasPrefix(lineStr, "-") {
|
||||||
dl.typ = diffLineDeleted
|
dl.typ = diffLineDeleted
|
||||||
dl.data = strings.TrimPrefix(lineStr, "-")
|
dl.data = strings.TrimPrefix(lineStr, "-")
|
||||||
|
@ -73,7 +73,7 @@ func NewGomodguard() *goanalysis.Linter {
|
|||||||
defer mu.Unlock()
|
defer mu.Unlock()
|
||||||
|
|
||||||
for _, err := range gomodguardErrors {
|
for _, err := range gomodguardErrors {
|
||||||
issues = append(issues, goanalysis.NewIssue(&result.Issue{ //nolint:scopelint
|
issues = append(issues, goanalysis.NewIssue(&result.Issue{
|
||||||
FromLinter: gomodguardName,
|
FromLinter: gomodguardName,
|
||||||
Pos: err.Position,
|
Pos: err.Position,
|
||||||
Text: err.Reason,
|
Text: err.Reason,
|
||||||
|
@ -73,7 +73,7 @@ func NewGosec() *goanalysis.Linter {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
res = append(res, goanalysis.NewIssue(&result.Issue{ //nolint:scopelint
|
res = append(res, goanalysis.NewIssue(&result.Issue{
|
||||||
Pos: token.Position{
|
Pos: token.Position{
|
||||||
Filename: i.File,
|
Filename: i.File,
|
||||||
Line: line,
|
Line: line,
|
||||||
|
@ -42,7 +42,7 @@ func NewIneffassign() *goanalysis.Linter {
|
|||||||
|
|
||||||
res := make([]goanalysis.Issue, 0, len(issues))
|
res := make([]goanalysis.Issue, 0, len(issues))
|
||||||
for _, i := range issues {
|
for _, i := range issues {
|
||||||
res = append(res, goanalysis.NewIssue(&result.Issue{ //nolint:scopelint
|
res = append(res, goanalysis.NewIssue(&result.Issue{
|
||||||
Pos: i.Pos,
|
Pos: i.Pos,
|
||||||
Text: fmt.Sprintf("ineffectual assignment to %s", formatCode(i.IdentName, lintCtx.Cfg)),
|
Text: fmt.Sprintf("ineffectual assignment to %s", formatCode(i.IdentName, lintCtx.Cfg)),
|
||||||
FromLinter: ineffassignName,
|
FromLinter: ineffassignName,
|
||||||
|
@ -48,7 +48,7 @@ func NewInterfacer() *goanalysis.Linter {
|
|||||||
res := make([]goanalysis.Issue, 0, len(issues))
|
res := make([]goanalysis.Issue, 0, len(issues))
|
||||||
for _, i := range issues {
|
for _, i := range issues {
|
||||||
pos := pass.Fset.Position(i.Pos())
|
pos := pass.Fset.Position(i.Pos())
|
||||||
res = append(res, goanalysis.NewIssue(&result.Issue{ //nolint:scopelint
|
res = append(res, goanalysis.NewIssue(&result.Issue{
|
||||||
Pos: pos,
|
Pos: pos,
|
||||||
Text: i.Message(),
|
Text: i.Message(),
|
||||||
FromLinter: interfacerName,
|
FromLinter: interfacerName,
|
||||||
|
@ -40,7 +40,7 @@ func NewMaligned() *goanalysis.Linter {
|
|||||||
if lintCtx.Settings().Maligned.SuggestNewOrder {
|
if lintCtx.Settings().Maligned.SuggestNewOrder {
|
||||||
text += fmt.Sprintf(":\n%s", formatCodeBlock(i.NewStructDef, lintCtx.Cfg))
|
text += fmt.Sprintf(":\n%s", formatCodeBlock(i.NewStructDef, lintCtx.Cfg))
|
||||||
}
|
}
|
||||||
issues = append(issues, goanalysis.NewIssue(&result.Issue{ //nolint:scopelint
|
issues = append(issues, goanalysis.NewIssue(&result.Issue{
|
||||||
Pos: i.Pos,
|
Pos: i.Pos,
|
||||||
Text: text,
|
Text: text,
|
||||||
FromLinter: linterName,
|
FromLinter: linterName,
|
||||||
|
@ -46,7 +46,7 @@ func NewNestif() *goanalysis.Linter {
|
|||||||
|
|
||||||
res := make([]goanalysis.Issue, 0, len(issues))
|
res := make([]goanalysis.Issue, 0, len(issues))
|
||||||
for _, i := range issues {
|
for _, i := range issues {
|
||||||
res = append(res, goanalysis.NewIssue(&result.Issue{ //nolint:scopelint
|
res = append(res, goanalysis.NewIssue(&result.Issue{
|
||||||
Pos: i.Pos,
|
Pos: i.Pos,
|
||||||
Text: i.Message,
|
Text: i.Message,
|
||||||
FromLinter: nestifName,
|
FromLinter: nestifName,
|
||||||
|
92
pkg/golinters/nolintlint.go
Normal file
92
pkg/golinters/nolintlint.go
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
package golinters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/analysis"
|
||||||
|
|
||||||
|
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||||
|
"github.com/golangci/golangci-lint/pkg/golinters/nolintlint"
|
||||||
|
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||||
|
"github.com/golangci/golangci-lint/pkg/result"
|
||||||
|
)
|
||||||
|
|
||||||
|
const NolintlintName = "nolintlint"
|
||||||
|
|
||||||
|
func NewNoLintLint() *goanalysis.Linter {
|
||||||
|
var mu sync.Mutex
|
||||||
|
var resIssues []goanalysis.Issue
|
||||||
|
|
||||||
|
analyzer := &analysis.Analyzer{
|
||||||
|
Name: NolintlintName,
|
||||||
|
Doc: goanalysis.TheOnlyanalyzerDoc,
|
||||||
|
}
|
||||||
|
return goanalysis.NewLinter(
|
||||||
|
NolintlintName,
|
||||||
|
"Reports ill-formed or insufficient nolint directives",
|
||||||
|
[]*analysis.Analyzer{analyzer},
|
||||||
|
nil,
|
||||||
|
).WithContextSetter(func(lintCtx *linter.Context) {
|
||||||
|
analyzer.Run = func(pass *analysis.Pass) (interface{}, error) {
|
||||||
|
var needs nolintlint.Needs
|
||||||
|
settings := lintCtx.Settings().NoLintLint
|
||||||
|
if settings.RequireExplanation {
|
||||||
|
needs |= nolintlint.NeedsExplanation
|
||||||
|
}
|
||||||
|
if !settings.AllowLeadingSpace {
|
||||||
|
needs |= nolintlint.NeedsMachineOnly
|
||||||
|
}
|
||||||
|
if settings.RequireSpecific {
|
||||||
|
needs |= nolintlint.NeedsSpecific
|
||||||
|
}
|
||||||
|
if !settings.AllowUnused {
|
||||||
|
needs |= nolintlint.NeedsUnused
|
||||||
|
}
|
||||||
|
|
||||||
|
lnt, err := nolintlint.NewLinter(needs, settings.AllowNoExplanation)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes := make([]ast.Node, 0, len(pass.Files))
|
||||||
|
for _, n := range pass.Files {
|
||||||
|
nodes = append(nodes, n)
|
||||||
|
}
|
||||||
|
issues, err := lnt.Run(pass.Fset, nodes...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("linter failed to run: %s", err)
|
||||||
|
}
|
||||||
|
var res []goanalysis.Issue
|
||||||
|
for _, i := range issues {
|
||||||
|
expectNoLint := false
|
||||||
|
var expectedNolintLinter string
|
||||||
|
if ii, ok := i.(nolintlint.UnusedCandidate); ok {
|
||||||
|
expectedNolintLinter = ii.ExpectedLinter
|
||||||
|
expectNoLint = true
|
||||||
|
}
|
||||||
|
issue := &result.Issue{
|
||||||
|
FromLinter: NolintlintName,
|
||||||
|
Text: i.Details(),
|
||||||
|
Pos: i.Position(),
|
||||||
|
ExpectNoLint: expectNoLint,
|
||||||
|
ExpectedNoLintLinter: expectedNolintLinter,
|
||||||
|
}
|
||||||
|
res = append(res, goanalysis.NewIssue(issue, pass))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(res) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mu.Lock()
|
||||||
|
resIssues = append(resIssues, res...)
|
||||||
|
mu.Unlock()
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue {
|
||||||
|
return resIssues
|
||||||
|
}).WithLoadMode(goanalysis.LoadModeSyntax)
|
||||||
|
}
|
31
pkg/golinters/nolintlint/README.md
Normal file
31
pkg/golinters/nolintlint/README.md
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# nolintlint
|
||||||
|
|
||||||
|
nolintlint is a Go static analysis tool to find ill-formed or insufficiently explained `// nolint` directives for golangci
|
||||||
|
(or any other linter, using th )
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
To ensure that lint exceptions have explanations. Consider the case below:
|
||||||
|
|
||||||
|
```Go
|
||||||
|
import "crypto/md5" //nolint
|
||||||
|
|
||||||
|
func hash(data []byte) []byte {
|
||||||
|
return md5.New().Sum(data) //nolint
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In the above case, nolint directives are present but the user has no idea why this is being done or which linter
|
||||||
|
is being suppressed (in this case, gosec recommends against use of md5). `nolintlint` can require that the code provide an explanation, which might look as follows:
|
||||||
|
|
||||||
|
```Go
|
||||||
|
import "crypto/md5" //nolint:gosec // this is not used in a secure application
|
||||||
|
|
||||||
|
func hash(data []byte) []byte {
|
||||||
|
return md5.New().Sum(data) //nolint:gosec // this result is not used in a secure application
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`nolintlint` can also identify cases where you may have written `// nolint`. Finally `nolintlint`, can also enforce that you
|
||||||
|
use the machine-readable nolint directive format `//nolint` and that you mention what linter is being suppressed, as shown above when we write `//nolint:gosec`.
|
||||||
|
|
239
pkg/golinters/nolintlint/nolintlint.go
Normal file
239
pkg/golinters/nolintlint/nolintlint.go
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
// nolintlint provides a linter to ensure that all //nolint directives are followed by explanations
|
||||||
|
package nolintlint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BaseIssue struct {
|
||||||
|
fullDirective string
|
||||||
|
directiveWithOptionalLeadingSpace string
|
||||||
|
position token.Position
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b BaseIssue) Position() token.Position {
|
||||||
|
return b.position
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExtraLeadingSpace struct {
|
||||||
|
BaseIssue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i ExtraLeadingSpace) Details() string {
|
||||||
|
return fmt.Sprintf("directive `%s` should not have more than one leading space", i.fullDirective)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i ExtraLeadingSpace) String() string { return toString(i) }
|
||||||
|
|
||||||
|
type NotMachine struct {
|
||||||
|
BaseIssue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i NotMachine) Details() string {
|
||||||
|
expected := i.fullDirective[:2] + strings.TrimLeftFunc(i.fullDirective[2:], unicode.IsSpace)
|
||||||
|
return fmt.Sprintf("directive `%s` should be written without leading space as `%s`",
|
||||||
|
i.fullDirective, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i NotMachine) String() string { return toString(i) }
|
||||||
|
|
||||||
|
type NotSpecific struct {
|
||||||
|
BaseIssue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i NotSpecific) Details() string {
|
||||||
|
return fmt.Sprintf("directive `%s` should mention specific linter such as `//%s:my-linter`",
|
||||||
|
i.fullDirective, i.directiveWithOptionalLeadingSpace)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i NotSpecific) String() string { return toString(i) }
|
||||||
|
|
||||||
|
type ParseError struct {
|
||||||
|
BaseIssue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i ParseError) Details() string {
|
||||||
|
return fmt.Sprintf("directive `%s` should match `//%s[:<comma-separated-linters>] [// <explanation>]`",
|
||||||
|
i.fullDirective,
|
||||||
|
i.directiveWithOptionalLeadingSpace)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i ParseError) String() string { return toString(i) }
|
||||||
|
|
||||||
|
type NoExplanation struct {
|
||||||
|
BaseIssue
|
||||||
|
fullDirectiveWithoutExplanation string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i NoExplanation) Details() string {
|
||||||
|
return fmt.Sprintf("directive `%s` should provide explanation such as `%s // this is why`",
|
||||||
|
i.fullDirective, i.fullDirectiveWithoutExplanation)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i NoExplanation) String() string { return toString(i) }
|
||||||
|
|
||||||
|
type UnusedCandidate struct {
|
||||||
|
BaseIssue
|
||||||
|
ExpectedLinter string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i UnusedCandidate) Details() string {
|
||||||
|
details := fmt.Sprintf("directive `%s` is unused", i.fullDirective)
|
||||||
|
if i.ExpectedLinter != "" {
|
||||||
|
details += fmt.Sprintf(" for linter %s", i.ExpectedLinter)
|
||||||
|
}
|
||||||
|
return details
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i UnusedCandidate) String() string { return toString(i) }
|
||||||
|
|
||||||
|
func toString(i Issue) string {
|
||||||
|
return fmt.Sprintf("%s at %s", i.Details(), i.Position())
|
||||||
|
}
|
||||||
|
|
||||||
|
type Issue interface {
|
||||||
|
Details() string
|
||||||
|
Position() token.Position
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Needs uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
NeedsMachineOnly Needs = 1 << iota
|
||||||
|
NeedsSpecific
|
||||||
|
NeedsExplanation
|
||||||
|
NeedsUnused
|
||||||
|
NeedsAll = NeedsMachineOnly | NeedsSpecific | NeedsExplanation
|
||||||
|
)
|
||||||
|
|
||||||
|
// matches lines starting with the nolint directive
|
||||||
|
var directiveOnlyPattern = regexp.MustCompile(`^\s*(nolint)(:\s*[\w-]+\s*(?:,\s*[\w-]+\s*)*)?\b`)
|
||||||
|
|
||||||
|
// matches a complete nolint directive
|
||||||
|
var fullDirectivePattern = regexp.MustCompile(`^//\s*nolint(:\s*[\w-]+\s*(?:,\s*[\w-]+\s*)*)?\s*(//.*)?\s*\n?$`)
|
||||||
|
|
||||||
|
type Linter struct {
|
||||||
|
excludes []string // lists individual linters that don't require explanations
|
||||||
|
needs Needs // indicates which linter checks to perform
|
||||||
|
excludeByLinter map[string]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLinter creates a linter that enforces that the provided directives fulfill the provided requirements
|
||||||
|
func NewLinter(needs Needs, excludes []string) (*Linter, error) {
|
||||||
|
excludeByName := make(map[string]bool)
|
||||||
|
for _, e := range excludes {
|
||||||
|
excludeByName[e] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Linter{
|
||||||
|
needs: needs,
|
||||||
|
excludeByLinter: excludeByName,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var leadingSpacePattern = regexp.MustCompile(`^//(\s*)`)
|
||||||
|
var trailingBlankExplanation = regexp.MustCompile(`\s*(//\s*)?$`)
|
||||||
|
|
||||||
|
func (l Linter) Run(fset *token.FileSet, nodes ...ast.Node) ([]Issue, error) {
|
||||||
|
var issues []Issue
|
||||||
|
for _, node := range nodes {
|
||||||
|
if file, ok := node.(*ast.File); ok {
|
||||||
|
for _, c := range file.Comments {
|
||||||
|
text := c.Text()
|
||||||
|
matches := directiveOnlyPattern.FindStringSubmatch(text)
|
||||||
|
if len(matches) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
directive := matches[1]
|
||||||
|
|
||||||
|
// check for a space between the "//" and the directive
|
||||||
|
leadingSpaceMatches := leadingSpacePattern.FindStringSubmatch(c.List[0].Text) // c.Text() doesn't have all leading space
|
||||||
|
if len(leadingSpaceMatches) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
leadingSpace := leadingSpaceMatches[1]
|
||||||
|
|
||||||
|
directiveWithOptionalLeadingSpace := directive
|
||||||
|
if len(leadingSpace) > 0 {
|
||||||
|
directiveWithOptionalLeadingSpace = " " + directive
|
||||||
|
}
|
||||||
|
|
||||||
|
base := BaseIssue{
|
||||||
|
fullDirective: c.List[0].Text,
|
||||||
|
directiveWithOptionalLeadingSpace: directiveWithOptionalLeadingSpace,
|
||||||
|
position: fset.Position(c.Pos()),
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for, report and eliminate leading spaces so we can check for other issues
|
||||||
|
if leadingSpace != "" && leadingSpace != " " {
|
||||||
|
issues = append(issues, ExtraLeadingSpace{
|
||||||
|
BaseIssue: base,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (l.needs&NeedsMachineOnly) != 0 && strings.HasPrefix(directiveWithOptionalLeadingSpace, " ") {
|
||||||
|
issues = append(issues, NotMachine{BaseIssue: base})
|
||||||
|
}
|
||||||
|
|
||||||
|
fullMatches := fullDirectivePattern.FindStringSubmatch(c.List[0].Text)
|
||||||
|
if len(fullMatches) == 0 {
|
||||||
|
issues = append(issues, ParseError{BaseIssue: base})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lintersText, explanation := fullMatches[1], fullMatches[2]
|
||||||
|
var linters []string
|
||||||
|
if len(lintersText) > 0 {
|
||||||
|
lls := strings.Split(lintersText[1:], ",")
|
||||||
|
linters = make([]string, 0, len(lls))
|
||||||
|
for _, ll := range lls {
|
||||||
|
ll = strings.TrimSpace(ll)
|
||||||
|
if ll != "" {
|
||||||
|
linters = append(linters, ll)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (l.needs & NeedsSpecific) != 0 {
|
||||||
|
if len(linters) == 0 {
|
||||||
|
issues = append(issues, NotSpecific{BaseIssue: base})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// when detecting unused directives, we send all the directives through and filter them out in the nolint processor
|
||||||
|
if l.needs&NeedsUnused != 0 {
|
||||||
|
if len(linters) == 0 {
|
||||||
|
issues = append(issues, UnusedCandidate{BaseIssue: base})
|
||||||
|
} else {
|
||||||
|
for _, linter := range linters {
|
||||||
|
issues = append(issues, UnusedCandidate{BaseIssue: base, ExpectedLinter: linter})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (l.needs&NeedsExplanation) != 0 && (explanation == "" || strings.TrimSpace(explanation) == "//") {
|
||||||
|
needsExplanation := len(linters) == 0 // if no linters are mentioned, we must have explanation
|
||||||
|
// otherwise, check if we are excluding all of the mentioned linters
|
||||||
|
for _, ll := range linters {
|
||||||
|
if !l.excludeByLinter[ll] { // if a linter does require explanation
|
||||||
|
needsExplanation = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if needsExplanation {
|
||||||
|
fullDirectiveWithoutExplanation := trailingBlankExplanation.ReplaceAllString(c.List[0].Text, "")
|
||||||
|
issues = append(issues, NoExplanation{
|
||||||
|
BaseIssue: base,
|
||||||
|
fullDirectiveWithoutExplanation: fullDirectiveWithoutExplanation,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return issues, nil
|
||||||
|
}
|
123
pkg/golinters/nolintlint/nolintlint_test.go
Normal file
123
pkg/golinters/nolintlint/nolintlint_test.go
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package nolintlint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNoLintLint(t *testing.T) {
|
||||||
|
t.Run("when no explanation is provided", func(t *testing.T) {
|
||||||
|
linter, _ := NewLinter(NeedsExplanation, nil)
|
||||||
|
expectIssues(t, linter, `
|
||||||
|
package bar
|
||||||
|
|
||||||
|
func foo() {
|
||||||
|
bad() //nolint
|
||||||
|
bad() //nolint //
|
||||||
|
bad() //nolint //
|
||||||
|
good() //nolint // this is ok
|
||||||
|
other() //nolintother
|
||||||
|
}`,
|
||||||
|
"directive `//nolint` should provide explanation such as `//nolint // this is why` at testing.go:5:9",
|
||||||
|
"directive `//nolint //` should provide explanation such as `//nolint // this is why` at testing.go:6:9",
|
||||||
|
"directive `//nolint // ` should provide explanation such as `//nolint // this is why` at testing.go:7:9",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when no explanation is needed for a specific linter", func(t *testing.T) {
|
||||||
|
linter, _ := NewLinter(NeedsExplanation, []string{"lll"})
|
||||||
|
expectIssues(t, linter, `
|
||||||
|
package bar
|
||||||
|
|
||||||
|
func foo() {
|
||||||
|
thisIsAReallyLongLine() //nolint:lll
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when no specific linter is mentioned", func(t *testing.T) {
|
||||||
|
linter, _ := NewLinter(NeedsSpecific, nil)
|
||||||
|
expectIssues(t, linter, `
|
||||||
|
package bar
|
||||||
|
|
||||||
|
func foo() {
|
||||||
|
good() //nolint:my-linter
|
||||||
|
bad() //nolint
|
||||||
|
bad() // nolint // because
|
||||||
|
}`,
|
||||||
|
"directive `//nolint` should mention specific linter such as `//nolint:my-linter` at testing.go:6:9",
|
||||||
|
"directive `// nolint // because` should mention specific linter such as `// nolint:my-linter` at testing.go:7:9")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when machine-readable style isn't used", func(t *testing.T) {
|
||||||
|
linter, _ := NewLinter(NeedsMachineOnly, nil)
|
||||||
|
expectIssues(t, linter, `
|
||||||
|
package bar
|
||||||
|
|
||||||
|
func foo() {
|
||||||
|
bad() // nolint
|
||||||
|
good() //nolint
|
||||||
|
}`, "directive `// nolint` should be written without leading space as `//nolint` at testing.go:5:9")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("extra spaces in front of directive are reported", func(t *testing.T) {
|
||||||
|
linter, _ := NewLinter(0, nil)
|
||||||
|
expectIssues(t, linter, `
|
||||||
|
package bar
|
||||||
|
|
||||||
|
func foo() {
|
||||||
|
bad() // nolint
|
||||||
|
good() // nolint
|
||||||
|
}`, "directive `// nolint` should not have more than one leading space at testing.go:5:9")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("spaces are allowed in comma-separated list of linters", func(t *testing.T) {
|
||||||
|
linter, _ := NewLinter(0, nil)
|
||||||
|
expectIssues(t, linter, `
|
||||||
|
package bar
|
||||||
|
|
||||||
|
func foo() {
|
||||||
|
good() // nolint:linter1,linter-two
|
||||||
|
bad() // nolint:linter1 linter2
|
||||||
|
good() // nolint: linter1,linter2
|
||||||
|
good() // nolint: linter1, linter2
|
||||||
|
}`,
|
||||||
|
"directive `// nolint:linter1 linter2` should match `// nolint[:<comma-separated-linters>] [// <explanation>]` at testing.go:6:9", //nolint:lll // this is a string
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("multi-line comments don't confuse parser", func(t *testing.T) {
|
||||||
|
linter, _ := NewLinter(0, nil)
|
||||||
|
expectIssues(t, linter, `
|
||||||
|
package bar
|
||||||
|
|
||||||
|
func foo() {
|
||||||
|
//nolint:test
|
||||||
|
// something else
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func expectIssues(t *testing.T, linter *Linter, contents string, issues ...string) {
|
||||||
|
actualIssues := parseFile(t, linter, contents)
|
||||||
|
actualIssueStrs := make([]string, 0, len(actualIssues))
|
||||||
|
for _, i := range actualIssues {
|
||||||
|
actualIssueStrs = append(actualIssueStrs, i.String())
|
||||||
|
}
|
||||||
|
assert.ElementsMatch(t, issues, actualIssueStrs, "expected %s but got %s", issues, actualIssues)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFile(t *testing.T, linter *Linter, contents string) []Issue {
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
expr, err := parser.ParseFile(fset, "testing.go", contents, parser.ParseComments)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to parse file contents: %s", err)
|
||||||
|
}
|
||||||
|
issues, err := linter.Run(fset, expr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to parse file: %s", err)
|
||||||
|
}
|
||||||
|
return issues
|
||||||
|
}
|
@ -34,7 +34,7 @@ func NewPrealloc() *goanalysis.Linter {
|
|||||||
var res []goanalysis.Issue
|
var res []goanalysis.Issue
|
||||||
hints := prealloc.Check(pass.Files, s.Simple, s.RangeLoops, s.ForLoops)
|
hints := prealloc.Check(pass.Files, s.Simple, s.RangeLoops, s.ForLoops)
|
||||||
for _, hint := range hints {
|
for _, hint := range hints {
|
||||||
res = append(res, goanalysis.NewIssue(&result.Issue{ //nolint:scopelint
|
res = append(res, goanalysis.NewIssue(&result.Issue{
|
||||||
Pos: pass.Fset.Position(hint.Pos),
|
Pos: pass.Fset.Position(hint.Pos),
|
||||||
Text: fmt.Sprintf("Consider preallocating %s", formatCode(hint.DeclaredSliceName, lintCtx.Cfg)),
|
Text: fmt.Sprintf("Consider preallocating %s", formatCode(hint.DeclaredSliceName, lintCtx.Cfg)),
|
||||||
FromLinter: preallocName,
|
FromLinter: preallocName,
|
||||||
|
@ -37,7 +37,7 @@ func NewStructcheck() *goanalysis.Linter {
|
|||||||
|
|
||||||
issues := make([]goanalysis.Issue, 0, len(structcheckIssues))
|
issues := make([]goanalysis.Issue, 0, len(structcheckIssues))
|
||||||
for _, i := range structcheckIssues {
|
for _, i := range structcheckIssues {
|
||||||
issues = append(issues, goanalysis.NewIssue(&result.Issue{ //nolint:scopelint
|
issues = append(issues, goanalysis.NewIssue(&result.Issue{
|
||||||
Pos: i.Pos,
|
Pos: i.Pos,
|
||||||
Text: fmt.Sprintf("%s is unused", formatCode(i.FieldName, lintCtx.Cfg)),
|
Text: fmt.Sprintf("%s is unused", formatCode(i.FieldName, lintCtx.Cfg)),
|
||||||
FromLinter: linterName,
|
FromLinter: linterName,
|
||||||
|
@ -35,7 +35,7 @@ func NewUnconvert() *goanalysis.Linter {
|
|||||||
|
|
||||||
issues := make([]goanalysis.Issue, 0, len(positions))
|
issues := make([]goanalysis.Issue, 0, len(positions))
|
||||||
for _, pos := range positions {
|
for _, pos := range positions {
|
||||||
issues = append(issues, goanalysis.NewIssue(&result.Issue{ //nolint:scopelint
|
issues = append(issues, goanalysis.NewIssue(&result.Issue{
|
||||||
Pos: pos,
|
Pos: pos,
|
||||||
Text: "unnecessary conversion",
|
Text: "unnecessary conversion",
|
||||||
FromLinter: linterName,
|
FromLinter: linterName,
|
||||||
|
@ -58,7 +58,7 @@ func NewUnparam() *goanalysis.Linter {
|
|||||||
|
|
||||||
var res []goanalysis.Issue
|
var res []goanalysis.Issue
|
||||||
for _, i := range unparamIssues {
|
for _, i := range unparamIssues {
|
||||||
res = append(res, goanalysis.NewIssue(&result.Issue{ //nolint:scopelint
|
res = append(res, goanalysis.NewIssue(&result.Issue{
|
||||||
Pos: pass.Fset.Position(i.Pos()),
|
Pos: pass.Fset.Position(i.Pos()),
|
||||||
Text: i.Message(),
|
Text: i.Message(),
|
||||||
FromLinter: linterName,
|
FromLinter: linterName,
|
||||||
|
@ -37,7 +37,7 @@ func NewVarcheck() *goanalysis.Linter {
|
|||||||
|
|
||||||
issues := make([]goanalysis.Issue, 0, len(varcheckIssues))
|
issues := make([]goanalysis.Issue, 0, len(varcheckIssues))
|
||||||
for _, i := range varcheckIssues {
|
for _, i := range varcheckIssues {
|
||||||
issues = append(issues, goanalysis.NewIssue(&result.Issue{ //nolint:scopelint
|
issues = append(issues, goanalysis.NewIssue(&result.Issue{
|
||||||
Pos: i.Pos,
|
Pos: i.Pos,
|
||||||
Text: fmt.Sprintf("%s is unused", formatCode(i.VarName, lintCtx.Cfg)),
|
Text: fmt.Sprintf("%s is unused", formatCode(i.VarName, lintCtx.Cfg)),
|
||||||
FromLinter: linterName,
|
FromLinter: linterName,
|
||||||
|
@ -70,7 +70,7 @@ func NewWhitespace() *goanalysis.Linter {
|
|||||||
}
|
}
|
||||||
issue.Replacement.NewLines = []string{bracketLine}
|
issue.Replacement.NewLines = []string{bracketLine}
|
||||||
|
|
||||||
res[k] = goanalysis.NewIssue(&issue, pass) //nolint:scopelint
|
res[k] = goanalysis.NewIssue(&issue, pass)
|
||||||
}
|
}
|
||||||
|
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
|
@ -66,7 +66,7 @@ func NewWSL() *goanalysis.Linter {
|
|||||||
defer mu.Unlock()
|
defer mu.Unlock()
|
||||||
|
|
||||||
for _, err := range wslErrors {
|
for _, err := range wslErrors {
|
||||||
issues = append(issues, goanalysis.NewIssue(&result.Issue{ //nolint:scopelint
|
issues = append(issues, goanalysis.NewIssue(&result.Issue{
|
||||||
FromLinter: name,
|
FromLinter: name,
|
||||||
Pos: err.Position,
|
Pos: err.Position,
|
||||||
Text: err.Reason,
|
Text: err.Reason,
|
||||||
|
@ -113,7 +113,7 @@ func (es EnabledSet) combineGoAnalysisLinters(linters map[string]*linter.Config)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(goanalysisLinters) <= 1 { //nolint:gomnd
|
if len(goanalysisLinters) <= 1 {
|
||||||
es.debugf("Didn't combine go/analysis linters: got only %d linters", len(goanalysisLinters))
|
es.debugf("Didn't combine go/analysis linters: got only %d linters", len(goanalysisLinters))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -269,6 +269,10 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
|
|||||||
linter.NewConfig(golinters.NewNestif()).
|
linter.NewConfig(golinters.NewNestif()).
|
||||||
WithPresets(linter.PresetComplexity).
|
WithPresets(linter.PresetComplexity).
|
||||||
WithURL("https://github.com/nakabonne/nestif"),
|
WithURL("https://github.com/nakabonne/nestif"),
|
||||||
|
// nolintlint must be last because it looks at the results of all the previous linters for unused nolint directives
|
||||||
|
linter.NewConfig(golinters.NewNoLintLint()).
|
||||||
|
WithPresets(linter.PresetStyle).
|
||||||
|
WithURL("https://github.com/golangci-lint/pkg/golinters/nolintlint"),
|
||||||
}
|
}
|
||||||
|
|
||||||
isLocalRun := os.Getenv("GOLANGCI_COM_RUN") == ""
|
isLocalRun := os.Getenv("GOLANGCI_COM_RUN") == ""
|
||||||
|
@ -77,6 +77,13 @@ func NewRunner(cfg *config.Config, log logutils.Log, goenv *goutil.Env,
|
|||||||
excludeRulesProcessor = processors.NewExcludeRules(excludeRules, lineCache, log.Child("exclude_rules"))
|
excludeRulesProcessor = processors.NewExcludeRules(excludeRules, lineCache, log.Child("exclude_rules"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enabledLintersSet := lintersdb.NewEnabledSet(dbManager,
|
||||||
|
lintersdb.NewValidator(dbManager), log.Child("enabledLinters"), cfg)
|
||||||
|
lcs, err := enabledLintersSet.Get(false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &Runner{
|
return &Runner{
|
||||||
Processors: []processors.Processor{
|
Processors: []processors.Processor{
|
||||||
processors.NewCgo(goenv),
|
processors.NewCgo(goenv),
|
||||||
@ -96,7 +103,7 @@ func NewRunner(cfg *config.Config, log logutils.Log, goenv *goutil.Env,
|
|||||||
|
|
||||||
excludeProcessor,
|
excludeProcessor,
|
||||||
excludeRulesProcessor,
|
excludeRulesProcessor,
|
||||||
processors.NewNolint(log.Child("nolint"), dbManager),
|
processors.NewNolint(log.Child("nolint"), dbManager, lcs),
|
||||||
|
|
||||||
processors.NewUniqByLine(cfg),
|
processors.NewUniqByLine(cfg),
|
||||||
processors.NewDiff(icfg.Diff, icfg.DiffFromRevision, icfg.DiffPatchFilePath),
|
processors.NewDiff(icfg.Diff, icfg.DiffFromRevision, icfg.DiffPatchFilePath),
|
||||||
|
@ -41,6 +41,10 @@ type Issue struct {
|
|||||||
|
|
||||||
// HunkPos is used only when golangci-lint is run over a diff
|
// HunkPos is used only when golangci-lint is run over a diff
|
||||||
HunkPos int `json:",omitempty"`
|
HunkPos int `json:",omitempty"`
|
||||||
|
|
||||||
|
// If we are expecting a nolint (because this is from nolintlint), record the expected linter
|
||||||
|
ExpectNoLint bool
|
||||||
|
ExpectedNoLintLinter string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Issue) FilePath() string {
|
func (i *Issue) FilePath() string {
|
||||||
|
@ -132,7 +132,7 @@ func getDoc(filePath string) (string, error) {
|
|||||||
var docLines []string
|
var docLines []string
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := strings.TrimSpace(scanner.Text())
|
line := strings.TrimSpace(scanner.Text())
|
||||||
if strings.HasPrefix(line, "//") { //nolint:gocritic
|
if strings.HasPrefix(line, "//") {
|
||||||
text := strings.TrimSpace(strings.TrimPrefix(line, "//"))
|
text := strings.TrimSpace(strings.TrimPrefix(line, "//"))
|
||||||
docLines = append(docLines, text)
|
docLines = append(docLines, text)
|
||||||
} else if line == "" || strings.HasPrefix(line, "package") {
|
} else if line == "" || strings.HasPrefix(line, "package") {
|
||||||
|
@ -185,7 +185,7 @@ func (f Fixer) applyInlineFixes(lineIssues []result.Issue, origLine []byte, line
|
|||||||
|
|
||||||
func (f Fixer) findNotIntersectingIssues(issues []result.Issue) []result.Issue {
|
func (f Fixer) findNotIntersectingIssues(issues []result.Issue) []result.Issue {
|
||||||
sort.SliceStable(issues, func(i, j int) bool {
|
sort.SliceStable(issues, func(i, j int) bool {
|
||||||
a, b := issues[i], issues[j] //nolint:scopelint
|
a, b := issues[i], issues[j]
|
||||||
return a.Line() < b.Line()
|
return a.Line() < b.Line()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ func NewMaxPerFileFromLinter(cfg *config.Config) *MaxPerFileFromLinter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &MaxPerFileFromLinter{
|
return &MaxPerFileFromLinter{
|
||||||
flc: fileToLinterToCountMap{}, //nolint:goimports,gofmt
|
flc: fileToLinterToCountMap{},
|
||||||
maxPerFileFromLinterConfig: maxPerFileFromLinterConfig,
|
maxPerFileFromLinterConfig: maxPerFileFromLinterConfig,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,8 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/golangci/golangci-lint/pkg/golinters"
|
||||||
|
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||||
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
|
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
|
||||||
"github.com/golangci/golangci-lint/pkg/logutils"
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||||
"github.com/golangci/golangci-lint/pkg/result"
|
"github.com/golangci/golangci-lint/pkg/result"
|
||||||
@ -17,6 +19,7 @@ var nolintDebugf = logutils.Debug("nolint")
|
|||||||
|
|
||||||
type ignoredRange struct {
|
type ignoredRange struct {
|
||||||
linters []string
|
linters []string
|
||||||
|
matchedIssueFromLinter map[string]bool
|
||||||
result.Range
|
result.Range
|
||||||
col int
|
col int
|
||||||
}
|
}
|
||||||
@ -26,6 +29,15 @@ func (i *ignoredRange) doesMatch(issue *result.Issue) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handle possible unused nolint directives
|
||||||
|
// nolintlint generates potential issues for every nolint directive and they are filtered out here
|
||||||
|
if issue.ExpectNoLint {
|
||||||
|
if issue.ExpectedNoLintLinter != "" {
|
||||||
|
return i.matchedIssueFromLinter[issue.ExpectedNoLintLinter]
|
||||||
|
}
|
||||||
|
return len(i.matchedIssueFromLinter) > 0
|
||||||
|
}
|
||||||
|
|
||||||
if len(i.linters) == 0 {
|
if len(i.linters) == 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -48,15 +60,22 @@ type filesCache map[string]*fileData
|
|||||||
type Nolint struct {
|
type Nolint struct {
|
||||||
cache filesCache
|
cache filesCache
|
||||||
dbManager *lintersdb.Manager
|
dbManager *lintersdb.Manager
|
||||||
|
enabledLinters map[string]bool
|
||||||
log logutils.Log
|
log logutils.Log
|
||||||
|
|
||||||
unknownLintersSet map[string]bool
|
unknownLintersSet map[string]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNolint(log logutils.Log, dbManager *lintersdb.Manager) *Nolint {
|
func NewNolint(log logutils.Log, dbManager *lintersdb.Manager, enabledLCs []*linter.Config) *Nolint {
|
||||||
|
enabledLinters := make(map[string]bool, len(enabledLCs))
|
||||||
|
for _, lc := range enabledLCs {
|
||||||
|
enabledLinters[lc.Name()] = true
|
||||||
|
}
|
||||||
|
|
||||||
return &Nolint{
|
return &Nolint{
|
||||||
cache: filesCache{},
|
cache: filesCache{},
|
||||||
dbManager: dbManager,
|
dbManager: dbManager,
|
||||||
|
enabledLinters: enabledLinters,
|
||||||
log: log,
|
log: log,
|
||||||
unknownLintersSet: map[string]bool{},
|
unknownLintersSet: map[string]bool{},
|
||||||
}
|
}
|
||||||
@ -69,6 +88,8 @@ func (p Nolint) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Nolint) Process(issues []result.Issue) ([]result.Issue, error) {
|
func (p *Nolint) Process(issues []result.Issue) ([]result.Issue, error) {
|
||||||
|
// put nolintlint issues last because we process other issues first to determine which nolint directives are unused
|
||||||
|
sort.Stable(sortWithNolintlintLast(issues))
|
||||||
return filterIssuesErr(issues, p.shouldPassIssue)
|
return filterIssuesErr(issues, p.shouldPassIssue)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,6 +145,22 @@ func (p *Nolint) buildIgnoredRangesForFile(f *ast.File, fset *token.FileSet, fil
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Nolint) shouldPassIssue(i *result.Issue) (bool, error) {
|
func (p *Nolint) shouldPassIssue(i *result.Issue) (bool, error) {
|
||||||
|
nolintDebugf("got issue: %v", *i)
|
||||||
|
if i.FromLinter == golinters.NolintlintName {
|
||||||
|
// always pass nolintlint issues except ones trying find unused nolint directives
|
||||||
|
if !i.ExpectNoLint {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
if i.ExpectedNoLintLinter != "" {
|
||||||
|
// don't expect disabled linters to cover their nolint statements
|
||||||
|
nolintDebugf("enabled linters: %v", p.enabledLinters)
|
||||||
|
if !p.enabledLinters[i.ExpectedNoLintLinter] {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
nolintDebugf("checking that lint issue was used for %s: %v", i.ExpectedNoLintLinter, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fd, err := p.getOrCreateFileData(i)
|
fd, err := p.getOrCreateFileData(i)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -131,6 +168,7 @@ func (p *Nolint) shouldPassIssue(i *result.Issue) (bool, error) {
|
|||||||
|
|
||||||
for _, ir := range fd.ignoredRanges {
|
for _, ir := range fd.ignoredRanges {
|
||||||
if ir.doesMatch(i) {
|
if ir.doesMatch(i) {
|
||||||
|
ir.matchedIssueFromLinter[i.FromLinter] = true
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -205,6 +243,7 @@ func (p *Nolint) extractInlineRangeFromComment(text string, g ast.Node, fset *to
|
|||||||
},
|
},
|
||||||
col: pos.Column,
|
col: pos.Column,
|
||||||
linters: linters,
|
linters: linters,
|
||||||
|
matchedIssueFromLinter: make(map[string]bool),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,3 +292,18 @@ func (p Nolint) Finish() {
|
|||||||
|
|
||||||
p.log.Warnf("Found unknown linters in //nolint directives: %s", strings.Join(unknownLinters, ", "))
|
p.log.Warnf("Found unknown linters in //nolint directives: %s", strings.Join(unknownLinters, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// put nolintlint last
|
||||||
|
type sortWithNolintlintLast []result.Issue
|
||||||
|
|
||||||
|
func (issues sortWithNolintlintLast) Len() int {
|
||||||
|
return len(issues)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (issues sortWithNolintlintLast) Less(i, j int) bool {
|
||||||
|
return issues[i].FromLinter != golinters.NolintlintName && issues[j].FromLinter == golinters.NolintlintName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (issues sortWithNolintlintLast) Swap(i, j int) {
|
||||||
|
issues[j], issues[i] = issues[i], issues[j]
|
||||||
|
}
|
||||||
|
@ -9,6 +9,8 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
|
|
||||||
|
"github.com/golangci/golangci-lint/pkg/config"
|
||||||
|
"github.com/golangci/golangci-lint/pkg/golinters"
|
||||||
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
|
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
|
||||||
"github.com/golangci/golangci-lint/pkg/logutils"
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||||
"github.com/golangci/golangci-lint/pkg/result"
|
"github.com/golangci/golangci-lint/pkg/result"
|
||||||
@ -31,7 +33,7 @@ func newNolint2FileIssue(line int) result.Issue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newTestNolintProcessor(log logutils.Log) *Nolint {
|
func newTestNolintProcessor(log logutils.Log) *Nolint {
|
||||||
return NewNolint(log, lintersdb.NewManager(nil, nil))
|
return NewNolint(log, lintersdb.NewManager(nil, nil), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMockLog() *logutils.MockLog {
|
func getMockLog() *logutils.MockLog {
|
||||||
@ -40,7 +42,6 @@ func getMockLog() *logutils.MockLog {
|
|||||||
return log
|
return log
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:funlen
|
|
||||||
func TestNolint(t *testing.T) {
|
func TestNolint(t *testing.T) {
|
||||||
p := newTestNolintProcessor(getMockLog())
|
p := newTestNolintProcessor(getMockLog())
|
||||||
defer p.Finish()
|
defer p.Finish()
|
||||||
@ -229,7 +230,7 @@ func TestNolintWholeFile(t *testing.T) {
|
|||||||
Filename: fileName,
|
Filename: fileName,
|
||||||
Line: 4,
|
Line: 4,
|
||||||
},
|
},
|
||||||
FromLinter: "unparam",
|
FromLinter: "varcheck",
|
||||||
})
|
})
|
||||||
processAssertSame(t, p, result.Issue{
|
processAssertSame(t, p, result.Issue{
|
||||||
Pos: token.Position{
|
Pos: token.Position{
|
||||||
@ -239,3 +240,68 @@ func TestNolintWholeFile(t *testing.T) {
|
|||||||
FromLinter: "deadcode",
|
FromLinter: "deadcode",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNolintUnused(t *testing.T) {
|
||||||
|
fileName := filepath.Join("testdata", "nolint_unused.go")
|
||||||
|
|
||||||
|
log := getMockLog()
|
||||||
|
log.On("Warnf", "Found unknown linters in //nolint directives: %s", "blah")
|
||||||
|
|
||||||
|
createProcessor := func(t *testing.T, log *logutils.MockLog, enabledLinters []string) *Nolint {
|
||||||
|
enabledSetLog := logutils.NewMockLog()
|
||||||
|
enabledSetLog.On("Infof", "Active %d linters: %s", len(enabledLinters), enabledLinters)
|
||||||
|
cfg := &config.Config{Linters: config.Linters{DisableAll: true, Enable: enabledLinters}}
|
||||||
|
dbManager := lintersdb.NewManager(cfg, nil)
|
||||||
|
enabledLintersSet := lintersdb.NewEnabledSet(dbManager, lintersdb.NewValidator(dbManager), enabledSetLog, cfg)
|
||||||
|
lcs, err := enabledLintersSet.Get(false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
return NewNolint(log, dbManager, lcs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the issues below the nolintlint issues that would be generated for the test file
|
||||||
|
nolintlintIssueVarcheck := result.Issue{
|
||||||
|
Pos: token.Position{
|
||||||
|
Filename: fileName,
|
||||||
|
Line: 3,
|
||||||
|
},
|
||||||
|
FromLinter: golinters.NolintlintName,
|
||||||
|
ExpectNoLint: true,
|
||||||
|
ExpectedNoLintLinter: "varcheck",
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("when an issue does not occur, it is not removed from the nolintlint issues", func(t *testing.T) {
|
||||||
|
p := createProcessor(t, log, []string{"nolintlint", "varcheck"})
|
||||||
|
defer p.Finish()
|
||||||
|
|
||||||
|
processAssertSame(t, p, nolintlintIssueVarcheck)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when an issue occurs, it is removed from the nolintlint issues", func(t *testing.T) {
|
||||||
|
p := createProcessor(t, log, []string{"nolintlint", "varcheck"})
|
||||||
|
defer p.Finish()
|
||||||
|
|
||||||
|
processAssertEmpty(t, p, []result.Issue{{
|
||||||
|
Pos: token.Position{
|
||||||
|
Filename: fileName,
|
||||||
|
Line: 3,
|
||||||
|
},
|
||||||
|
FromLinter: "varcheck",
|
||||||
|
}, nolintlintIssueVarcheck}...)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when a linter is not enabled, it is removed from the nolintlint unused issues", func(t *testing.T) {
|
||||||
|
enabledSetLog := logutils.NewMockLog()
|
||||||
|
enabledSetLog.On("Infof", "Active %d linters: %s", 1, []string{"nolintlint"})
|
||||||
|
|
||||||
|
cfg := &config.Config{Linters: config.Linters{DisableAll: true, Enable: []string{"nolintlint"}}}
|
||||||
|
dbManager := lintersdb.NewManager(cfg, nil)
|
||||||
|
enabledLintersSet := lintersdb.NewEnabledSet(dbManager, lintersdb.NewValidator(dbManager), enabledSetLog, cfg)
|
||||||
|
|
||||||
|
lcs, err := enabledLintersSet.Get(false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
p := NewNolint(log, dbManager, lcs)
|
||||||
|
defer p.Finish()
|
||||||
|
|
||||||
|
processAssertEmpty(t, p, nolintlintIssueVarcheck)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
3
pkg/result/processors/testdata/nolint_unused.go
vendored
Normal file
3
pkg/result/processors/testdata/nolint_unused.go
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package testdata
|
||||||
|
|
||||||
|
var nolintVarcheck int // nolint:varcheck
|
@ -1,4 +1,4 @@
|
|||||||
//nolint: unparam
|
//nolint: varcheck
|
||||||
package testdata
|
package testdata
|
||||||
|
|
||||||
var nolintUnparam int
|
var nolintVarcheck int
|
||||||
|
@ -211,7 +211,7 @@ type runResult struct {
|
|||||||
duration time.Duration
|
duration time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func compare(b *testing.B, gometalinterRun, golangciLintRun func(*testing.B), repoName, mode string, kLOC int) { // nolint
|
func compare(b *testing.B, gometalinterRun, golangciLintRun func(*testing.B), repoName, mode string, kLOC int) {
|
||||||
gometalinterRes := runOne(b, gometalinterRun, "gometalinter")
|
gometalinterRes := runOne(b, gometalinterRun, "gometalinter")
|
||||||
golangciLintRes := runOne(b, golangciLintRun, "golangci-lint")
|
golangciLintRes := runOne(b, golangciLintRun, "golangci-lint")
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ func errorCheck(outStr string, wantAuto bool, fullshort ...string) (err error) {
|
|||||||
if len(errs) == 0 {
|
if len(errs) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if len(errs) == 1 { //nolint:gomnd
|
if len(errs) == 1 {
|
||||||
return errs[0]
|
return errs[0]
|
||||||
}
|
}
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
@ -103,7 +103,7 @@ func splitOutput(out string, wantAuto bool) []string {
|
|||||||
var res []string
|
var res []string
|
||||||
for _, line := range strings.Split(out, "\n") {
|
for _, line := range strings.Split(out, "\n") {
|
||||||
line = strings.TrimSuffix(line, "\r") // normalize Windows output
|
line = strings.TrimSuffix(line, "\r") // normalize Windows output
|
||||||
if strings.HasPrefix(line, "\t") { //nolint:gocritic
|
if strings.HasPrefix(line, "\t") {
|
||||||
res[len(res)-1] += "\n" + line
|
res[len(res)-1] += "\n" + line
|
||||||
} else if strings.HasPrefix(line, "go tool") || strings.HasPrefix(line, "#") || !wantAuto && strings.HasPrefix(line, "<autogenerated>") {
|
} else if strings.HasPrefix(line, "go tool") || strings.HasPrefix(line, "#") || !wantAuto && strings.HasPrefix(line, "<autogenerated>") {
|
||||||
continue
|
continue
|
||||||
|
13
test/testdata/nolintlint.go
vendored
Normal file
13
test/testdata/nolintlint.go
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
//args: -Enolintlint
|
||||||
|
//config: linters-settings.nolintlint.require-explanation=true
|
||||||
|
//config: linters-settings.nolintlint.require-specific=true
|
||||||
|
//config: linters-settings.nolintlint.allowing-leading-space=false
|
||||||
|
package testdata
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func Foo() {
|
||||||
|
fmt.Println("not specific") //nolint // ERROR "directive `.*` should mention specific linter such as `//nolint:my-linter`"
|
||||||
|
fmt.Println("not machine readable") // nolint // ERROR "directive `.*` should be written as `//nolint`"
|
||||||
|
fmt.Println("extra spaces") // nolint:deadcode // because // ERROR "directive `.*` should not have more than one leading space"
|
||||||
|
}
|
11
test/testdata/nolintlint_unused.go
vendored
Normal file
11
test/testdata/nolintlint_unused.go
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
//args: -Enolintlint -Evarcheck
|
||||||
|
//config: linters-settings.nolintlint.allow-unused=false
|
||||||
|
package testdata
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func Foo() {
|
||||||
|
fmt.Println("unused") //nolint // ERROR "directive `//nolint .*` is unused"
|
||||||
|
fmt.Println("unused,specific") //nolint:varcheck // ERROR "directive `//nolint:varcheck .*` is unused for linter varcheck"
|
||||||
|
fmt.Println("not run") //nolint:unparam // unparam is not run so this is ok
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user