Skip to content

Commit c2a4d2f

Browse files
chore: update dependencies to latest versions
Update all Go dependencies to their latest versions including: - github.com/spf13/cobra: v1.5.0 → v1.10.2 - github.com/spf13/viper: v1.7.1 → v1.21.0 - github.com/getsentry/sentry-go: v0.6.0 → v0.41.0 - github.com/stretchr/testify: v1.8.4 → v1.11.1 - github.com/fatih/color: v1.12.0 → v1.18.0 - github.com/google/go-cmp: v0.5.5 → v0.6.0 Also includes improvements to issues display: - Add terminal width-aware table rendering with column wrapping - Improve issue location formatting (single line vs range) - Add color-coded severity formatting - Add JSON export capability for run issues with --json and --output-file flags
1 parent bf2cb7e commit c2a4d2f

4 files changed

Lines changed: 336 additions & 535 deletions

File tree

command/issues/list/list.go

Lines changed: 119 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"io/ioutil"
99
"os"
10+
"strings"
1011

1112
"github.com/MakeNowJust/heredoc"
1213
"github.com/deepsourcelabs/cli/config"
@@ -152,22 +153,135 @@ func (opts *IssuesListOptions) showIssues() {
152153
header := []string{"LOCATION", "ANALYZER", "CODE", "TITLE", "CATEGORY", "SEVERITY"}
153154
data := [][]string{header}
154155

156+
// Get terminal width for column width calculation
157+
terminalWidth := pterm.GetTerminalWidth()
158+
if terminalWidth == 0 {
159+
terminalWidth = 120 // Default fallback
160+
}
161+
162+
// Calculate column widths (6 columns total)
163+
// Allocate space: LOCATION (20%), ANALYZER (10%), CODE (10%), TITLE (35%), CATEGORY (15%), SEVERITY (10%)
164+
colWidths := []int{
165+
int(float64(terminalWidth) * 0.20), // LOCATION
166+
int(float64(terminalWidth) * 0.10), // ANALYZER
167+
int(float64(terminalWidth) * 0.10), // CODE
168+
int(float64(terminalWidth) * 0.35), // TITLE
169+
int(float64(terminalWidth) * 0.15), // CATEGORY
170+
int(float64(terminalWidth) * 0.10), // SEVERITY
171+
}
172+
173+
// Get current working directory for relative path display
174+
cwd, _ := os.Getwd()
175+
155176
// Add data rows
156177
for _, issue := range opts.issuesData {
157178
filePath := issue.Location.Path
179+
if cwd != "" && strings.HasPrefix(filePath, cwd) {
180+
filePath = strings.TrimPrefix(filePath, cwd+"/")
181+
}
182+
183+
// Format location with start and end line numbers
158184
beginLine := issue.Location.Position.BeginLine
159-
issueLocation := fmt.Sprintf("%s:%d", filePath, beginLine)
185+
endLine := issue.Location.Position.EndLine
186+
var issueLocation string
187+
if beginLine == endLine {
188+
issueLocation = fmt.Sprintf("%s:%d", filePath, beginLine)
189+
} else {
190+
issueLocation = fmt.Sprintf("%s:%d-%d", filePath, beginLine, endLine)
191+
}
192+
160193
analyzerShortcode := issue.Analyzer.Shortcode
161194
issueCategory := issue.IssueCategory
162-
issueSeverity := issue.IssueSeverity
195+
issueSeverity := formatSeverity(issue.IssueSeverity)
163196
issueCode := issue.IssueCode
164197
issueTitle := issue.IssueText
165198

166-
data = append(data, []string{issueLocation, analyzerShortcode, issueCode, issueTitle, issueCategory, issueSeverity})
199+
// Wrap text for each column
200+
data = append(data, []string{
201+
wrapText(issueLocation, colWidths[0]),
202+
wrapText(analyzerShortcode, colWidths[1]),
203+
wrapText(issueCode, colWidths[2]),
204+
wrapText(issueTitle, colWidths[3]),
205+
wrapText(issueCategory, colWidths[4]),
206+
wrapText(issueSeverity, colWidths[5]),
207+
})
208+
}
209+
210+
// Render table with header, boxed style, and row separators
211+
pterm.DefaultTable.WithHasHeader().WithBoxed().WithRowSeparator("-").WithHeaderRowSeparator("-").WithData(data).Render()
212+
}
213+
214+
// formatSeverity formats severity with appropriate color styling
215+
func formatSeverity(severity string) string {
216+
switch strings.ToUpper(severity) {
217+
case "CRITICAL":
218+
return pterm.Red(severity)
219+
case "MAJOR":
220+
return pterm.LightRed(severity)
221+
case "MINOR":
222+
return pterm.Yellow(severity)
223+
default:
224+
return severity
225+
}
226+
}
227+
228+
// wrapText wraps text to fit within the specified width, inserting newlines for multiline cells
229+
func wrapText(text string, maxWidth int) string {
230+
if maxWidth <= 0 {
231+
return text
232+
}
233+
234+
// If text fits, return as-is
235+
if len(text) <= maxWidth {
236+
return text
237+
}
238+
239+
// Split text into words for better wrapping
240+
words := strings.Fields(text)
241+
if len(words) == 0 {
242+
return text
243+
}
244+
245+
var lines []string
246+
var currentLine strings.Builder
247+
248+
for _, word := range words {
249+
// If adding this word would exceed the width, start a new line
250+
if currentLine.Len() > 0 && currentLine.Len()+len(word)+1 > maxWidth {
251+
lines = append(lines, currentLine.String())
252+
currentLine.Reset()
253+
}
254+
255+
// If a single word is longer than maxWidth, we have to break it
256+
if len(word) > maxWidth {
257+
// If we have content in current line, save it first
258+
if currentLine.Len() > 0 {
259+
lines = append(lines, currentLine.String())
260+
currentLine.Reset()
261+
}
262+
// Break the long word
263+
for len(word) > maxWidth {
264+
lines = append(lines, word[:maxWidth])
265+
word = word[maxWidth:]
266+
}
267+
if len(word) > 0 {
268+
currentLine.WriteString(word)
269+
}
270+
} else {
271+
// Add word to current line
272+
if currentLine.Len() > 0 {
273+
currentLine.WriteString(" ")
274+
}
275+
currentLine.WriteString(word)
276+
}
277+
}
278+
279+
// Add the last line if it has content
280+
if currentLine.Len() > 0 {
281+
lines = append(lines, currentLine.String())
167282
}
168283

169-
// Render table with header
170-
pterm.DefaultTable.WithHasHeader().WithData(data).Render()
284+
return strings.Join(lines, "\n")
171285
}
172286

