github.com/chenfeining/golangci-lint@v1.0.2-0.20230730162517-14c6c67868df/pkg/result/processors/nolint_test.go (about)

     1  package processors
     2  
     3  import (
     4  	"fmt"
     5  	"go/token"
     6  	"path/filepath"
     7  	"testing"
     8  
     9  	"github.com/stretchr/testify/assert"
    10  	"github.com/stretchr/testify/mock"
    11  
    12  	"github.com/chenfeining/golangci-lint/pkg/config"
    13  	"github.com/chenfeining/golangci-lint/pkg/golinters"
    14  	"github.com/chenfeining/golangci-lint/pkg/lint/lintersdb"
    15  	"github.com/chenfeining/golangci-lint/pkg/logutils"
    16  	"github.com/chenfeining/golangci-lint/pkg/result"
    17  )
    18  
    19  func newNolintFileIssue(line int, fromLinter string) result.Issue {
    20  	return result.Issue{
    21  		Pos: token.Position{
    22  			Filename: filepath.Join("testdata", "nolint.go"),
    23  			Line:     line,
    24  		},
    25  		FromLinter: fromLinter,
    26  	}
    27  }
    28  
    29  func newNolint2FileIssue(line int) result.Issue {
    30  	i := newNolintFileIssue(line, "errcheck")
    31  	i.Pos.Filename = filepath.Join("testdata", "nolint2.go")
    32  	return i
    33  }
    34  
    35  func newTestNolintProcessor(log logutils.Log) *Nolint {
    36  	return NewNolint(log, lintersdb.NewManager(nil, nil), nil)
    37  }
    38  
    39  func getMockLog() *logutils.MockLog {
    40  	log := logutils.NewMockLog()
    41  	log.On("Infof", mock.Anything, mock.Anything).Maybe()
    42  	return log
    43  }
    44  
    45  func TestNolint(t *testing.T) {
    46  	p := newTestNolintProcessor(getMockLog())
    47  	defer p.Finish()
    48  
    49  	// test inline comments
    50  	processAssertEmpty(t, p, newNolintFileIssue(3, "gofmt"))
    51  	processAssertEmpty(t, p, newNolintFileIssue(3, "gofmt")) // check cached is ok
    52  	processAssertSame(t, p, newNolintFileIssue(3, "gofmtA")) // check different name
    53  
    54  	processAssertEmpty(t, p, newNolintFileIssue(4, "gofmt"))
    55  	processAssertSame(t, p, newNolintFileIssue(4, "gofmtA")) // check different name
    56  
    57  	processAssertEmpty(t, p, newNolintFileIssue(5, "gofmt"))
    58  	processAssertEmpty(t, p, newNolintFileIssue(5, "govet"))
    59  	processAssertSame(t, p, newNolintFileIssue(5, "gofmtA")) // check different name
    60  
    61  	processAssertEmpty(t, p, newNolintFileIssue(6, "any"))
    62  	processAssertEmpty(t, p, newNolintFileIssue(7, "any"))
    63  
    64  	processAssertSame(t, p, newNolintFileIssue(1, "golint")) // no directive
    65  
    66  	// test preceding comments
    67  	processAssertEmpty(t, p, newNolintFileIssue(10, "any")) // preceding comment for var
    68  	processAssertEmpty(t, p, newNolintFileIssue(9, "any"))  // preceding comment for var itself
    69  
    70  	processAssertSame(t, p, newNolintFileIssue(14, "any"))  // preceding comment with extra \n
    71  	processAssertEmpty(t, p, newNolintFileIssue(12, "any")) // preceding comment with extra \n itself
    72  
    73  	processAssertSame(t, p, newNolintFileIssue(17, "any"))  // preceding comment on different column
    74  	processAssertEmpty(t, p, newNolintFileIssue(16, "any")) // preceding comment on different column itself
    75  
    76  	// preceding comment for func name and comment itself
    77  	for i := 19; i <= 23; i++ {
    78  		processAssertEmpty(t, p, newNolintFileIssue(i, "any"))
    79  	}
    80  
    81  	processAssertSame(t, p, newNolintFileIssue(24, "any")) // right after func
    82  
    83  	// preceding multiline comment: last line
    84  	for i := 25; i <= 30; i++ {
    85  		processAssertEmpty(t, p, newNolintFileIssue(i, "any"))
    86  	}
    87  
    88  	processAssertSame(t, p, newNolintFileIssue(31, "any")) // between funcs
    89  
    90  	// preceding multiline comment: first line
    91  	for i := 32; i <= 37; i++ {
    92  		processAssertEmpty(t, p, newNolintFileIssue(i, "any"))
    93  	}
    94  
    95  	processAssertSame(t, p, newNolintFileIssue(38, "any")) // between funcs
    96  
    97  	// preceding multiline comment: medium line
    98  	for i := 39; i <= 45; i++ {
    99  		processAssertEmpty(t, p, newNolintFileIssue(i, "any"))
   100  	}
   101  
   102  	// check bug with transitive expanding for next and next line
   103  	for i := 1; i <= 8; i++ {
   104  		processAssertSame(t, p, newNolint2FileIssue(i))
   105  	}
   106  	for i := 9; i <= 10; i++ {
   107  		processAssertEmpty(t, p, newNolint2FileIssue(i))
   108  	}
   109  
   110  	// check inline comment for function
   111  	for i := 11; i <= 13; i++ {
   112  		processAssertSame(t, p, newNolint2FileIssue(i))
   113  	}
   114  	processAssertEmpty(t, p, newNolint2FileIssue(14))
   115  	for i := 15; i <= 18; i++ {
   116  		processAssertSame(t, p, newNolint2FileIssue(i))
   117  	}
   118  
   119  	// variables block exclude
   120  	for i := 55; i <= 56; i++ {
   121  		processAssertSame(t, p, newNolint2FileIssue(i))
   122  	}
   123  }
   124  
   125  func TestNolintInvalidLinterName(t *testing.T) {
   126  	fileName := filepath.Join("testdata", "nolint_bad_names.go")
   127  	issues := []result.Issue{
   128  		{
   129  			Pos: token.Position{
   130  				Filename: fileName,
   131  				Line:     10,
   132  			},
   133  			FromLinter: "errcheck",
   134  		},
   135  		{
   136  			Pos: token.Position{
   137  				Filename: fileName,
   138  				Line:     13,
   139  			},
   140  			FromLinter: "errcheck",
   141  		},
   142  		{
   143  			Pos: token.Position{
   144  				Filename: fileName,
   145  				Line:     22,
   146  			},
   147  			FromLinter: "ineffassign",
   148  		},
   149  	}
   150  
   151  	log := getMockLog()
   152  	log.On("Warnf", "Found unknown linters in //nolint directives: %s", "bad1, bad2")
   153  
   154  	p := newTestNolintProcessor(log)
   155  	processAssertEmpty(t, p, issues...)
   156  	p.Finish()
   157  }
   158  
   159  func TestNolintInvalidLinterNameWithViolationOnTheSameLine(t *testing.T) {
   160  	log := getMockLog()
   161  	log.On("Warnf", "Found unknown linters in //nolint directives: %s", "foobar")
   162  	issues := []result.Issue{
   163  		{
   164  			Pos: token.Position{
   165  				Filename: filepath.Join("testdata", "nolint_apply_to_unknown.go"),
   166  				Line:     4,
   167  			},
   168  			FromLinter: "gofmt",
   169  		},
   170  	}
   171  
   172  	p := newTestNolintProcessor(log)
   173  	processedIssues, err := p.Process(issues)
   174  	p.Finish()
   175  
   176  	assert.Len(t, processedIssues, 1)
   177  	assert.Equal(t, issues, processedIssues)
   178  	assert.NoError(t, err)
   179  }
   180  
   181  func TestNolintAliases(t *testing.T) {
   182  	p := newTestNolintProcessor(getMockLog())
   183  	for _, line := range []int{47, 49, 51} {
   184  		line := line
   185  		t.Run(fmt.Sprintf("line-%d", line), func(t *testing.T) {
   186  			processAssertEmpty(t, p, newNolintFileIssue(line, "gosec"))
   187  		})
   188  	}
   189  	p.Finish()
   190  }
   191  
   192  func TestIgnoredRangeMatches(t *testing.T) {
   193  	var testcases = []struct {
   194  		doc      string
   195  		issue    result.Issue
   196  		linters  []string
   197  		expected bool
   198  	}{
   199  		{
   200  			doc: "unmatched line",
   201  			issue: result.Issue{
   202  				Pos: token.Position{
   203  					Line: 100,
   204  				},
   205  			},
   206  		},
   207  		{
   208  			doc: "matched line, all linters",
   209  			issue: result.Issue{
   210  				Pos: token.Position{
   211  					Line: 5,
   212  				},
   213  			},
   214  			expected: true,
   215  		},
   216  		{
   217  			doc: "matched line, unmatched linter",
   218  			issue: result.Issue{
   219  				Pos: token.Position{
   220  					Line: 5,
   221  				},
   222  			},
   223  			linters: []string{"vet"},
   224  		},
   225  		{
   226  			doc: "matched line and linters",
   227  			issue: result.Issue{
   228  				Pos: token.Position{
   229  					Line: 20,
   230  				},
   231  				FromLinter: "vet",
   232  			},
   233  			linters:  []string{"vet"},
   234  			expected: true,
   235  		},
   236  	}
   237  
   238  	for _, testcase := range testcases {
   239  		ir := ignoredRange{
   240  			col: 20,
   241  			Range: result.Range{
   242  				From: 5,
   243  				To:   20,
   244  			},
   245  			linters: testcase.linters,
   246  		}
   247  		assert.Equal(t, testcase.expected, ir.doesMatch(&testcase.issue), testcase.doc)
   248  	}
   249  }
   250  
   251  func TestNolintWholeFile(t *testing.T) {
   252  	fileName := filepath.Join("testdata", "nolint_whole_file.go")
   253  
   254  	p := newTestNolintProcessor(getMockLog())
   255  	defer p.Finish()
   256  
   257  	processAssertEmpty(t, p, result.Issue{
   258  		Pos: token.Position{
   259  			Filename: fileName,
   260  			Line:     9,
   261  		},
   262  		FromLinter: "errcheck",
   263  	})
   264  	processAssertSame(t, p, result.Issue{
   265  		Pos: token.Position{
   266  			Filename: fileName,
   267  			Line:     14,
   268  		},
   269  		FromLinter: "govet",
   270  	})
   271  }
   272  
   273  func TestNolintUnused(t *testing.T) {
   274  	fileName := filepath.Join("testdata", "nolint_unused.go")
   275  
   276  	log := getMockLog()
   277  	log.On("Warnf", "Found unknown linters in //nolint directives: %s", "blah")
   278  
   279  	createProcessor := func(t *testing.T, log *logutils.MockLog, enabledLinters []string) *Nolint {
   280  		enabledSetLog := logutils.NewMockLog()
   281  		enabledSetLog.On("Infof", "Active %d linters: %s", len(enabledLinters), enabledLinters)
   282  
   283  		cfg := &config.Config{Linters: config.Linters{DisableAll: true, Enable: enabledLinters}}
   284  		dbManager := lintersdb.NewManager(cfg, nil)
   285  
   286  		enabledLintersSet := lintersdb.NewEnabledSet(dbManager, lintersdb.NewValidator(dbManager), enabledSetLog, cfg)
   287  
   288  		enabledLintersMap, err := enabledLintersSet.GetEnabledLintersMap()
   289  		assert.NoError(t, err)
   290  
   291  		return NewNolint(log, dbManager, enabledLintersMap)
   292  	}
   293  
   294  	// the issue below is the nolintlint issue that would be generated for the test file
   295  	nolintlintIssueVarcheck := result.Issue{
   296  		Pos: token.Position{
   297  			Filename: fileName,
   298  			Line:     3,
   299  		},
   300  		FromLinter:           golinters.NoLintLintName,
   301  		ExpectNoLint:         true,
   302  		ExpectedNoLintLinter: "varcheck",
   303  	}
   304  
   305  	// the issue below is another nolintlint issue that would be generated for the test file
   306  	nolintlintIssueVarcheckUnusedOK := result.Issue{
   307  		Pos: token.Position{
   308  			Filename: fileName,
   309  			Line:     5,
   310  		},
   311  		FromLinter:           golinters.NoLintLintName,
   312  		ExpectNoLint:         true,
   313  		ExpectedNoLintLinter: "varcheck",
   314  	}
   315  
   316  	t.Run("when an issue does not occur, it is not removed from the nolintlint issues", func(t *testing.T) {
   317  		p := createProcessor(t, log, []string{"nolintlint", "varcheck"})
   318  		defer p.Finish()
   319  
   320  		processAssertSame(t, p, nolintlintIssueVarcheck)
   321  	})
   322  
   323  	t.Run("when an issue does not occur but nolintlint is nolinted, it is removed from the nolintlint issues", func(t *testing.T) {
   324  		p := createProcessor(t, log, []string{"nolintlint", "varcheck"})
   325  		defer p.Finish()
   326  
   327  		processAssertEmpty(t, p, nolintlintIssueVarcheckUnusedOK)
   328  	})
   329  
   330  	t.Run("when an issue occurs, it is removed from the nolintlint issues", func(t *testing.T) {
   331  		p := createProcessor(t, log, []string{"nolintlint", "varcheck"})
   332  		defer p.Finish()
   333  
   334  		processAssertEmpty(t, p, []result.Issue{{
   335  			Pos: token.Position{
   336  				Filename: fileName,
   337  				Line:     3,
   338  			},
   339  			FromLinter: "varcheck",
   340  		}, nolintlintIssueVarcheck}...)
   341  	})
   342  
   343  	t.Run("when a linter is not enabled, it is removed from the nolintlint unused issues", func(t *testing.T) {
   344  		enabledSetLog := logutils.NewMockLog()
   345  		enabledSetLog.On("Infof", "Active %d linters: %s", 1, []string{"nolintlint"})
   346  
   347  		cfg := &config.Config{Linters: config.Linters{DisableAll: true, Enable: []string{"nolintlint"}}}
   348  		dbManager := lintersdb.NewManager(cfg, nil)
   349  		enabledLintersSet := lintersdb.NewEnabledSet(dbManager, lintersdb.NewValidator(dbManager), enabledSetLog, cfg)
   350  
   351  		enabledLintersMap, err := enabledLintersSet.GetEnabledLintersMap()
   352  		assert.NoError(t, err)
   353  		p := NewNolint(log, dbManager, enabledLintersMap)
   354  		defer p.Finish()
   355  
   356  		processAssertEmpty(t, p, nolintlintIssueVarcheck)
   357  	})
   358  }