Fix #96: support lll

This commit is contained in:
Denis Isaev 2018-06-28 22:39:23 +03:00 committed by Isaev Denis
parent 7b2a63dfa6
commit 1a9af12d6d
17 changed files with 207 additions and 46 deletions

View File

@ -104,6 +104,9 @@ linters-settings:
# Default is to use a neutral variety of English.
# Setting locale to US will correct the British spelling of 'colour' to 'color'.
locale: US
lll:
# max line length, lines longer will be reported. Default is 120. '\t' is counted as 1 character.
line-length: 120
linters:
enable:

View File

@ -114,6 +114,7 @@ maligned: Tool to detect Go structs that would take less memory if their fields
megacheck: 3 sub-linters in one: unused, gosimple and staticcheck [fast: false]
depguard: Go linter that checks if package imports are in a list of acceptable packages [fast: false]
misspell: Finds commonly misspelled English words in comments [fast: true]
lll: Reports long lines [fast: true]
```
Pass `-E/--enable` to enable linter and `-D/--disable` to disable:
@ -220,6 +221,7 @@ golangci-lint linters
- [megacheck](https://github.com/dominikh/go-tools/tree/master/cmd/megacheck) - 3 sub-linters in one: unused, gosimple and staticcheck
- [depguard](https://github.com/OpenPeeDeeP/depguard) - Go linter that checks if package imports are in a list of acceptable packages
- [misspell](https://github.com/client9/misspell) - Finds commonly misspelled English words in comments
- [lll](https://github.com/walle/lll) - Reports long lines
# Configuration
The config file has lower priority than command-line options. If the same bool/string/int option is provided on the command-line
@ -422,6 +424,9 @@ linters-settings:
# Default is to use a neutral variety of English.
# Setting locale to US will correct the British spelling of 'colour' to 'color'.
locale: US
lll:
# max line length, lines longer will be reported. Default is 120. '\t' is counted as 1 character.
line-length: 120
linters:
enable:
@ -601,6 +606,7 @@ Thanks to developers and authors of used linters:
- [alecthomas](https://github.com/alecthomas)
- [OpenPeeDeeP](https://github.com/OpenPeeDeeP)
- [client9](https://github.com/client9)
- [walle](https://github.com/walle)
# Future Plans
1. Upstream all changes of forked linters.

View File

@ -23,7 +23,7 @@ type Executor struct {
func NewExecutor(version, commit, date string) *Executor {
e := &Executor{
cfg: &config.Config{},
cfg: config.NewDefault(),
version: version,
commit: commit,
date: date,

View File

@ -32,7 +32,8 @@ func getDefaultExcludeHelp() string {
return strings.Join(parts, "\n")
}
const welcomeMessage = "Run this tool in cloud on every github pull request in https://golangci.com for free (public repos)"
const welcomeMessage = "Run this tool in cloud on every github pull " +
"request in https://golangci.com for free (public repos)"
func wh(text string) string {
return color.GreenString(text)
@ -62,7 +63,8 @@ func initFlagSet(fs *pflag.FlagSet, cfg *config.Config) {
fs.StringSliceVar(&rc.BuildTags, "build-tags", nil, wh("Build tags"))
fs.DurationVar(&rc.Deadline, "deadline", time.Minute, wh("Deadline for total work"))
fs.BoolVar(&rc.AnalyzeTests, "tests", true, wh("Analyze tests (*_test.go)"))
fs.BoolVar(&rc.PrintResourcesUsage, "print-resources-usage", false, wh("Print avg and max memory usage of golangci-lint and total time"))
fs.BoolVar(&rc.PrintResourcesUsage, "print-resources-usage", false,
wh("Print avg and max memory usage of golangci-lint and total time"))
fs.StringVarP(&rc.Config, "config", "c", "", wh("Read config from file path `PATH`"))
fs.BoolVar(&rc.NoConfig, "no-config", false, wh("Don't read config"))
fs.StringSliceVar(&rc.SkipDirs, "skip-dirs", nil, wh("Regexps of directories to skip"))
@ -75,16 +77,20 @@ func initFlagSet(fs *pflag.FlagSet, cfg *config.Config) {
// but when number of linters started to grow it became ovious that
// we can't fill 90% of flags by linters settings: common flags became hard to find.
// New linters settings should be done only through config file.
fs.BoolVar(&lsc.Errcheck.CheckTypeAssertions, "errcheck.check-type-assertions", false, "Errcheck: check for ignored type assertion results")
fs.BoolVar(&lsc.Errcheck.CheckTypeAssertions, "errcheck.check-type-assertions",
false, "Errcheck: check for ignored type assertion results")
hideFlag("errcheck.check-type-assertions")
fs.BoolVar(&lsc.Errcheck.CheckAssignToBlank, "errcheck.check-blank", false, "Errcheck: check for errors assigned to blank identifier: _ = errFunc()")
fs.BoolVar(&lsc.Errcheck.CheckAssignToBlank, "errcheck.check-blank", false,
"Errcheck: check for errors assigned to blank identifier: _ = errFunc()")
hideFlag("errcheck.check-blank")
fs.BoolVar(&lsc.Govet.CheckShadowing, "govet.check-shadowing", false, "Govet: check for shadowed variables")
fs.BoolVar(&lsc.Govet.CheckShadowing, "govet.check-shadowing", false,
"Govet: check for shadowed variables")
hideFlag("govet.check-shadowing")
fs.Float64Var(&lsc.Golint.MinConfidence, "golint.min-confidence", 0.8, "Golint: minimum confidence of a problem to print it")
fs.Float64Var(&lsc.Golint.MinConfidence, "golint.min-confidence", 0.8,
"Golint: minimum confidence of a problem to print it")
hideFlag("golint.min-confidence")
fs.BoolVar(&lsc.Gofmt.Simplify, "gofmt.simplify", true, "Gofmt: simplify code")
@ -94,7 +100,8 @@ func initFlagSet(fs *pflag.FlagSet, cfg *config.Config) {
30, "Minimal complexity of function to report it")
hideFlag("gocyclo.min-complexity")
fs.BoolVar(&lsc.Maligned.SuggestNewOrder, "maligned.suggest-new", false, "Maligned: print suggested more optimal struct fields ordering")
fs.BoolVar(&lsc.Maligned.SuggestNewOrder, "maligned.suggest-new", false,
"Maligned: print suggested more optimal struct fields ordering")
hideFlag("maligned.suggest-new")
fs.IntVar(&lsc.Dupl.Threshold, "dupl.threshold",
@ -124,7 +131,8 @@ func initFlagSet(fs *pflag.FlagSet, cfg *config.Config) {
fs.BoolVar(&lc.EnableAll, "enable-all", false, wh("Enable all linters"))
fs.BoolVar(&lc.DisableAll, "disable-all", false, wh("Disable all linters"))
fs.StringSliceVarP(&lc.Presets, "presets", "p", nil,
wh(fmt.Sprintf("Enable presets (%s) of linters. Run 'golangci-lint linters' to see them. This option implies option --disable-all", strings.Join(lintersdb.AllPresets(), "|"))))
wh(fmt.Sprintf("Enable presets (%s) of linters. Run 'golangci-lint linters' to see "+
"them. This option implies option --disable-all", strings.Join(lintersdb.AllPresets(), "|"))))
fs.BoolVar(&lc.Fast, "fast", false, wh("Run only fast linters from enabled linters set"))
// Issues config
@ -132,13 +140,20 @@ func initFlagSet(fs *pflag.FlagSet, cfg *config.Config) {
fs.StringSliceVarP(&ic.ExcludePatterns, "exclude", "e", nil, wh("Exclude issue by regexp"))
fs.BoolVar(&ic.UseDefaultExcludes, "exclude-use-default", true, getDefaultExcludeHelp())
fs.IntVar(&ic.MaxIssuesPerLinter, "max-issues-per-linter", 50, wh("Maximum issues count per one linter. Set to 0 to disable"))
fs.IntVar(&ic.MaxSameIssues, "max-same-issues", 3, wh("Maximum count of issues with the same text. Set to 0 to disable"))
fs.IntVar(&ic.MaxIssuesPerLinter, "max-issues-per-linter", 50,
wh("Maximum issues count per one linter. Set to 0 to disable"))
fs.IntVar(&ic.MaxSameIssues, "max-same-issues", 3,
wh("Maximum count of issues with the same text. Set to 0 to disable"))
fs.BoolVarP(&ic.Diff, "new", "n", false,
wh("Show only new issues: if there are unstaged changes or untracked files, only those changes are analyzed, else only changes in HEAD~ are analyzed.\nIt's a super-useful option for integration of golangci-lint into existing large codebase.\nIt's not practical to fix all existing issues at the moment of integration: much better don't allow issues in new code"))
fs.StringVar(&ic.DiffFromRevision, "new-from-rev", "", wh("Show only new issues created after git revision `REV`"))
fs.StringVar(&ic.DiffPatchFilePath, "new-from-patch", "", wh("Show only new issues created in git patch with file path `PATH`"))
wh("Show only new issues: if there are unstaged changes or untracked files, only those changes "+
"are analyzed, else only changes in HEAD~ are analyzed.\nIt's a super-useful option for integration "+
"of golangci-lint into existing large codebase.\nIt's not practical to fix all existing issues at "+
"the moment of integration: much better don't allow issues in new code"))
fs.StringVar(&ic.DiffFromRevision, "new-from-rev", "",
wh("Show only new issues created after git revision `REV`"))
fs.StringVar(&ic.DiffPatchFilePath, "new-from-patch", "",
wh("Show only new issues created in git patch with file path `PATH`"))
}

View File

@ -30,12 +30,14 @@ type ExcludePattern struct {
var DefaultExcludePatterns = []ExcludePattern{
{
Pattern: "Error return value of .((os\\.)?std(out|err)\\..*|.*Close|.*Flush|os\\.Remove(All)?|.*printf?|os\\.(Un)?Setenv). is not checked",
Pattern: "Error return value of .((os\\.)?std(out|err)\\..*|.*Close" +
"|.*Flush|os\\.Remove(All)?|.*printf?|os\\.(Un)?Setenv). is not checked",
Linter: "errcheck",
Why: "Almost all programs ignore errors on these functions and in most cases it's ok",
},
{
Pattern: "(comment on exported (method|function|type|const)|should have( a package)? comment|comment should be of the form)",
Pattern: "(comment on exported (method|function|type|const)|" +
"should have( a package)? comment|comment should be of the form)",
Linter: "golint",
Why: "Annoying issue about not having a comment. The rare codebase has such comments",
},
@ -156,6 +158,17 @@ type LintersSettings struct {
Misspell struct {
Locale string
}
Lll LllSettings
}
type LllSettings struct {
LineLength int `mapstructure:"line-length"`
}
var defaultLintersSettings = LintersSettings{
Lll: LllSettings{
LineLength: 120,
},
}
type Linters struct {
@ -196,3 +209,9 @@ type Config struct { //nolint:maligned
InternalTest bool // Option is used only for testing golangci-lint code, don't use it
}
func NewDefault() *Config {
return &Config{
LintersSettings: defaultLintersSettings,
}
}

View File

@ -16,7 +16,8 @@ func (Errcheck) Name() string {
}
func (Errcheck) Desc() string {
return "Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases"
return "Errcheck is a program for checking for unchecked errors " +
"in go programs. These unchecked errors can be critical bugs in some cases"
}
func (e Errcheck) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {

View File

@ -31,7 +31,8 @@ func (g Gofmt) Desc() string {
return "Goimports does everything that gofmt does. Additionally it checks unused imports"
}
return "Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification"
return "Gofmt checks whether code was gofmt-ed. By default " +
"this tool runs with -s option to check for code simplification"
}
func getFirstDeletedAndAddedLineNumberInHunk(h *diff.Hunk) (int, int, error) {

View File

@ -25,7 +25,8 @@ func (Govet) Name() string {
}
func (Govet) Desc() string {
return "Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string"
return "Vet examines Go source code and reports suspicious constructs, " +
"such as Printf calls whose arguments do not align with the format string"
}
func (g Govet) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {

72
pkg/golinters/lll.go Normal file
View File

@ -0,0 +1,72 @@
package golinters
import (
"bufio"
"context"
"fmt"
"go/token"
"os"
"strings"
"unicode/utf8"
"github.com/golangci/golangci-lint/pkg/lint/linter"
"github.com/golangci/golangci-lint/pkg/result"
)
type Lll struct{}
func (Lll) Name() string {
return "lll"
}
func (Lll) Desc() string {
return "Reports long lines"
}
func (lint Lll) getIssuesForFile(filename string, maxLineLen int) ([]result.Issue, error) {
var res []result.Issue
f, err := os.Open(filename)
if err != nil {
return nil, fmt.Errorf("can't open file %s: %s", filename, err)
}
lineNumber := 1
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
line = strings.Replace(line, "\t", " ", -1)
lineLen := utf8.RuneCountInString(line)
if lineLen > maxLineLen {
res = append(res, result.Issue{
Pos: token.Position{
Filename: filename,
Line: lineNumber,
Column: 1,
},
Text: fmt.Sprintf("line is %d characters", lineLen),
FromLinter: lint.Name(),
})
}
lineNumber++
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("can't scan file %s: %s", filename, err)
}
return res, nil
}
func (lint Lll) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
var res []result.Issue
for _, f := range lintCtx.PkgProgram.Files(lintCtx.Cfg.Run.AnalyzeTests) {
issues, err := lint.getIssuesForFile(f, lintCtx.Settings().Lll.LineLength)
if err != nil {
return nil, err
}
res = append(res, issues...)
}
return res, nil
}

View File

@ -14,7 +14,8 @@ import (
)
func AllPresets() []string {
return []string{linter.PresetBugs, linter.PresetUnused, linter.PresetFormatting, linter.PresetStyle, linter.PresetComplexity, linter.PresetPerformance}
return []string{linter.PresetBugs, linter.PresetUnused, linter.PresetFormatting,
linter.PresetStyle, linter.PresetComplexity, linter.PresetPerformance}
}
func allPresetsSet() map[string]bool {
@ -166,6 +167,10 @@ func GetAllSupportedLinterConfigs() []linter.Config {
WithPresets(linter.PresetStyle).
WithSpeed(7).
WithURL("https://github.com/client9/misspell"),
linter.NewConfig(golinters.Lll{}).
WithPresets(linter.PresetStyle).
WithSpeed(10).
WithURL("https://github.com/walle/lll"),
}
if os.Getenv("GOLANGCI_COM_RUN") == "1" {
@ -175,6 +180,7 @@ func GetAllSupportedLinterConfigs() []linter.Config {
golinters.Maligned{}.Name(): true, // rarely usable
golinters.TypeCheck{}.Name(): true, // annoying because of different building envs
golinters.Misspell{}.Name(): true, // unsure about false-positives number
golinters.Lll{}.Name(): true, // annoying
}
return enableLinterConfigs(lcs, func(lc *linter.Config) bool {
return !disabled[lc.Linter.Name()]
@ -314,7 +320,10 @@ func GetAllLinterConfigsForPreset(p string) []linter.Config {
return ret
}
func getEnabledLintersSet(lcfg *config.Linters, enabledByDefaultLinters []linter.Config) map[string]*linter.Config { // nolint:gocyclo
// nolint:gocyclo
func getEnabledLintersSet(lcfg *config.Linters,
enabledByDefaultLinters []linter.Config) map[string]*linter.Config {
resultLintersSet := map[string]*linter.Config{}
switch {
case len(lcfg.Presets) != 0:

View File

@ -109,7 +109,9 @@ func isLocalProjectAnalysis(args []string) bool {
return true
}
func getTypeCheckFuncBodies(cfg *config.Run, linters []linter.Config, pkgProg *packages.Program, log logutils.Log) func(string) bool {
func getTypeCheckFuncBodies(cfg *config.Run, linters []linter.Config,
pkgProg *packages.Program, log logutils.Log) func(string) bool {
if !isLocalProjectAnalysis(cfg.Args) {
loadDebugf("analysis in nonlocal, don't optimize loading by not typechecking func bodies")
return nil
@ -155,7 +157,9 @@ func getTypeCheckFuncBodies(cfg *config.Run, linters []linter.Config, pkgProg *p
}
}
func loadWholeAppIfNeeded(ctx context.Context, linters []linter.Config, cfg *config.Config, pkgProg *packages.Program, log logutils.Log) (*loader.Program, *loader.Config, error) {
func loadWholeAppIfNeeded(ctx context.Context, linters []linter.Config, cfg *config.Config,
pkgProg *packages.Program, log logutils.Log) (*loader.Program, *loader.Config, error) {
if !isFullImportNeeded(linters, cfg) {
return nil, nil, nil
}
@ -255,7 +259,9 @@ func separateNotCompilingPackages(lintCtx *linter.Context) {
}
//nolint:gocyclo
func LoadContext(ctx context.Context, linters []linter.Config, cfg *config.Config, log logutils.Log) (*linter.Context, error) {
func LoadContext(ctx context.Context, linters []linter.Config, cfg *config.Config,
log logutils.Log) (*linter.Context, error) {
// Set GOROOT to have working cross-compilation: cross-compiled binaries
// have invalid GOROOT. XXX: can't use runtime.GOROOT().
goroot, err := goutils.DiscoverGoRoot()

View File

@ -66,7 +66,9 @@ type lintRes struct {
issues []result.Issue
}
func (r Runner) runLinterSafe(ctx context.Context, lintCtx *linter.Context, lc linter.Config) (ret []result.Issue, err error) {
func (r Runner) runLinterSafe(ctx context.Context, lintCtx *linter.Context,
lc linter.Config) (ret []result.Issue, err error) {
defer func() {
if panicData := recover(); panicData != nil {
err = fmt.Errorf("panic occurred: %s", panicData)
@ -88,7 +90,9 @@ func (r Runner) runLinterSafe(ctx context.Context, lintCtx *linter.Context, lc l
return issues, nil
}
func (r Runner) runWorker(ctx context.Context, lintCtx *linter.Context, tasksCh <-chan linter.Config, lintResultsCh chan<- lintRes, name string) {
func (r Runner) runWorker(ctx context.Context, lintCtx *linter.Context,
tasksCh <-chan linter.Config, lintResultsCh chan<- lintRes, name string) {
sw := timeutils.NewStopwatch(name, r.Log)
defer sw.Print()

View File

@ -5,7 +5,6 @@ import (
"context"
"fmt"
"io/ioutil"
"strings"
"time"
"github.com/fatih/color"
@ -140,18 +139,15 @@ func (p Text) printUnderLinePointer(i *result.Issue, line string) {
return
}
var j int
for ; j < len(line) && line[j] == '\t'; j++ {
col0 := i.Pos.Column - 1
prefixRunes := make([]rune, 0, len(line))
for j := 0; j < len(line) && j < col0; j++ {
if line[j] == '\t' {
prefixRunes = append(prefixRunes, '\t')
} else {
prefixRunes = append(prefixRunes, ' ')
}
tabsCount := j
spacesCount := i.Pos.Column - 1 - tabsCount
prefix := ""
if tabsCount != 0 {
prefix += strings.Repeat("\t", tabsCount)
}
if spacesCount != 0 {
prefix += strings.Repeat(" ", spacesCount)
}
fmt.Fprintf(logutils.StdOut, "%s%s\n", prefix, p.SprintfColored(color.FgYellow, "^"))
fmt.Fprintf(logutils.StdOut, "%s%s\n", string(prefixRunes), p.SprintfColored(color.FgYellow, "^"))
}

View File

@ -103,10 +103,12 @@ func getDoc(f *ast.File, fset *token.FileSet, filePath string) string {
var importPos token.Pos
if len(f.Imports) != 0 {
importPos = f.Imports[0].Pos()
autogenDebugf("file %q: search comments until first import pos %d (%s)", filePath, importPos, fset.Position(importPos))
autogenDebugf("file %q: search comments until first import pos %d (%s)",
filePath, importPos, fset.Position(importPos))
} else {
importPos = f.End()
autogenDebugf("file %q: search comments until EOF pos %d (%s)", filePath, importPos, fset.Position(importPos))
autogenDebugf("file %q: search comments until EOF pos %d (%s)",
filePath, importPos, fset.Position(importPos))
}
var neededComments []string

View File

@ -39,7 +39,8 @@ func TestIsAutogeneratedDetection(t *testing.T) {
// DO NOT EDIT ** This file was generated with the bake tool ** DO NOT EDIT //
// Generated by running
// maketables --tables=all --data=http://www.unicode.org/Public/8.0.0/ucd/UnicodeData.txt --casefolding=http://www.unicode.org/Public/8.0.0/ucd/CaseFolding.txt
// maketables --tables=all --data=http://www.unicode.org/Public/8.0.0/ucd/UnicodeData.txt
// --casefolding=http://www.unicode.org/Public/8.0.0/ucd/CaseFolding.txt
// DO NOT EDIT
/*

6
test/testdata/lll.go vendored Normal file
View File

@ -0,0 +1,6 @@
// args: -Elll
package testdata
func Lll() {
// In my experience, long lines are the lines with comments, not the code. So this is a long comment // ERROR "line is 135 characters"
}

19
third_party/lll/LICENSE vendored Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2016 Fredrik Wallgren
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.