173287
// Handles exporting issues as JSON

command/runs/issues/issues.go

Lines changed: 100 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package issues
22

33
import (
44
"context"
5+
"encoding/json"
56
"fmt"
7+
"io/ioutil"
68
"os"
79
"os/exec"
810
"strings"
@@ -18,7 +20,10 @@ import (
1820
)
1921

2022
type IssuesOptions struct {
21-
commitOid string
23+
commitOid string
24+
jsonOutput bool
25+
outputFile string
26+
runWithIssues *runs.RunWithIssues
2227
}
2328

2429
// NewCmdRunsIssues shows the issues in a specific analysis run.
@@ -29,18 +34,25 @@ func NewCmdRunsIssues() *cobra.Command {
2934
Use %[1]s to view the issues found in a specific run.
3035
`, style.Cyan("deepsource runs issues <commit-oid>"))
3136

37+
opts := IssuesOptions{}
38+
3239
cmd := &cobra.Command{
3340
Use: "issues <commit-oid>",
3441
Short: "View issues in a specific analysis run",
3542
Long: doc,
3643
Args: args.ExactArgs(1),
3744
RunE: func(cmd *cobra.Command, args []string) error {
38-
opts := IssuesOptions{
39-
commitOid: args[0],
40-
}
45+
opts.commitOid = args[0]
4146
return opts.Run(cmd.Context())
4247
},
4348
}
49+
50+
// --json flag
51+
cmd.Flags().BoolVar(&opts.jsonOutput, "json", false, "Output issues in JSON format")
52+
53+
// --output-file, -o flag
54+
cmd.Flags().StringVarP(&opts.outputFile, "output-file", "o", "", "Output file to write the issues to")
55+
4456
return cmd
4557
}
4658

@@ -75,6 +87,13 @@ func (opts *IssuesOptions) Run(ctx context.Context) error {
7587
return fmt.Errorf("failed to fetch run issues: %w", err)
7688
}
7789

90+
opts.runWithIssues = runWithIssues
91+
92+
// If JSON output is requested, export and return
93+
if opts.jsonOutput {
94+
return opts.exportJSON(opts.outputFile)
95+
}
96+
7897
// Display run info
7998
commitShort := commitOid
8099
if len(commitShort) > 8 {
@@ -271,3 +290,80 @@ func expandCommitOID(commitOid string) (string, error) {
271290

272291
return commitOid, nil
273292
}
293+
294+
// RunIssuesJSON represents the JSON structure for run issues output
295+
type RunIssuesJSON struct {
296+
RunUid string `json:"run_uid"`
297+
CommitOid string `json:"commit_oid"`
298+
BranchName string `json:"branch_name"`
299+
Status string `json:"status"`
300+
Issues []IssueJSON `json:"issues"`
301+
}
302+
303+
// IssueJSON represents an issue in JSON format
304+
type IssueJSON struct {
305+
Path string `json:"path"`
306+
BeginLine int `json:"begin_line"`
307+
BeginColumn int `json:"begin_column"`
308+
EndLine int `json:"end_line"`
309+
EndColumn int `json:"end_column"`
310+
IssueText string `json:"issue_text"`
311+
IssueCode string `json:"issue_code"`
312+
Title string `json:"title"`
313+
Category string `json:"category"`
314+
Severity string `json:"severity"`
315+
AnalyzerName string `json:"analyzer_name"`
316+
AnalyzerShortcode string `json:"analyzer_shortcode"`
317+
}
318+
319+
// exportJSON exports the run issues to JSON format
320+
func (opts *IssuesOptions) exportJSON(filename string) error {
321+
if opts.runWithIssues == nil {
322+
return fmt.Errorf("no run data available to export")
323+
}
324+
325+
// Convert to JSON structure
326+
jsonData := RunIssuesJSON{
327+
RunUid: opts.runWithIssues.RunUid,
328+
CommitOid: opts.runWithIssues.CommitOid,
329+
BranchName: opts.runWithIssues.BranchName,
330+
Status: opts.runWithIssues.Status,
331+
Issues: make([]IssueJSON, 0, len(opts.runWithIssues.Issues)),
332+
}
333+
334+
for _, issue := range opts.runWithIssues.Issues {
335+
jsonData.Issues = append(jsonData.Issues, IssueJSON{
336+
Path: issue.Path,
337+
BeginLine: issue.BeginLine,
338+
BeginColumn: issue.BeginColumn,
339+
EndLine: issue.EndLine,
340+
EndColumn: issue.EndColumn,
341+
IssueText: issue.IssueText,
342+
IssueCode: issue.IssueCode,
343+
Title: issue.Title,
344+
Category: issue.Category,
345+
Severity: issue.Severity,
346+
AnalyzerName: issue.AnalyzerName,
347+
AnalyzerShortcode: issue.AnalyzerShortcode,
348+
})
349+
}
350+
351+
// Marshal to JSON
352+
data, err := json.MarshalIndent(jsonData, "", " ")
353+
if err != nil {
354+
return fmt.Errorf("failed to marshal JSON: %w", err)
355+
}
356+
357+
// Write to file or stdout
358+
if filename == "" {
359+
fmt.Println(string(data))
360+
return nil
361+
}
362+
363+
if err := ioutil.WriteFile(filename, data, 0644); err != nil {
364+
return fmt.Errorf("failed to write JSON file: %w", err)
365+
}
366+
367+
pterm.Printf("Saved issues to %s!\n", filename)
368+
return nil
369+
}

0 commit comments

Comments
 (0)