diff --git a/cmd/core-lint/main.go b/cmd/core-lint/main.go index f703b37..1667039 100644 --- a/cmd/core-lint/main.go +++ b/cmd/core-lint/main.go @@ -323,7 +323,9 @@ func newCheckCommand() *cli.Command { } return lintpkg.WriteReportSARIF(command.OutOrStdout(), report) default: - lintpkg.WriteText(command.OutOrStdout(), findings) + if err := lintpkg.WriteText(command.OutOrStdout(), findings); err != nil { + return err + } if format == "text" && len(findings) > 0 { writeCatalogSummary(command.OutOrStdout(), findings) } @@ -405,11 +407,9 @@ func writeReport(writer io.Writer, output string, report lintpkg.Report) error { case "json": return lintpkg.WriteReportJSON(writer, report) case "text": - lintpkg.WriteReportText(writer, report) - return nil + return lintpkg.WriteReportText(writer, report) case "github": - lintpkg.WriteReportGitHub(writer, report) - return nil + return lintpkg.WriteReportGitHub(writer, report) case "sarif": return lintpkg.WriteReportSARIF(writer, report) default: diff --git a/pkg/lint/report.go b/pkg/lint/report.go index 41a9cc3..40d408d 100644 --- a/pkg/lint/report.go +++ b/pkg/lint/report.go @@ -75,7 +75,7 @@ func WriteJSONL(w io.Writer, findings []Finding) error { // WriteText writes findings in a human-readable format. // // lint.WriteText(os.Stdout, findings) -func WriteText(w io.Writer, findings []Finding) { +func WriteText(w io.Writer, findings []Finding) error { for _, finding := range findings { message := finding.Message if message == "" { @@ -85,8 +85,11 @@ func WriteText(w io.Writer, findings []Finding) { if code == "" { code = finding.RuleID } - fmt.Fprintf(w, "%s:%d [%s] %s (%s)\n", finding.File, finding.Line, finding.Severity, message, code) + if _, err := fmt.Fprintf(w, "%s:%d [%s] %s (%s)\n", finding.File, finding.Line, finding.Severity, message, code); err != nil { + return err + } } + return nil } // WriteReportJSON writes the RFC report document as pretty-printed JSON. @@ -101,15 +104,18 @@ func WriteReportJSON(w io.Writer, report Report) error { // WriteReportText writes report findings followed by a short summary. // // lint.WriteReportText(os.Stdout, report) -func WriteReportText(w io.Writer, report Report) { - WriteText(w, report.Findings) - fmt.Fprintf(w, "\n%d finding(s): %d error(s), %d warning(s), %d info\n", report.Summary.Total, report.Summary.Errors, report.Summary.Warnings, report.Summary.Info) +func WriteReportText(w io.Writer, report Report) error { + if err := WriteText(w, report.Findings); err != nil { + return err + } + _, err := fmt.Fprintf(w, "\n%d finding(s): %d error(s), %d warning(s), %d info\n", report.Summary.Total, report.Summary.Errors, report.Summary.Warnings, report.Summary.Info) + return err } // WriteReportGitHub writes GitHub Actions annotation lines. // // lint.WriteReportGitHub(os.Stdout, report) -func WriteReportGitHub(w io.Writer, report Report) { +func WriteReportGitHub(w io.Writer, report Report) error { for _, finding := range report.Findings { level := githubAnnotationLevel(finding.Severity) @@ -132,8 +138,11 @@ func WriteReportGitHub(w io.Writer, report Report) { if code == "" { code = finding.RuleID } - fmt.Fprintf(w, "::%s%s::[%s] %s (%s)\n", level, location, finding.Tool, message, code) + if _, err := fmt.Fprintf(w, "::%s%s::[%s] %s (%s)\n", level, location, finding.Tool, message, code); err != nil { + return err + } } + return nil } // WriteReportSARIF writes a minimal SARIF document for code scanning tools. diff --git a/pkg/lint/report_test.go b/pkg/lint/report_test.go index db62d3e..c31bfd9 100644 --- a/pkg/lint/report_test.go +++ b/pkg/lint/report_test.go @@ -3,6 +3,7 @@ package lint import ( "bytes" "encoding/json" + "errors" "strings" "testing" @@ -118,7 +119,8 @@ func TestWriteJSONL_Good_Empty(t *testing.T) { func TestWriteText_Good(t *testing.T) { findings := sampleFindings() var buf bytes.Buffer - WriteText(&buf, findings) + err := WriteText(&buf, findings) + require.NoError(t, err) output := buf.String() assert.Contains(t, output, "store/query.go:42") @@ -131,14 +133,15 @@ func TestWriteText_Good(t *testing.T) { func TestWriteText_Good_Empty(t *testing.T) { var buf bytes.Buffer - WriteText(&buf, nil) + err := WriteText(&buf, nil) + require.NoError(t, err) assert.Empty(t, buf.String()) } func TestWriteReportGitHub_Good_MapsInfoToNotice(t *testing.T) { var buf bytes.Buffer - WriteReportGitHub(&buf, Report{ + err := WriteReportGitHub(&buf, Report{ Findings: []Finding{{ Tool: "demo", File: "example.go", @@ -149,10 +152,23 @@ func TestWriteReportGitHub_Good_MapsInfoToNotice(t *testing.T) { Message: "explanation", }}, }) + require.NoError(t, err) assert.Contains(t, buf.String(), "::notice file=example.go,line=7,col=3::[demo] explanation (demo-rule)") } +func TestWriteText_Bad_PropagatesWriterErrors(t *testing.T) { + err := WriteText(failingWriter{}, sampleFindings()) + require.Error(t, err) +} + +func TestWriteReportGitHub_Bad_PropagatesWriterErrors(t *testing.T) { + err := WriteReportGitHub(failingWriter{}, Report{ + Findings: sampleFindings(), + }) + require.Error(t, err) +} + func TestWriteReportSARIF_Good_MapsInfoToNote(t *testing.T) { var buf bytes.Buffer @@ -176,3 +192,9 @@ func TestWriteReportSARIF_Good_MapsInfoToNote(t *testing.T) { results := runs[0].(map[string]any)["results"].([]any) assert.Equal(t, "note", results[0].(map[string]any)["level"]) } + +type failingWriter struct{} + +func (failingWriter) Write([]byte) (int, error) { + return 0, errors.New("write failed") +}