github.com/serversong/goreporter@v0.0.0-20200325104552-3cfaf44fd178/linters/simpler/lint/testutil/util.go (about)

     1  // Copyright (c) 2013 The Go Authors. All rights reserved.
     2  //
     3  // Use of this source code is governed by a BSD-style
     4  // license that can be found in the LICENSE file or at
     5  // https://developers.google.com/open-source/licenses/bsd.
     6  
     7  package testutil // import "github.com/360EntSecGroup-Skylar/goreporter/linters/simpler/lint/testutil"
     8  
     9  import (
    10  	"flag"
    11  	"fmt"
    12  	"go/parser"
    13  	"go/token"
    14  	"io/ioutil"
    15  	"os"
    16  	"path"
    17  	"path/filepath"
    18  	"regexp"
    19  	"strconv"
    20  	"strings"
    21  	"testing"
    22  
    23  	"github.com/360EntSecGroup-Skylar/goreporter/linters/simpler/lint"
    24  
    25  	"golang.org/x/tools/go/loader"
    26  )
    27  
    28  var lintMatch = flag.String("lint.match", "", "restrict testdata matches to this pattern")
    29  
    30  func TestAll(t *testing.T, c lint.Checker, dir string) {
    31  	baseDir := filepath.Join("testdata", dir)
    32  	fis, err := ioutil.ReadDir(baseDir)
    33  	if err != nil {
    34  		t.Fatalf("ioutil.ReadDir: %v", err)
    35  	}
    36  	if len(fis) == 0 {
    37  		t.Fatalf("no files in %v", baseDir)
    38  	}
    39  	rx, err := regexp.Compile(*lintMatch)
    40  	if err != nil {
    41  		t.Fatalf("Bad -lint.match value %q: %v", *lintMatch, err)
    42  	}
    43  
    44  	files := map[int][]os.FileInfo{}
    45  	for _, fi := range fis {
    46  		if !rx.MatchString(fi.Name()) {
    47  			continue
    48  		}
    49  		if !strings.HasSuffix(fi.Name(), ".go") {
    50  			continue
    51  		}
    52  		parts := strings.Split(fi.Name(), "_")
    53  		v := 0
    54  		if len(parts) > 1 && strings.HasPrefix(parts[len(parts)-1], "go1") {
    55  			var err error
    56  			s := parts[len(parts)-1][len("go1"):]
    57  			s = s[:len(s)-len(".go")]
    58  			v, err = strconv.Atoi(s)
    59  			if err != nil {
    60  				t.Fatalf("cannot process file name %q: %s", fi.Name(), err)
    61  			}
    62  		}
    63  		files[v] = append(files[v], fi)
    64  	}
    65  
    66  	conf := &loader.Config{
    67  		ParserMode: parser.ParseComments,
    68  	}
    69  	sources := map[string][]byte{}
    70  	for _, fi := range fis {
    71  		filename := path.Join(baseDir, fi.Name())
    72  		src, err := ioutil.ReadFile(filename)
    73  		if err != nil {
    74  			t.Errorf("Failed reading %s: %v", fi.Name(), err)
    75  			continue
    76  		}
    77  		f, err := conf.ParseFile(filename, src)
    78  		if err != nil {
    79  			t.Errorf("error parsing %s: %s", filename, err)
    80  			continue
    81  		}
    82  		sources[fi.Name()] = src
    83  		conf.CreateFromFiles(fi.Name(), f)
    84  	}
    85  
    86  	lprog, err := conf.Load()
    87  	if err != nil {
    88  		t.Fatalf("error loading program: %s", err)
    89  	}
    90  
    91  	for version, fis := range files {
    92  		l := &lint.Linter{Checker: c, GoVersion: version}
    93  
    94  		res := l.Lint(lprog)
    95  		for _, fi := range fis {
    96  			name := fi.Name()
    97  			src := sources[name]
    98  
    99  			ins := parseInstructions(t, name, src)
   100  
   101  			for _, in := range ins {
   102  				ok := false
   103  				for i, p := range res {
   104  					pos := lprog.Fset.Position(p.Position)
   105  					if pos.Line != in.Line || filepath.Base(pos.Filename) != name {
   106  						continue
   107  					}
   108  					if in.Match.MatchString(p.Text) {
   109  						// remove this problem from ps
   110  						copy(res[i:], res[i+1:])
   111  						res = res[:len(res)-1]
   112  
   113  						//t.Logf("/%v/ matched at %s:%d", in.Match, fi.Name(), in.Line)
   114  						ok = true
   115  						break
   116  					}
   117  				}
   118  				if !ok {
   119  					t.Errorf("Lint failed at %s:%d; /%v/ did not match", name, in.Line, in.Match)
   120  				}
   121  			}
   122  		}
   123  		for _, p := range res {
   124  			pos := lprog.Fset.Position(p.Position)
   125  			name := filepath.Base(pos.Filename)
   126  			for _, fi := range fis {
   127  				if name == fi.Name() {
   128  					t.Errorf("Unexpected problem at %s: %v", pos, p.Text)
   129  					break
   130  				}
   131  			}
   132  		}
   133  	}
   134  }
   135  
   136  type instruction struct {
   137  	Line        int            // the line number this applies to
   138  	Match       *regexp.Regexp // what pattern to match
   139  	Replacement string         // what the suggested replacement line should be
   140  }
   141  
   142  // parseInstructions parses instructions from the comments in a Go source file.
   143  // It returns nil if none were parsed.
   144  func parseInstructions(t *testing.T, filename string, src []byte) []instruction {
   145  	fset := token.NewFileSet()
   146  	f, err := parser.ParseFile(fset, filename, src, parser.ParseComments)
   147  	if err != nil {
   148  		t.Fatalf("Test file %v does not parse: %v", filename, err)
   149  	}
   150  	var ins []instruction
   151  	for _, cg := range f.Comments {
   152  		ln := fset.Position(cg.Pos()).Line
   153  		raw := cg.Text()
   154  		for _, line := range strings.Split(raw, "\n") {
   155  			if line == "" || strings.HasPrefix(line, "#") {
   156  				continue
   157  			}
   158  			if line == "OK" && ins == nil {
   159  				// so our return value will be non-nil
   160  				ins = make([]instruction, 0)
   161  				continue
   162  			}
   163  			if !strings.Contains(line, "MATCH") {
   164  				continue
   165  			}
   166  			rx, err := extractPattern(line)
   167  			if err != nil {
   168  				t.Fatalf("At %v:%d: %v", filename, ln, err)
   169  			}
   170  			matchLine := ln
   171  			if i := strings.Index(line, "MATCH:"); i >= 0 {
   172  				// This is a match for a different line.
   173  				lns := strings.TrimPrefix(line[i:], "MATCH:")
   174  				lns = lns[:strings.Index(lns, " ")]
   175  				matchLine, err = strconv.Atoi(lns)
   176  				if err != nil {
   177  					t.Fatalf("Bad match line number %q at %v:%d: %v", lns, filename, ln, err)
   178  				}
   179  			}
   180  			var repl string
   181  			if r, ok := extractReplacement(line); ok {
   182  				repl = r
   183  			}
   184  			ins = append(ins, instruction{
   185  				Line:        matchLine,
   186  				Match:       rx,
   187  				Replacement: repl,
   188  			})
   189  		}
   190  	}
   191  	return ins
   192  }
   193  
   194  func extractPattern(line string) (*regexp.Regexp, error) {
   195  	n := strings.Index(line, " ")
   196  	if n == 01 {
   197  		return nil, fmt.Errorf("malformed match instruction %q", line)
   198  	}
   199  	line = line[n+1:]
   200  	var pat string
   201  	switch line[0] {
   202  	case '/':
   203  		a, b := strings.Index(line, "/"), strings.LastIndex(line, "/")
   204  		if a == -1 || a == b {
   205  			return nil, fmt.Errorf("malformed match instruction %q", line)
   206  		}
   207  		pat = line[a+1 : b]
   208  	case '"':
   209  		a, b := strings.Index(line, `"`), strings.LastIndex(line, `"`)
   210  		if a == -1 || a == b {
   211  			return nil, fmt.Errorf("malformed match instruction %q", line)
   212  		}
   213  		pat = regexp.QuoteMeta(line[a+1 : b])
   214  	default:
   215  		return nil, fmt.Errorf("malformed match instruction %q", line)
   216  	}
   217  
   218  	rx, err := regexp.Compile(pat)
   219  	if err != nil {
   220  		return nil, fmt.Errorf("bad match pattern %q: %v", pat, err)
   221  	}
   222  	return rx, nil
   223  }
   224  
   225  func extractReplacement(line string) (string, bool) {
   226  	// Look for this:  / -> `
   227  	// (the end of a match and start of a backtick string),
   228  	// and then the closing backtick.
   229  	const start = "/ -> `"
   230  	a, b := strings.Index(line, start), strings.LastIndex(line, "`")
   231  	if a < 0 || a > b {
   232  		return "", false
   233  	}
   234  	return line[a+len(start) : b], true
   235  }