adding support for arbitrary paths with ...

This commit is contained in:
David Lawrence 2016-12-02 10:40:36 -08:00
parent 39113216a8
commit 1a481fad70
8 changed files with 490 additions and 83 deletions

2
.gitignore vendored
View file

@ -26,3 +26,5 @@ _testmain.go
*.prof *.prof
.DS_Store .DS_Store
.vscode

View file

@ -15,60 +15,58 @@
package main package main
import ( import (
"fmt" "log"
"os"
"path/filepath"
"strings" "strings"
"github.com/ryanuber/go-glob"
) )
type filelist struct { // fileList uses a map for patterns to ensure each pattern only
paths map[string]bool // appears once
globs []string type fileList struct {
patterns map[string]struct{}
} }
func newFileList(paths ...string) *filelist { func newFileList(paths ...string) *fileList {
f := &fileList{
f := &filelist{ patterns: make(map[string]struct{}),
make(map[string]bool),
make([]string, 0),
}
for _, path := range paths {
if e := f.Set(path); e != nil {
// #nosec
fmt.Fprintf(os.Stderr, "Unable to add %s to filelist: %s\n", path, e)
} }
for _, p := range paths {
f.patterns[p] = struct{}{}
} }
return f return f
} }
func (f *filelist) String() string { func (f *fileList) String() string {
return strings.Join(f.globs, ", ") ps := make([]string, 0, len(f.patterns))
for p := range f.patterns {
ps = append(ps, p)
}
return strings.Join(ps, ", ")
} }
func (f *filelist) Set(path string) error { func (f *fileList) Set(path string) error {
f.globs = append(f.globs, path) if path == "" {
matches, e := filepath.Glob(path) // don't bother adding the empty path
if e != nil { return nil
return e
}
for _, each := range matches {
abs, e := filepath.Abs(each)
if e != nil {
return e
}
f.paths[abs] = true
} }
f.patterns[path] = struct{}{}
return nil return nil
} }
func (f filelist) Contains(path string) bool { func (f fileList) Contains(path string) bool {
_, present := f.paths[path] for p := range f.patterns {
return present if glob.Glob(p, path) {
log.Printf("excluding: %s\n", path)
return true
}
}
log.Printf("including: %s\n", path)
return false
} }
/* /*
func (f filelist) Dump() { func (f fileList) Dump() {
for k, _ := range f.paths { for k, _ := range f.paths {
println(k) println(k)
} }

245
filelist_test.go Normal file
View file

@ -0,0 +1,245 @@
package main
import (
"reflect"
"testing"
)
func Test_newFileList(t *testing.T) {
type args struct {
paths []string
}
tests := []struct {
name string
args args
want *fileList
}{
{
name: "nil paths",
args: args{paths: nil},
want: &fileList{patterns: map[string]struct{}{}},
},
{
name: "empty paths",
args: args{paths: []string{}},
want: &fileList{patterns: map[string]struct{}{}},
},
{
name: "have paths",
args: args{paths: []string{"*_test.go"}},
want: &fileList{patterns: map[string]struct{}{
"*_test.go": struct{}{},
}},
},
}
for _, tt := range tests {
if got := newFileList(tt.args.paths...); !reflect.DeepEqual(got, tt.want) {
t.Errorf("%q. newFileList() = %v, want %v", tt.name, got, tt.want)
}
}
}
func Test_fileList_String(t *testing.T) {
type fields struct {
patterns []string
}
tests := []struct {
name string
fields fields
want string
}{
{
name: "nil patterns",
fields: fields{patterns: nil},
want: "",
},
{
name: "empty patterns",
fields: fields{patterns: []string{}},
want: "",
},
{
name: "one pattern",
fields: fields{patterns: []string{"foo"}},
want: "foo",
},
{
name: "two patterns",
fields: fields{patterns: []string{"foo", "bar"}},
want: "foo, bar",
},
}
for _, tt := range tests {
f := newFileList(tt.fields.patterns...)
if got := f.String(); got != tt.want {
t.Errorf("%q. fileList.String() = %v, want %v", tt.name, got, tt.want)
}
}
}
func Test_fileList_Set(t *testing.T) {
type fields struct {
patterns []string
}
type args struct {
path string
}
tests := []struct {
name string
fields fields
args args
want map[string]struct{}
wantErr bool
}{
{
name: "add empty path",
fields: fields{patterns: nil},
args: args{path: ""},
want: map[string]struct{}{},
wantErr: false,
},
{
name: "add path to nil patterns",
fields: fields{patterns: nil},
args: args{path: "foo"},
want: map[string]struct{}{
"foo": struct{}{},
},
wantErr: false,
},
{
name: "add path to empty patterns",
fields: fields{patterns: []string{}},
args: args{path: "foo"},
want: map[string]struct{}{
"foo": struct{}{},
},
wantErr: false,
},
{
name: "add path to populated patterns",
fields: fields{patterns: []string{"foo"}},
args: args{path: "bar"},
want: map[string]struct{}{
"foo": struct{}{},
"bar": struct{}{},
},
wantErr: false,
},
}
for _, tt := range tests {
f := newFileList(tt.fields.patterns...)
if err := f.Set(tt.args.path); (err != nil) != tt.wantErr {
t.Errorf("%q. fileList.Set() error = %v, wantErr %v", tt.name, err, tt.wantErr)
}
if !reflect.DeepEqual(f.patterns, tt.want) {
t.Errorf("%q. got state fileList.patterns = %v, want state %v", tt.name, f.patterns, tt.want)
}
}
}
func Test_fileList_Contains(t *testing.T) {
type fields struct {
patterns []string
}
type args struct {
path string
}
tests := []struct {
name string
fields fields
args args
want bool
}{
{
name: "nil patterns",
fields: fields{patterns: nil},
args: args{path: "foo"},
want: false,
},
{
name: "empty patterns",
fields: fields{patterns: nil},
args: args{path: "foo"},
want: false,
},
{
name: "one pattern, no wildcard, no match",
fields: fields{patterns: []string{"foo"}},
args: args{path: "bar"},
want: false,
},
{
name: "one pattern, no wildcard, match",
fields: fields{patterns: []string{"foo"}},
args: args{path: "foo"},
want: true,
},
{
name: "one pattern, wildcard prefix, match",
fields: fields{patterns: []string{"*foo"}},
args: args{path: "foo"},
want: true,
},
{
name: "one pattern, wildcard suffix, match",
fields: fields{patterns: []string{"foo*"}},
args: args{path: "foo"},
want: true,
},
{
name: "one pattern, wildcard both ends, match",
fields: fields{patterns: []string{"*foo*"}},
args: args{path: "foo"},
want: true,
},
{
name: "default test match 1",
fields: fields{patterns: []string{"*_test.go"}},
args: args{path: "foo_test.go"},
want: true,
},
{
name: "default test match 2",
fields: fields{patterns: []string{"*_test.go"}},
args: args{path: "bar/foo_test.go"},
want: true,
},
{
name: "default test match 3",
fields: fields{patterns: []string{"*_test.go"}},
args: args{path: "/bar/foo_test.go"},
want: true,
},
{
name: "default test match 4",
fields: fields{patterns: []string{"*_test.go"}},
args: args{path: "baz/bar/foo_test.go"},
want: true,
},
{
name: "default test match 5",
fields: fields{patterns: []string{"*_test.go"}},
args: args{path: "/baz/bar/foo_test.go"},
want: true,
},
{
name: "many patterns, no match",
fields: fields{patterns: []string{"*_one.go", "*_two.go"}},
args: args{path: "/baz/bar/foo_test.go"},
want: false,
},
{
name: "many patterns, match",
fields: fields{patterns: []string{"*_one.go", "*_two.go", "*_test.go"}},
args: args{path: "/baz/bar/foo_test.go"},
want: true,
},
}
for _, tt := range tests {
f := newFileList(tt.fields.patterns...)
if got := f.Contains(tt.args.path); got != tt.want {
t.Errorf("%q. fileList.Contains() = %v, want %v", tt.name, got, tt.want)
}
}
}

127
main.go
View file

@ -29,22 +29,30 @@ import (
"github.com/GoASTScanner/gas/output" "github.com/GoASTScanner/gas/output"
) )
type recursion bool
const (
recurse recursion = true
noRecurse recursion = false
)
var (
// #nosec flag // #nosec flag
var flagIgnoreNoSec = flag.Bool("nosec", false, "Ignores #nosec comments when set") flagIgnoreNoSec = flag.Bool("nosec", false, "Ignores #nosec comments when set")
// format output // format output
var flagFormat = flag.String("fmt", "text", "Set output format. Valid options are: json, csv, html, or text") flagFormat = flag.String("fmt", "text", "Set output format. Valid options are: json, csv, html, or text")
// output file // output file
var flagOutput = flag.String("out", "", "Set output file for results") flagOutput = flag.String("out", "", "Set output file for results")
// config file // config file
var flagConfig = flag.String("conf", "", "Path to optional config file") flagConfig = flag.String("conf", "", "Path to optional config file")
// quiet // quiet
var flagQuiet = flag.Bool("quiet", false, "Only show output when errors are found") flagQuiet = flag.Bool("quiet", false, "Only show output when errors are found")
var usageText = ` usageText = `
GAS - Go AST Scanner GAS - Go AST Scanner
Gas analyzes Go source code to look for common programming mistakes that Gas analyzes Go source code to look for common programming mistakes that
@ -67,7 +75,8 @@ USAGE:
` `
var logger *log.Logger logger *log.Logger
)
func extendConfList(conf map[string]interface{}, name string, inputStr string) { func extendConfList(conf map[string]interface{}, name string, inputStr string) {
if inputStr == "" { if inputStr == "" {
@ -145,7 +154,7 @@ func main() {
flag.Usage = usage flag.Usage = usage
// Exclude files // Exclude files
excluded := newFileList("**/*_test.go") excluded := newFileList("*_test.go")
flag.Var(excluded, "skip", "File pattern to exclude from scan") flag.Var(excluded, "skip", "File pattern to exclude from scan")
incRules := "" incRules := ""
@ -183,44 +192,13 @@ func main() {
analyzer := gas.NewAnalyzer(config, logger) analyzer := gas.NewAnalyzer(config, logger)
AddRules(&analyzer, config) AddRules(&analyzer, config)
// Traverse directory structure if './...' toAnalyze := getFilesToAnalyze(flag.Args(), excluded)
if flag.NArg() == 1 && flag.Arg(0) == "./..." {
cwd, err := os.Getwd() for _, file := range toAnalyze {
if err != nil { if err := analyzer.Process(file); err != nil {
logger.Fatalf("Unable to traverse path %s, reason - %s", flag.Arg(0), err)
}
filepath.Walk(cwd, func(path string, info os.FileInfo, err error) error {
if excluded.Contains(path) && info.IsDir() {
logger.Printf("Skipping %s\n", path)
return filepath.SkipDir
}
if !info.IsDir() && !excluded.Contains(path) &&
strings.HasSuffix(path, ".go") {
err = analyzer.Process(path)
if err != nil {
logger.Fatal(err) logger.Fatal(err)
} }
} }
return nil
})
} else {
// Process each file individually
for _, filename := range flag.Args() {
if finfo, err := os.Stat(filename); err == nil {
if !finfo.IsDir() && !excluded.Contains(filename) &&
strings.HasSuffix(filename, ".go") {
if err = analyzer.Process(filename); err != nil {
logger.Fatal(err)
}
}
} else {
logger.Fatal(err)
}
}
}
issuesFound := len(analyzer.Issues) > 0 issuesFound := len(analyzer.Issues) > 0
// Exit quietly if nothing was found // Exit quietly if nothing was found
@ -245,3 +223,68 @@ func main() {
os.Exit(1) os.Exit(1)
} }
} }
// getFilesToAnalyze lists all files
func getFilesToAnalyze(paths []string, excluded *fileList) []string {
log.Println("getFilesToAnalyze: start")
var toAnalyze []string
for _, path := range paths {
log.Printf("getFilesToAnalyze: processing \"%s\"\n", path)
// get the absolute path before doing anything else
path, err := filepath.Abs(path)
if err != nil {
log.Fatal(err)
}
if filepath.Base(path) == "..." {
toAnalyze = append(
toAnalyze,
listFiles(filepath.Dir(path), recurse, excluded)...,
)
} else {
var (
finfo os.FileInfo
err error
)
if finfo, err = os.Stat(path); err != nil {
logger.Fatal(err)
}
if !finfo.IsDir() {
if shouldInclude(path, excluded) {
toAnalyze = append(toAnalyze, path)
}
} else {
toAnalyze = listFiles(path, noRecurse, excluded)
}
}
}
log.Println("getFilesToAnalyze: end")
return toAnalyze
}
// listFiles returns a list of all files found that pass the shouldInclude check.
// If doRecursiveWalk it true, it will walk the tree rooted at absPath, otherwise it
// will only include files directly within the dir referenced by absPath.
func listFiles(absPath string, doRecursiveWalk recursion, excluded *fileList) []string {
var files []string
walk := func(path string, info os.FileInfo, err error) error {
if info.IsDir() && doRecursiveWalk == noRecurse {
return filepath.SkipDir
}
if shouldInclude(path, excluded) {
files = append(files, path)
}
return nil
}
if err := filepath.Walk(absPath, walk); err != nil {
log.Fatal(err)
}
return files
}
// shouldInclude checks if a specific path which is expected to reference
// a regular file should be included
func shouldInclude(path string, excluded *fileList) bool {
return filepath.Ext(path) == ".go" && !excluded.Contains(path)
}

