Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,21 @@ func rootRunE(cmd *cobra.Command, args []string) error {
return err
}

findings := p.ParsePaths(print, parseArgs(args)...)
parseReport := p.ParsePathsWithReport(print, parseArgs(args)...)
findings := parseReport.FilesWithFindings

if exitOneOnFailure && findings > 0 {
// We intentionally return an error if exitOneOnFailure is true, but don't want to show usage
cmd.SilenceUsage = true
err = fmt.Errorf("files with findings: %d", findings)
if parseReport.FirstFinding != nil {
err = fmt.Errorf("%w (first finding: %s:%d:%d)",
err,
parseReport.FirstFinding.Filename,
parseReport.FirstFinding.Line,
parseReport.FirstFinding.Column,
)
}
}

if findings == 0 {
Expand Down
1 change: 1 addition & 0 deletions cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ func TestRunE(t *testing.T) {
err := rootRunE(new(cobra.Command), []string{"../testdata"})
assert.Error(t, err)
assert.Regexp(t, regexp.MustCompile(`^files with findings: \d`), err.Error())
assert.Regexp(t, regexp.MustCompile(`first finding: .+:\d+:\d+`), err.Error())
})

t.Run("no rules enabled", func(t *testing.T) {
Expand Down
49 changes: 45 additions & 4 deletions pkg/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,19 @@ type Parser struct {
rchan chan result.FileResults
}

// FindingLocation captures where a finding starts in source.
type FindingLocation struct {
Filename string
Line int
Column int
}

// ParseReport summarizes ParsePaths results.
type ParseReport struct {
FilesWithFindings int
FirstFinding *FindingLocation
}

// NewParser returns a pointer to a Parser that is used to check for findings
// based on the rules provided, ignoring files based on the ignorer provided
func NewParser(rules []*rule.Rule, ignorer *ignore.Ignore) *Parser {
Expand All @@ -42,16 +55,25 @@ func NewParser(rules []*rule.Rule, ignorer *ignore.Ignore) *Parser {

// ParsePaths parses all files provided and returns the number of files with findings
func (p *Parser) ParsePaths(print printer.Printer, paths ...string) int {
return p.ParsePathsWithReport(print, paths...).FilesWithFindings
}

// ParsePathsWithReport parses all files provided and returns a report including
// the number of files with findings and the first finding location.
func (p *Parser) ParsePathsWithReport(print printer.Printer, paths ...string) ParseReport {
report := ParseReport{}
print.Start()
defer print.End()

// data provided through stdin
if util.InSlice(os.Stdin.Name(), paths) {
r, _ := p.generateFileFindings(os.Stdin)
if r.Len() > 0 {
report.FirstFinding = firstFindingLocation(r)
print.Print(r)
}
return r.Len()
report.FilesWithFindings = r.Len()
return report
}

if len(paths) == 0 {
Expand All @@ -75,13 +97,32 @@ func (p *Parser) ParsePaths(print printer.Printer, paths ...string) int {
close(p.rchan)
}()

findings := 0
for r := range p.rchan {
sort.Sort(r)
print.Print(&r)
findings++
report.FilesWithFindings++
if report.FirstFinding == nil {
report.FirstFinding = firstFindingLocation(&r)
}
}
return report
}

func firstFindingLocation(r *result.FileResults) *FindingLocation {
if r == nil || len(r.Results) == 0 {
return nil
}

start := r.Results[0].GetStartPosition()
if start == nil {
return nil
}

return &FindingLocation{
Filename: start.Filename,
Line: start.Line,
Column: start.Column,
}
return findings
}

func (p *Parser) processFiles(files <-chan string, done chan bool, wg *sync.WaitGroup) {
Expand Down
17 changes: 17 additions & 0 deletions pkg/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,23 @@ func parsePathTests(t *testing.T) {
assert.EqualValues(t, &expected, pr.results[0])
})

t.Run("finding report includes first location", func(t *testing.T) {
f, err := newFile(t, "i have a whitelist")
assert.NoError(t, err)

pr := new(testPrinter)
p, err := testParser()
assert.NoError(t, err)
report := p.ParsePathsWithReport(pr, f.Name())

assert.Equal(t, 1, report.FilesWithFindings)
if assert.NotNil(t, report.FirstFinding) {
assert.Equal(t, filepath.ToSlash(f.Name()), report.FirstFinding.Filename)
assert.Equal(t, 1, report.FirstFinding.Line)
assert.Equal(t, 9, report.FirstFinding.Column)
}
})

t.Run("no findings", func(t *testing.T) {
f, err := newFile(t, "i have a no findings\n")
assert.NoError(t, err)
Expand Down