Verify linter name in integration tests (#1595)
This commit is contained in:
parent
257eb9523a
commit
ec46f42e01
@ -3,6 +3,7 @@ package printers
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
|
|
||||||
@ -52,7 +53,7 @@ func (p *Text) Print(ctx context.Context, issues []result.Issue) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p Text) printIssue(i *result.Issue) {
|
func (p Text) printIssue(i *result.Issue) {
|
||||||
text := p.SprintfColored(color.FgRed, "%s", i.Text)
|
text := p.SprintfColored(color.FgRed, "%s", strings.TrimSpace(i.Text))
|
||||||
if p.printLinterName {
|
if p.printLinterName {
|
||||||
text += fmt.Sprintf(" (%s)", i.FromLinter)
|
text += fmt.Sprintf(" (%s)", i.FromLinter)
|
||||||
}
|
}
|
||||||
|
113
test/errchk.go
113
test/errchk.go
@ -11,6 +11,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var errorLineRx = regexp.MustCompile(`^\S+?: (.*)\((\S+?)\)$`)
|
||||||
|
|
||||||
// errorCheck matches errors in outStr against comments in source files.
|
// errorCheck matches errors in outStr against comments in source files.
|
||||||
// For each line of the source files which should generate an error,
|
// For each line of the source files which should generate an error,
|
||||||
// there should be a comment of the form // ERROR "regexp".
|
// there should be a comment of the form // ERROR "regexp".
|
||||||
@ -22,8 +24,8 @@ import (
|
|||||||
//
|
//
|
||||||
// Sources files are supplied as fullshort slice.
|
// Sources files are supplied as fullshort slice.
|
||||||
// It consists of pairs: full path to source file and its base name.
|
// It consists of pairs: full path to source file and its base name.
|
||||||
//nolint:gocyclo
|
//nolint:gocyclo,funlen
|
||||||
func errorCheck(outStr string, wantAuto bool, fullshort ...string) (err error) {
|
func errorCheck(outStr string, wantAuto bool, defaultWantedLinter string, fullshort ...string) (err error) {
|
||||||
var errs []error
|
var errs []error
|
||||||
out := splitOutput(outStr, wantAuto)
|
out := splitOutput(outStr, wantAuto)
|
||||||
// Cut directory name.
|
// Cut directory name.
|
||||||
@ -37,9 +39,16 @@ func errorCheck(outStr string, wantAuto bool, fullshort ...string) (err error) {
|
|||||||
var want []wantedError
|
var want []wantedError
|
||||||
for j := 0; j < len(fullshort); j += 2 {
|
for j := 0; j < len(fullshort); j += 2 {
|
||||||
full, short := fullshort[j], fullshort[j+1]
|
full, short := fullshort[j], fullshort[j+1]
|
||||||
want = append(want, wantedErrors(full, short)...)
|
want = append(want, wantedErrors(full, short, defaultWantedLinter)...)
|
||||||
}
|
}
|
||||||
for _, we := range want {
|
for _, we := range want {
|
||||||
|
if we.linter == "" {
|
||||||
|
err := fmt.Errorf("%s:%d: no expected linter indicated for test",
|
||||||
|
we.file, we.lineNum)
|
||||||
|
errs = append(errs, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
var errmsgs []string
|
var errmsgs []string
|
||||||
if we.auto {
|
if we.auto {
|
||||||
errmsgs, out = partitionStrings("<autogenerated>", out)
|
errmsgs, out = partitionStrings("<autogenerated>", out)
|
||||||
@ -51,25 +60,35 @@ func errorCheck(outStr string, wantAuto bool, fullshort ...string) (err error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
matched := false
|
matched := false
|
||||||
n := len(out)
|
|
||||||
var textsToMatch []string
|
var textsToMatch []string
|
||||||
for _, errmsg := range errmsgs {
|
for _, errmsg := range errmsgs {
|
||||||
// Assume errmsg says "file:line: foo".
|
// Assume errmsg says "file:line: foo (<linter>)".
|
||||||
// Cut leading "file:line: " to avoid accidental matching of file name instead of message.
|
matches := errorLineRx.FindStringSubmatch(errmsg)
|
||||||
text := errmsg
|
if len(matches) == 0 {
|
||||||
if i := strings.Index(text, " "); i >= 0 {
|
err := fmt.Errorf("%s:%d: unexpected error line: %s",
|
||||||
text = text[i+1:]
|
we.file, we.lineNum, errmsg)
|
||||||
|
errs = append(errs, err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
text, actualLinter := matches[1], matches[2]
|
||||||
|
|
||||||
if we.re.MatchString(text) {
|
if we.re.MatchString(text) {
|
||||||
matched = true
|
matched = true
|
||||||
} else {
|
} else {
|
||||||
out = append(out, errmsg)
|
out = append(out, errmsg)
|
||||||
textsToMatch = append(textsToMatch, text)
|
textsToMatch = append(textsToMatch, text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if actualLinter != we.linter {
|
||||||
|
err := fmt.Errorf("%s:%d: expected error from %q but got error from %q in:\n\t%s",
|
||||||
|
we.file, we.lineNum, we.linter, actualLinter, strings.Join(out, "\n\t"))
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !matched {
|
if !matched {
|
||||||
err := fmt.Errorf("%s:%d: no match for %#q vs %q in:\n\t%s",
|
err := fmt.Errorf("%s:%d: no match for %#q vs %q in:\n\t%s",
|
||||||
we.file, we.lineNum, we.reStr, textsToMatch, strings.Join(out[n:], "\n\t"))
|
we.file, we.lineNum, we.reStr, textsToMatch, strings.Join(out, "\n\t"))
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -150,18 +169,18 @@ type wantedError struct {
|
|||||||
auto bool // match <autogenerated> line
|
auto bool // match <autogenerated> line
|
||||||
file string
|
file string
|
||||||
prefix string
|
prefix string
|
||||||
|
linter string
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errRx = regexp.MustCompile(`// (?:GC_)?ERROR (.*)`)
|
errRx = regexp.MustCompile(`// (?:GC_)?ERROR (.*)`)
|
||||||
errAutoRx = regexp.MustCompile(`// (?:GC_)?ERRORAUTO (.*)`)
|
errAutoRx = regexp.MustCompile(`// (?:GC_)?ERRORAUTO (.*)`)
|
||||||
errQuotesRx = regexp.MustCompile(`"([^"]*)"`)
|
linterPrefixRx = regexp.MustCompile("^\\s*([^\\s\"`]+)")
|
||||||
lineRx = regexp.MustCompile(`LINE(([+-])(\d+))?`)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// wantedErrors parses expected errors from comments in a file.
|
// wantedErrors parses expected errors from comments in a file.
|
||||||
//nolint:nakedret
|
//nolint:nakedret
|
||||||
func wantedErrors(file, short string) (errs []wantedError) {
|
func wantedErrors(file, short, defaultLinter string) (errs []wantedError) {
|
||||||
cache := make(map[string]*regexp.Regexp)
|
cache := make(map[string]*regexp.Regexp)
|
||||||
|
|
||||||
src, err := ioutil.ReadFile(file)
|
src, err := ioutil.ReadFile(file)
|
||||||
@ -184,47 +203,35 @@ func wantedErrors(file, short string) (errs []wantedError) {
|
|||||||
if m == nil {
|
if m == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
all := m[1]
|
rest := m[1]
|
||||||
mm := errQuotesRx.FindAllStringSubmatch(all, -1)
|
linter := defaultLinter
|
||||||
if mm == nil {
|
if lm := linterPrefixRx.FindStringSubmatch(rest); lm != nil {
|
||||||
log.Fatalf("%s:%d: invalid errchk line: %s", file, lineNum, line)
|
linter = lm[1]
|
||||||
|
rest = rest[len(lm[0]):]
|
||||||
}
|
}
|
||||||
for _, m := range mm {
|
rx, err := strconv.Unquote(strings.TrimSpace(rest))
|
||||||
replacedOnce := false
|
if err != nil {
|
||||||
rx := lineRx.ReplaceAllStringFunc(m[1], func(m string) string {
|
log.Fatalf("%s:%d: invalid errchk line: %s, %v", file, lineNum, line, err)
|
||||||
if replacedOnce {
|
}
|
||||||
return m
|
re := cache[rx]
|
||||||
}
|
if re == nil {
|
||||||
replacedOnce = true
|
var err error
|
||||||
n := lineNum
|
re, err = regexp.Compile(rx)
|
||||||
if strings.HasPrefix(m, "LINE+") {
|
if err != nil {
|
||||||
delta, _ := strconv.Atoi(m[5:])
|
log.Fatalf("%s:%d: invalid regexp \"%#q\" in ERROR line: %v", file, lineNum, rx, err)
|
||||||
n += delta
|
|
||||||
} else if strings.HasPrefix(m, "LINE-") {
|
|
||||||
delta, _ := strconv.Atoi(m[5:])
|
|
||||||
n -= delta
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s:%d", short, n)
|
|
||||||
})
|
|
||||||
re := cache[rx]
|
|
||||||
if re == nil {
|
|
||||||
var err error
|
|
||||||
re, err = regexp.Compile(rx)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("%s:%d: invalid regexp \"%#q\" in ERROR line: %v", file, lineNum, rx, err)
|
|
||||||
}
|
|
||||||
cache[rx] = re
|
|
||||||
}
|
}
|
||||||
prefix := fmt.Sprintf("%s:%d", short, lineNum)
|
cache[rx] = re
|
||||||
errs = append(errs, wantedError{
|
|
||||||
reStr: rx,
|
|
||||||
re: re,
|
|
||||||
prefix: prefix,
|
|
||||||
auto: auto,
|
|
||||||
lineNum: lineNum,
|
|
||||||
file: short,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
prefix := fmt.Sprintf("%s:%d", short, lineNum)
|
||||||
|
errs = append(errs, wantedError{
|
||||||
|
reStr: rx,
|
||||||
|
re: re,
|
||||||
|
prefix: prefix,
|
||||||
|
auto: auto,
|
||||||
|
lineNum: lineNum,
|
||||||
|
file: short,
|
||||||
|
linter: linter,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -15,7 +15,7 @@ import (
|
|||||||
"github.com/golangci/golangci-lint/test/testshared"
|
"github.com/golangci/golangci-lint/test/testshared"
|
||||||
)
|
)
|
||||||
|
|
||||||
func runGoErrchk(c *exec.Cmd, files []string, t *testing.T) {
|
func runGoErrchk(c *exec.Cmd, defaultExpectedLinter string, files []string, t *testing.T) {
|
||||||
output, err := c.CombinedOutput()
|
output, err := c.CombinedOutput()
|
||||||
// The returned error will be nil if the test file does not have any issues
|
// The returned error will be nil if the test file does not have any issues
|
||||||
// and thus the linter exits with exit code 0. So perform the additional
|
// and thus the linter exits with exit code 0. So perform the additional
|
||||||
@ -33,7 +33,7 @@ func runGoErrchk(c *exec.Cmd, files []string, t *testing.T) {
|
|||||||
fullshort = append(fullshort, f, filepath.Base(f))
|
fullshort = append(fullshort, f, filepath.Base(f))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = errorCheck(string(output), false, fullshort...)
|
err = errorCheck(string(output), false, defaultExpectedLinter, fullshort...)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +124,6 @@ func testOneSource(t *testing.T, sourcePath string) {
|
|||||||
"--allow-parallel-runners",
|
"--allow-parallel-runners",
|
||||||
"--disable-all",
|
"--disable-all",
|
||||||
"--print-issued-lines=false",
|
"--print-issued-lines=false",
|
||||||
"--print-linter-name=false",
|
|
||||||
"--out-format=line-number",
|
"--out-format=line-number",
|
||||||
"--max-same-issues=100",
|
"--max-same-issues=100",
|
||||||
}
|
}
|
||||||
@ -156,14 +155,15 @@ func testOneSource(t *testing.T, sourcePath string) {
|
|||||||
|
|
||||||
cmd := exec.Command(binName, caseArgs...)
|
cmd := exec.Command(binName, caseArgs...)
|
||||||
t.Log(caseArgs)
|
t.Log(caseArgs)
|
||||||
runGoErrchk(cmd, []string{sourcePath}, t)
|
runGoErrchk(cmd, rc.expectedLinter, []string{sourcePath}, t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type runContext struct {
|
type runContext struct {
|
||||||
args []string
|
args []string
|
||||||
config map[string]interface{}
|
config map[string]interface{}
|
||||||
configPath string
|
configPath string
|
||||||
|
expectedLinter string
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildConfigFromShortRepr(t *testing.T, repr string, config map[string]interface{}) {
|
func buildConfigFromShortRepr(t *testing.T, repr string, config map[string]interface{}) {
|
||||||
@ -213,7 +213,7 @@ func extractRunContextFromComments(t *testing.T, sourcePath string) *runContext
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(line, "//") {
|
if !strings.HasPrefix(line, "//") {
|
||||||
return rc
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
line = strings.TrimLeft(strings.TrimPrefix(line, "//"), " ")
|
line = strings.TrimLeft(strings.TrimPrefix(line, "//"), " ")
|
||||||
@ -242,9 +242,29 @@ func extractRunContextFromComments(t *testing.T, sourcePath string) *runContext
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(line, "expected_linter: ") {
|
||||||
|
expectedLinter := strings.TrimPrefix(line, "expected_linter: ")
|
||||||
|
assert.NotEmpty(t, expectedLinter)
|
||||||
|
rc.expectedLinter = expectedLinter
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
assert.Fail(t, "invalid prefix of comment line %s", line)
|
assert.Fail(t, "invalid prefix of comment line %s", line)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// guess the expected linter if none is specified
|
||||||
|
if rc.expectedLinter == "" {
|
||||||
|
for _, arg := range rc.args {
|
||||||
|
if strings.HasPrefix(arg, "-E") && !strings.Contains(arg, ",") {
|
||||||
|
if rc.expectedLinter != "" {
|
||||||
|
assert.Fail(t, "could not infer expected linter for errors because multiple linters are enabled. Please use the `expected_linter: ` directive in your test to indicate the linter-under-test.") //nolint:lll
|
||||||
|
break
|
||||||
|
}
|
||||||
|
rc.expectedLinter = arg[2:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return rc
|
return rc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
test/testdata/asciicheck.go
vendored
2
test/testdata/asciicheck.go
vendored
@ -1,4 +1,4 @@
|
|||||||
//args: -Easciicheck
|
//args: -Easciicheck
|
||||||
package testdata
|
package testdata
|
||||||
|
|
||||||
type TеstStruct struct{} // ERROR `identifier "TеstStruct" contain non-ASCII character: U+0435 'е'`
|
type TеstStruct struct{} // ERROR `identifier "TеstStruct" contain non-ASCII character: U\+0435 'е'`
|
||||||
|
4
test/testdata/default_exclude.go
vendored
4
test/testdata/default_exclude.go
vendored
@ -4,13 +4,13 @@
|
|||||||
/*Package testdata ...*/
|
/*Package testdata ...*/
|
||||||
package testdata
|
package testdata
|
||||||
|
|
||||||
// InvalidFuncComment, both golint and stylecheck will complain about this, // ERROR `ST1020: comment on exported function ExportedFunc1 should be of the form "ExportedFunc1 ..."`
|
// InvalidFuncComment, both golint and stylecheck will complain about this, // ERROR stylecheck `ST1020: comment on exported function ExportedFunc1 should be of the form "ExportedFunc1 ..."`
|
||||||
// if include EXC0011, only the warning from golint will be ignored.
|
// if include EXC0011, only the warning from golint will be ignored.
|
||||||
// And only the warning from stylecheck will start with "ST1020".
|
// And only the warning from stylecheck will start with "ST1020".
|
||||||
func ExportedFunc1() {
|
func ExportedFunc1() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// InvalidFuncComment // ERROR `ST1020: comment on exported function ExportedFunc2 should be of the form "ExportedFunc2 ..."`
|
// InvalidFuncComment // ERROR stylecheck `ST1020: comment on exported function ExportedFunc2 should be of the form "ExportedFunc2 ..."`
|
||||||
// nolint:golint
|
// nolint:golint
|
||||||
func ExportedFunc2() {
|
func ExportedFunc2() {
|
||||||
}
|
}
|
||||||
|
8
test/testdata/exportloopref.go
vendored
8
test/testdata/exportloopref.go
vendored
@ -10,11 +10,11 @@ func dummyFunction() {
|
|||||||
println("loop expecting 10, 11, 12, 13")
|
println("loop expecting 10, 11, 12, 13")
|
||||||
for i, p := range []int{10, 11, 12, 13} {
|
for i, p := range []int{10, 11, 12, 13} {
|
||||||
printp(&p)
|
printp(&p)
|
||||||
slice = append(slice, &p) // ERROR : "exporting a pointer for the loop variable p"
|
slice = append(slice, &p) // ERROR "exporting a pointer for the loop variable p"
|
||||||
array[i] = &p // ERROR : "exporting a pointer for the loop variable p"
|
array[i] = &p // ERROR "exporting a pointer for the loop variable p"
|
||||||
if i%2 == 0 {
|
if i%2 == 0 {
|
||||||
ref = &p // ERROR : "exporting a pointer for the loop variable p"
|
ref = &p // ERROR "exporting a pointer for the loop variable p"
|
||||||
str.x = &p // ERROR : "exporting a pointer for the loop variable p"
|
str.x = &p // ERROR "exporting a pointer for the loop variable p"
|
||||||
}
|
}
|
||||||
var vStr struct{ x *int }
|
var vStr struct{ x *int }
|
||||||
var vArray [4]*int
|
var vArray [4]*int
|
||||||
|
2
test/testdata/forbidigo.go
vendored
2
test/testdata/forbidigo.go
vendored
@ -5,5 +5,5 @@ package testdata
|
|||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
func Forbidigo() {
|
func Forbidigo() {
|
||||||
fmt.Printf("too noisy!!!") // ERROR "use of `fmt.Printf` forbidden by pattern `fmt\\.Print.*`"
|
fmt.Printf("too noisy!!!") // ERROR "use of `fmt\\.Printf` forbidden by pattern `fmt\\\\.Print\\.\\*`"
|
||||||
}
|
}
|
||||||
|
4
test/testdata/funlen.go
vendored
4
test/testdata/funlen.go
vendored
@ -3,7 +3,7 @@
|
|||||||
//config: linters-settings.funlen.statements=10
|
//config: linters-settings.funlen.statements=10
|
||||||
package testdata
|
package testdata
|
||||||
|
|
||||||
func TooManyLines() { // ERROR "Function 'TooManyLines' is too long \(22 > 20\)"
|
func TooManyLines() { // ERROR `Function 'TooManyLines' is too long \(22 > 20\)`
|
||||||
t := struct {
|
t := struct {
|
||||||
A string
|
A string
|
||||||
B string
|
B string
|
||||||
@ -28,7 +28,7 @@ func TooManyLines() { // ERROR "Function 'TooManyLines' is too long \(22 > 20\)"
|
|||||||
_ = t
|
_ = t
|
||||||
}
|
}
|
||||||
|
|
||||||
func TooManyStatements() { // ERROR "Function 'TooManyStatements' has too many statements \(11 > 10\)"
|
func TooManyStatements() { // ERROR `Function 'TooManyStatements' has too many statements \(11 > 10\)`
|
||||||
a := 1
|
a := 1
|
||||||
b := a
|
b := a
|
||||||
c := b
|
c := b
|
||||||
|
2
test/testdata/go-header_bad.go
vendored
2
test/testdata/go-header_bad.go
vendored
@ -1,4 +1,4 @@
|
|||||||
/*MY TITLE!*/ // ERROR "Expected:TITLE., Actual: TITLE!"
|
/*MY TITLE!*/ // ERROR `Expected:TITLE\., Actual: TITLE!`
|
||||||
|
|
||||||
//args: -Egoheader
|
//args: -Egoheader
|
||||||
//config_path: testdata/configs/go-header.yml
|
//config_path: testdata/configs/go-header.yml
|
||||||
|
2
test/testdata/gocritic.go
vendored
2
test/testdata/gocritic.go
vendored
@ -7,7 +7,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = *flag.Bool("global1", false, "") // ERROR "flagDeref: immediate deref in \*flag.Bool\(.global1., false, ..\) is most likely an error; consider using flag\.BoolVar"
|
var _ = *flag.Bool("global1", false, "") // ERROR `flagDeref: immediate deref in \*flag.Bool\(.global1., false, ..\) is most likely an error; consider using flag\.BoolVar`
|
||||||
|
|
||||||
type size1 struct {
|
type size1 struct {
|
||||||
a bool
|
a bool
|
||||||
|
14
test/testdata/godox.go
vendored
14
test/testdata/godox.go
vendored
@ -3,11 +3,11 @@
|
|||||||
package testdata
|
package testdata
|
||||||
|
|
||||||
func todoLeftInCode() {
|
func todoLeftInCode() {
|
||||||
// TODO implement me // ERROR godox.go:6: Line contains FIXME/TODO: "TODO implement me"
|
// TODO implement me // ERROR `Line contains FIXME/TODO: "TODO implement me`
|
||||||
//TODO no space // ERROR godox.go:7: Line contains FIXME/TODO: "TODO no space"
|
//TODO no space // ERROR `Line contains FIXME/TODO: "TODO no space`
|
||||||
// TODO(author): 123 // ERROR godox.go:8: Line contains FIXME/TODO: "TODO\(author\): 123 // ERROR godox.go:8: L..."
|
// TODO(author): 123 // ERROR `Line contains FIXME/TODO: "TODO\(author\): 123`
|
||||||
//TODO(author): 123 // ERROR godox.go:9: Line contains FIXME/TODO: "TODO\(author\): 123 // ERROR godox.go:9: L..."
|
//TODO(author): 123 // ERROR `Line contains FIXME/TODO: "TODO\(author\): 123`
|
||||||
//TODO(author) 456 // ERROR godox.go:10: Line contains FIXME/TODO: "TODO\(author\) 456 // ERROR godox.go:10: L..."
|
//TODO(author) 456 // ERROR `Line contains FIXME/TODO: "TODO\(author\) 456`
|
||||||
// TODO: qwerty // ERROR godox.go:11: Line contains FIXME/TODO: "TODO: qwerty // ERROR godox.go:11: Line ..."
|
// TODO: qwerty // ERROR `Line contains FIXME/TODO: "TODO: qwerty`
|
||||||
// todo 789 // ERROR godox.go:12: Line contains FIXME/TODO: "todo 789"
|
// todo 789 // ERROR `Line contains FIXME/TODO: "todo 789`
|
||||||
}
|
}
|
||||||
|
6
test/testdata/goerr113.go
vendored
6
test/testdata/goerr113.go
vendored
@ -4,17 +4,17 @@ package testdata
|
|||||||
import "os"
|
import "os"
|
||||||
|
|
||||||
func SimpleEqual(e1, e2 error) bool {
|
func SimpleEqual(e1, e2 error) bool {
|
||||||
return e1 == e2 // ERROR `err113: do not compare errors directly, use errors.Is() instead: "e1 == e2"`
|
return e1 == e2 // ERROR `err113: do not compare errors directly, use errors.Is\(\) instead: "e1 == e2"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SimpleNotEqual(e1, e2 error) bool {
|
func SimpleNotEqual(e1, e2 error) bool {
|
||||||
return e1 != e2 // ERROR `err113: do not compare errors directly, use errors.Is() instead: "e1 != e2"`
|
return e1 != e2 // ERROR `err113: do not compare errors directly, use errors.Is\(\) instead: "e1 != e2"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckGoerr13Import(e error) bool {
|
func CheckGoerr13Import(e error) bool {
|
||||||
f, err := os.Create("f.txt")
|
f, err := os.Create("f.txt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err == e // ERROR `err113: do not compare errors directly, use errors.Is() instead: "err == e"`
|
return err == e // ERROR `err113: do not compare errors directly, use errors.Is\(\) instead: "err == e"`
|
||||||
}
|
}
|
||||||
f.Close()
|
f.Close()
|
||||||
return false
|
return false
|
||||||
|
2
test/testdata/gofmt_no_simplify.go
vendored
2
test/testdata/gofmt_no_simplify.go
vendored
@ -9,5 +9,5 @@ func GofmtNotSimplifiedOk() {
|
|||||||
fmt.Print(x[1:len(x)])
|
fmt.Print(x[1:len(x)])
|
||||||
}
|
}
|
||||||
|
|
||||||
func GofmtBadFormat(){ // ERROR "^File is not `gofmt`-ed$"
|
func GofmtBadFormat(){ // ERROR "^File is not `gofmt`-ed"
|
||||||
}
|
}
|
||||||
|
4
test/testdata/gomnd.go
vendored
4
test/testdata/gomnd.go
vendored
@ -9,14 +9,14 @@ import (
|
|||||||
|
|
||||||
func UseMagicNumber() {
|
func UseMagicNumber() {
|
||||||
c := &http.Client{
|
c := &http.Client{
|
||||||
Timeout: 2 * time.Second, // ERROR : "Magic number: 2, in <assign> detected"
|
Timeout: 2 * time.Second, // ERROR "Magic number: 2, in <assign> detected"
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := c.Get("http://www.google.com")
|
res, err := c.Get("http://www.google.com")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
if res.StatusCode != 200 { // ERROR : "Magic number: 200, in <condition> detected"
|
if res.StatusCode != 200 { // ERROR "Magic number: 200, in <condition> detected"
|
||||||
log.Println("Something went wrong")
|
log.Println("Something went wrong")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2
test/testdata/gomodguard.go
vendored
2
test/testdata/gomodguard.go
vendored
@ -6,7 +6,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
|
|
||||||
"golang.org/x/mod/modfile"
|
"golang.org/x/mod/modfile"
|
||||||
"gopkg.in/yaml.v2" // ERROR : "import of package `gopkg.in/yaml.v2` is blocked because the module is in the blocked modules list. `github.com/kylelemons/go-gypsy` is a recommended module. This is an example of recommendations."
|
"gopkg.in/yaml.v2" // ERROR "import of package `gopkg.in/yaml.v2` is blocked because the module is in the blocked modules list. `github.com/kylelemons/go-gypsy` is a recommended module. This is an example of recommendations."
|
||||||
)
|
)
|
||||||
|
|
||||||
// Something just some struct
|
// Something just some struct
|
||||||
|
2
test/testdata/govet.go
vendored
2
test/testdata/govet.go
vendored
@ -14,7 +14,7 @@ func Govet() error {
|
|||||||
|
|
||||||
func GovetShadow(f io.Reader, buf []byte) (err error) {
|
func GovetShadow(f io.Reader, buf []byte) (err error) {
|
||||||
if f != nil {
|
if f != nil {
|
||||||
_, err := f.Read(buf) // ERROR "shadow: declaration of .err. shadows declaration at line \d+"
|
_, err := f.Read(buf) // ERROR `shadow: declaration of .err. shadows declaration at line \d+`
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
10
test/testdata/nestif.go
vendored
10
test/testdata/nestif.go
vendored
@ -5,19 +5,19 @@ package testdata
|
|||||||
func _() {
|
func _() {
|
||||||
var b1, b2, b3, b4 bool
|
var b1, b2, b3, b4 bool
|
||||||
|
|
||||||
if b1 { // ERROR "`if b1` is deeply nested \(complexity: 1\)"
|
if b1 { // ERROR "`if b1` is deeply nested \\(complexity: 1\\)"
|
||||||
if b2 { // +1
|
if b2 { // +1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if b1 { // ERROR "`if b1` is deeply nested \(complexity: 3\)"
|
if b1 { // ERROR "`if b1` is deeply nested \\(complexity: 3\\)"
|
||||||
if b2 { // +1
|
if b2 { // +1
|
||||||
if b3 { // +2
|
if b3 { // +2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if b1 { // ERROR "`if b1` is deeply nested \(complexity: 5\)"
|
if b1 { // ERROR "`if b1` is deeply nested \\(complexity: 5\\)"
|
||||||
if b2 { // +1
|
if b2 { // +1
|
||||||
} else if b3 { // +1
|
} else if b3 { // +1
|
||||||
if b4 { // +2
|
if b4 { // +2
|
||||||
@ -26,7 +26,7 @@ func _() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if b1 { // ERROR "`if b1` is deeply nested \(complexity: 9\)"
|
if b1 { // ERROR "`if b1` is deeply nested \\(complexity: 9\\)"
|
||||||
if b2 { // +1
|
if b2 { // +1
|
||||||
if b3 { // +2
|
if b3 { // +2
|
||||||
}
|
}
|
||||||
@ -40,7 +40,7 @@ func _() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if b1 == b2 == b3 { // ERROR "`if b1 == b2 == b3` is deeply nested \(complexity: 1\)"
|
if b1 == b2 == b3 { // ERROR "`if b1 == b2 == b3` is deeply nested \\(complexity: 1\\)"
|
||||||
if b4 { // +1
|
if b4 { // +1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
48
test/testdata/noctx.go
vendored
48
test/testdata/noctx.go
vendored
@ -13,25 +13,25 @@ func Noctx() {
|
|||||||
cli := &http.Client{}
|
cli := &http.Client{}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
http.Get(url) // ERROR "net/http\.Get must not be called"
|
http.Get(url) // ERROR `net/http\.Get must not be called`
|
||||||
_ = http.Get // OK
|
_ = http.Get // OK
|
||||||
f := http.Get // OK
|
f := http.Get // OK
|
||||||
f(url) // ERROR "net/http\.Get must not be called"
|
f(url) // ERROR `net/http\.Get must not be called`
|
||||||
|
|
||||||
http.Head(url) // ERROR "net/http\.Head must not be called"
|
http.Head(url) // ERROR `net/http\.Head must not be called`
|
||||||
http.Post(url, "", nil) // ERROR "net/http\.Post must not be called"
|
http.Post(url, "", nil) // ERROR `net/http\.Post must not be called`
|
||||||
http.PostForm(url, nil) // ERROR "net/http\.PostForm must not be called"
|
http.PostForm(url, nil) // ERROR `net/http\.PostForm must not be called`
|
||||||
|
|
||||||
cli.Get(url) // ERROR "\(\*net/http\.Client\)\.Get must not be called"
|
cli.Get(url) // ERROR `\(\*net/http\.Client\)\.Get must not be called`
|
||||||
_ = cli.Get // OK
|
_ = cli.Get // OK
|
||||||
m := cli.Get // OK
|
m := cli.Get // OK
|
||||||
m(url) // ERROR "\(\*net/http\.Client\)\.Get must not be called"
|
m(url) // ERROR `\(\*net/http\.Client\)\.Get must not be called`
|
||||||
|
|
||||||
cli.Head(url) // ERROR "\(\*net/http\.Client\)\.Head must not be called"
|
cli.Head(url) // ERROR `\(\*net/http\.Client\)\.Head must not be called`
|
||||||
cli.Post(url, "", nil) // ERROR "\(\*net/http\.Client\)\.Post must not be called"
|
cli.Post(url, "", nil) // ERROR `\(\*net/http\.Client\)\.Post must not be called`
|
||||||
cli.PostForm(url, nil) // ERROR "\(\*net/http\.Client\)\.PostForm must not be called"
|
cli.PostForm(url, nil) // ERROR `\(\*net/http\.Client\)\.PostForm must not be called`
|
||||||
|
|
||||||
req, _ := http.NewRequest(http.MethodPost, url, nil) // ERROR "should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext"
|
req, _ := http.NewRequest(http.MethodPost, url, nil) // ERROR `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext`
|
||||||
cli.Do(req)
|
cli.Do(req)
|
||||||
|
|
||||||
req2, _ := http.NewRequestWithContext(ctx, http.MethodPost, url, nil) // OK
|
req2, _ := http.NewRequestWithContext(ctx, http.MethodPost, url, nil) // OK
|
||||||
@ -44,7 +44,7 @@ func Noctx() {
|
|||||||
f2 := func(req *http.Request, ctx context.Context) *http.Request {
|
f2 := func(req *http.Request, ctx context.Context) *http.Request {
|
||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
req4, _ := http.NewRequest(http.MethodPost, url, nil) // ERROR "should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext"
|
req4, _ := http.NewRequest(http.MethodPost, url, nil) // ERROR `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext`
|
||||||
req4 = f2(req4, ctx)
|
req4 = f2(req4, ctx)
|
||||||
|
|
||||||
req41, _ := http.NewRequest(http.MethodPost, url, nil) // OK
|
req41, _ := http.NewRequest(http.MethodPost, url, nil) // OK
|
||||||
@ -52,21 +52,21 @@ func Noctx() {
|
|||||||
req41 = f2(req41, ctx)
|
req41 = f2(req41, ctx)
|
||||||
|
|
||||||
newRequest := http.NewRequest
|
newRequest := http.NewRequest
|
||||||
req5, _ := newRequest(http.MethodPost, url, nil) // ERROR "should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext"
|
req5, _ := newRequest(http.MethodPost, url, nil) // ERROR `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext`
|
||||||
cli.Do(req5)
|
cli.Do(req5)
|
||||||
|
|
||||||
req51, _ := newRequest(http.MethodPost, url, nil) // OK
|
req51, _ := newRequest(http.MethodPost, url, nil) // OK
|
||||||
req51 = req51.WithContext(ctx)
|
req51 = req51.WithContext(ctx)
|
||||||
cli.Do(req51)
|
cli.Do(req51)
|
||||||
|
|
||||||
req52, _ := newRequestPkg(http.MethodPost, url, nil) // ERROR "should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext"
|
req52, _ := newRequestPkg(http.MethodPost, url, nil) // ERROR `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext`
|
||||||
cli.Do(req52)
|
cli.Do(req52)
|
||||||
|
|
||||||
type MyRequest = http.Request
|
type MyRequest = http.Request
|
||||||
f3 := func(req *MyRequest, ctx context.Context) *MyRequest {
|
f3 := func(req *MyRequest, ctx context.Context) *MyRequest {
|
||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
req6, _ := http.NewRequest(http.MethodPost, url, nil) // ERROR "should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext"
|
req6, _ := http.NewRequest(http.MethodPost, url, nil) // ERROR `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext`
|
||||||
req6 = f3(req6, ctx)
|
req6 = f3(req6, ctx)
|
||||||
|
|
||||||
req61, _ := http.NewRequest(http.MethodPost, url, nil) // OK
|
req61, _ := http.NewRequest(http.MethodPost, url, nil) // OK
|
||||||
@ -77,7 +77,7 @@ func Noctx() {
|
|||||||
f4 := func(req *MyRequest2, ctx context.Context) *MyRequest2 {
|
f4 := func(req *MyRequest2, ctx context.Context) *MyRequest2 {
|
||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
req7, _ := http.NewRequest(http.MethodPost, url, nil) // ERROR "should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext"
|
req7, _ := http.NewRequest(http.MethodPost, url, nil) // ERROR `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext`
|
||||||
req71 := MyRequest2(*req7)
|
req71 := MyRequest2(*req7)
|
||||||
f4(&req71, ctx)
|
f4(&req71, ctx)
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ func Noctx() {
|
|||||||
f4(&req73, ctx)
|
f4(&req73, ctx)
|
||||||
|
|
||||||
req8, _ := func() (*http.Request, error) {
|
req8, _ := func() (*http.Request, error) {
|
||||||
return http.NewRequest(http.MethodPost, url, nil) // ERROR "should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext"
|
return http.NewRequest(http.MethodPost, url, nil) // ERROR `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext`
|
||||||
}()
|
}()
|
||||||
cli.Do(req8)
|
cli.Do(req8)
|
||||||
|
|
||||||
@ -101,30 +101,30 @@ func Noctx() {
|
|||||||
f5 := func(req, req2 *http.Request, ctx context.Context) (*http.Request, *http.Request) {
|
f5 := func(req, req2 *http.Request, ctx context.Context) (*http.Request, *http.Request) {
|
||||||
return req, req2
|
return req, req2
|
||||||
}
|
}
|
||||||
req9, _ := http.NewRequest(http.MethodPost, url, nil) // ERROR "should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext"
|
req9, _ := http.NewRequest(http.MethodPost, url, nil) // ERROR `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext`
|
||||||
req9, _ = f5(req9, req9, ctx)
|
req9, _ = f5(req9, req9, ctx)
|
||||||
|
|
||||||
req91, _ := http.NewRequest(http.MethodPost, url, nil) // OK
|
req91, _ := http.NewRequest(http.MethodPost, url, nil) // OK
|
||||||
req91 = req91.WithContext(ctx)
|
req91 = req91.WithContext(ctx)
|
||||||
req9, _ = f5(req91, req91, ctx)
|
req9, _ = f5(req91, req91, ctx)
|
||||||
|
|
||||||
req10, _ := http.NewRequest(http.MethodPost, url, nil) // ERROR "should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext"
|
req10, _ := http.NewRequest(http.MethodPost, url, nil) // ERROR `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext`
|
||||||
req11, _ := http.NewRequest(http.MethodPost, url, nil) // ERROR "should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext"
|
req11, _ := http.NewRequest(http.MethodPost, url, nil) // ERROR `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext`
|
||||||
req10, req11 = f5(req10, req11, ctx)
|
req10, req11 = f5(req10, req11, ctx)
|
||||||
|
|
||||||
req101, _ := http.NewRequest(http.MethodPost, url, nil) // ERROR "should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext"
|
req101, _ := http.NewRequest(http.MethodPost, url, nil) // ERROR `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext`
|
||||||
req111, _ := http.NewRequest(http.MethodPost, url, nil) // OK
|
req111, _ := http.NewRequest(http.MethodPost, url, nil) // OK
|
||||||
req111 = req111.WithContext(ctx)
|
req111 = req111.WithContext(ctx)
|
||||||
req101, req111 = f5(req101, req111, ctx)
|
req101, req111 = f5(req101, req111, ctx)
|
||||||
|
|
||||||
func() (*http.Request, *http.Request) {
|
func() (*http.Request, *http.Request) {
|
||||||
req12, _ := http.NewRequest(http.MethodPost, url, nil) // ERROR "should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext"
|
req12, _ := http.NewRequest(http.MethodPost, url, nil) // ERROR `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext`
|
||||||
req13, _ := http.NewRequest(http.MethodPost, url, nil) // ERROR "should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext"
|
req13, _ := http.NewRequest(http.MethodPost, url, nil) // ERROR `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext`
|
||||||
return req12, req13
|
return req12, req13
|
||||||
}()
|
}()
|
||||||
|
|
||||||
func() (*http.Request, *http.Request) {
|
func() (*http.Request, *http.Request) {
|
||||||
req14, _ := http.NewRequest(http.MethodPost, url, nil) // ERROR "should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext"
|
req14, _ := http.NewRequest(http.MethodPost, url, nil) // ERROR `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext`
|
||||||
req15, _ := http.NewRequest(http.MethodPost, url, nil) // OK
|
req15, _ := http.NewRequest(http.MethodPost, url, nil) // OK
|
||||||
req15 = req15.WithContext(ctx)
|
req15 = req15.WithContext(ctx)
|
||||||
|
|
||||||
|
1
test/testdata/nolintlint_unused.go
vendored
1
test/testdata/nolintlint_unused.go
vendored
@ -1,5 +1,6 @@
|
|||||||
//args: -Enolintlint -Evarcheck
|
//args: -Enolintlint -Evarcheck
|
||||||
//config: linters-settings.nolintlint.allow-unused=false
|
//config: linters-settings.nolintlint.allow-unused=false
|
||||||
|
//expected_linter: nolintlint
|
||||||
package testdata
|
package testdata
|
||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
8
test/testdata/staticcheck_in_megacheck.go
vendored
8
test/testdata/staticcheck_in_megacheck.go
vendored
@ -5,7 +5,7 @@ import "fmt"
|
|||||||
|
|
||||||
func StaticcheckInMegacheck() {
|
func StaticcheckInMegacheck() {
|
||||||
var x int
|
var x int
|
||||||
x = x // ERROR "self-assignment of x to x"
|
x = x // ERROR staticcheck "self-assignment of x to x"
|
||||||
fmt.Printf("%d", x)
|
fmt.Printf("%d", x)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,3 +18,9 @@ func StaticcheckNolintMegacheckInMegacheck() {
|
|||||||
var x int
|
var x int
|
||||||
x = x //nolint:megacheck
|
x = x //nolint:megacheck
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Staticcheck2() {
|
||||||
|
var x int
|
||||||
|
x = x // ERROR staticcheck "self-assignment of x to x"
|
||||||
|
fmt.Printf("%d", x)
|
||||||
|
}
|
||||||
|
8
test/testdata/stylecheck_not_in_megacheck.go
vendored
8
test/testdata/stylecheck_not_in_megacheck.go
vendored
@ -1,16 +1,8 @@
|
|||||||
//args: -Emegacheck
|
//args: -Emegacheck
|
||||||
package testdata
|
package testdata
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
func StylecheckNotInMegacheck(x int) {
|
func StylecheckNotInMegacheck(x int) {
|
||||||
if 0 == x {
|
if 0 == x {
|
||||||
panic(x)
|
panic(x)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Staticcheck2() {
|
|
||||||
var x int
|
|
||||||
x = x // ERROR "self-assignment of x to x"
|
|
||||||
fmt.Printf("%d", x)
|
|
||||||
}
|
|
||||||
|
8
test/testdata/thelper.go
vendored
8
test/testdata/thelper.go
vendored
@ -8,11 +8,11 @@ func thelperWithHelperAfterAssignment(t *testing.T) { // ERROR "test helper func
|
|||||||
t.Helper()
|
t.Helper()
|
||||||
}
|
}
|
||||||
|
|
||||||
func thelperWithNotFirst(s string, t *testing.T, i int) { // ERROR "parameter \*testing.T should be the first"
|
func thelperWithNotFirst(s string, t *testing.T, i int) { // ERROR `parameter \*testing.T should be the first`
|
||||||
t.Helper()
|
t.Helper()
|
||||||
}
|
}
|
||||||
|
|
||||||
func thelperWithIncorrectName(o *testing.T) { // ERROR "parameter \*testing.T should have name t"
|
func thelperWithIncorrectName(o *testing.T) { // ERROR `parameter \*testing.T should have name t`
|
||||||
o.Helper()
|
o.Helper()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,11 +21,11 @@ func bhelperWithHelperAfterAssignment(b *testing.B) { // ERROR "test helper func
|
|||||||
b.Helper()
|
b.Helper()
|
||||||
}
|
}
|
||||||
|
|
||||||
func bhelperWithNotFirst(s string, b *testing.B, i int) { // ERROR "parameter \*testing.B should be the first"
|
func bhelperWithNotFirst(s string, b *testing.B, i int) { // ERROR `parameter \*testing.B should be the first`
|
||||||
b.Helper()
|
b.Helper()
|
||||||
}
|
}
|
||||||
|
|
||||||
func bhelperWithIncorrectName(o *testing.B) { // ERROR "parameter \*testing.B should have name b"
|
func bhelperWithIncorrectName(o *testing.B) { // ERROR `parameter \*testing.B should have name b`
|
||||||
o.Helper()
|
o.Helper()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user