45
main_test.go Normal file
View file

@ -0,0 +1,45 @@
package main
import "testing"
func Test_shouldInclude(t *testing.T) {
type args struct {
path string
excluded *fileList
}
tests := []struct {
name string
args args
want bool
}{
{
name: "non .go file",
args: args{
path: "thing.txt",
excluded: newFileList(),
},
want: false,
},
{
name: ".go file, not excluded",
args: args{
path: "thing.go",
excluded: newFileList(),
},
want: true,
},
{
name: ".go file, excluded",
args: args{
path: "thing.go",
excluded: newFileList("thing.go"),
},
want: false,
},
}
for _, tt := range tests {
if got := shouldInclude(tt.args.path, tt.args.excluded); got != tt.want {
t.Errorf("%q. shouldInclude() = %v, want %v", tt.name, got, tt.want)
}
}
}

2
vendor.conf Normal file
View file

@ -0,0 +1,2 @@
# Import path | revision | Repository(optional)
github.com/ryanuber/go-glob 572520ed46dbddaed19ea3d9541bdd0494163693

21
vendor/github.com/ryanuber/go-glob/LICENSE generated vendored Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Ryan Uber
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.

51
vendor/github.com/ryanuber/go-glob/glob.go generated vendored Normal file
View file

@ -0,0 +1,51 @@
package glob
import "strings"
// The character which is treated like a glob
const GLOB = "*"
// Glob will test a string pattern, potentially containing globs, against a
// subject string. The result is a simple true/false, determining whether or
// not the glob pattern matched the subject text.
func Glob(pattern, subj string) bool {
// Empty pattern can only match empty subject
if pattern == "" {
return subj == pattern
}
// If the pattern _is_ a glob, it matches everything
if pattern == GLOB {
return true
}
parts := strings.Split(pattern, GLOB)
if len(parts) == 1 {
// No globs in pattern, so test for equality
return subj == pattern
}
leadingGlob := strings.HasPrefix(pattern, GLOB)
trailingGlob := strings.HasSuffix(pattern, GLOB)
end := len(parts) - 1
// Check the first section. Requires special handling.
if !leadingGlob && !strings.HasPrefix(subj, parts[0]) {
return false
}
// Go over the middle parts and ensure they match.
for i := 1; i < end; i++ {
if !strings.Contains(subj, parts[i]) {
return false
}
// Trim evaluated text from subj as we loop over the pattern.
idx := strings.Index(subj, parts[i]) + len(parts[i])
subj = subj[idx:]
}
// Reached the last section. Requires special handling.
return trailingGlob || strings.HasSuffix(subj, parts[end])
}