Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(misconf): highlight only affected rows #7310

Merged
merged 4 commits into from
Aug 15, 2024
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,7 @@
"IsCause": true,
"Annotation": "",
"Truncated": false,
"Highlighted": "\u001b[0m \u001b[38;5;33mrunAsUser\u001b[0m: \u001b[38;5;37m0",
"Highlighted": "\u001b[0m \u001b[38;5;33mrunAsUser\u001b[0m: \u001b[38;5;37m0\u001b[0m",
"FirstCause": false,
"LastCause": true
}
Expand Down
315 changes: 170 additions & 145 deletions pkg/iac/scan/code.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package scan

import (
"bufio"
"bytes"
"fmt"
"io/fs"
"path/filepath"
Expand All @@ -15,6 +14,44 @@ type Code struct {
Lines []Line
}

func (c *Code) truncateLines(maxLines int) {
previouslyTruncated := maxLines-1 > 0 && c.Lines[maxLines-2].Truncated
if maxLines-1 > 0 && c.Lines[maxLines-1].LastCause {
c.Lines[maxLines-2].LastCause = true
}
c.Lines[maxLines-1] = Line{
Truncated: true,
Number: c.Lines[maxLines-1].Number,
}
if previouslyTruncated {
c.Lines = c.Lines[:maxLines-1]
} else {
c.Lines = c.Lines[:maxLines]
}
}

func (c *Code) markFirstAndLastCauses() {
var isFirst bool
var isLast bool

for i, line := range c.Lines {
if line.IsCause && !isFirst {
c.Lines[i].FirstCause = true
isFirst = true
}

if isFirst && !line.IsCause && i > 0 {
c.Lines[i-1].LastCause = true
isLast = true
break
}
}

if !isLast && len(c.Lines) > 0 {
c.Lines[len(c.Lines)-1].LastCause = true
}
}

type Line struct {
Number int `json:"Number"`
Content string `json:"Content"`
Expand Down Expand Up @@ -96,199 +133,187 @@ func OptionCodeWithHighlighted(include bool) CodeOption {
}
}

func validateRange(r iacTypes.Range) error {
if r.GetStartLine() < 0 || r.GetStartLine() > r.GetEndLine() || r.GetEndLine() < 0 {
return fmt.Errorf("invalid range: %s", r.String())
}
return nil
}

// nolint
func (r *Result) GetCode(opts ...CodeOption) (*Code, error) {

settings := defaultCodeSettings
for _, opt := range opts {
opt(&settings)
}

srcFS := r.Metadata().Range().GetFS()
if srcFS == nil {
fsys := r.Metadata().Range().GetFS()
if fsys == nil {
return nil, fmt.Errorf("code unavailable: result was not mapped to a known filesystem")
}

innerRange := r.Range()
outerRange := innerRange
metadata := r.Metadata()
for {
if parent := metadata.Parent(); parent != nil &&
parent.Range().GetFilename() == metadata.Range().GetFilename() &&
parent.Range().GetStartLine() > 0 {
outerRange = parent.Range()
metadata = *parent
continue
}
break
innerRange := r.metadata.Range()
if err := innerRange.Validate(); err != nil {
return nil, err
}

if err := validateRange(innerRange); err != nil {
return nil, err
if innerRange.GetStartLine() == 0 {
return nil, fmt.Errorf("inner range has invalid start line: %s", innerRange.String())
}
if err := validateRange(outerRange); err != nil {

outerRange := r.getOuterRange()
if err := outerRange.Validate(); err != nil {
return nil, err
}

slashed := filepath.ToSlash(r.fsPath)
slashed = strings.TrimPrefix(slashed, "/")

content, err := fs.ReadFile(srcFS, slashed)
filePath := strings.TrimPrefix(filepath.ToSlash(r.fsPath), "/")
rawLines, err := readLinesFromFile(fsys, filePath, outerRange.GetStartLine(), outerRange.GetEndLine())
if err != nil {
return nil, fmt.Errorf("failed to read file from result filesystem (%#v): %w", srcFS, err)
return nil, err
}

hasAnnotation := r.Annotation() != ""

code := Code{
Lines: nil,
if outerRange.GetEndLine()-outerRange.GetStartLine() > len(rawLines) {
return nil, fmt.Errorf("invalid outer range: %s", outerRange.String())
}

var rawLines []string
bs := bufio.NewScanner(bytes.NewReader(content))
for bs.Scan() {
rawLines = append(rawLines, bs.Text())
}
if bs.Err() != nil {
return nil, fmt.Errorf("failed to scan file : %w", err)
}
highlightedLines := r.getHighlightedLines(outerRange, innerRange, rawLines, settings)

var highlightedLines []string
if settings.includeHighlighted {
highlightedLines = highlight(iacTypes.CreateFSKey(innerRange.GetFS()), innerRange.GetLocalFilename(), content, settings.theme)
if len(highlightedLines) < len(rawLines) {
highlightedLines = rawLines
}
var code Code

shrink := settings.allowTruncation && outerRange.LineCount() > (innerRange.LineCount()+10)

if shrink {
code.Lines = r.getTruncatedLines(outerRange, innerRange, rawLines, highlightedLines)
} else {
highlightedLines = make([]string, len(rawLines))
code.Lines = r.getAllLines(outerRange, innerRange, rawLines, highlightedLines)
}

if outerRange.GetEndLine()-1 >= len(rawLines) || innerRange.GetStartLine() == 0 {
return nil, fmt.Errorf("invalid line number")
if settings.allowTruncation && len(code.Lines) > settings.maxLines && settings.maxLines > 0 {
code.truncateLines(settings.maxLines)
}

shrink := settings.allowTruncation && outerRange.LineCount() > (innerRange.LineCount()+10)
code.markFirstAndLastCauses()

if shrink {
return &code, nil
}

if outerRange.GetStartLine() < innerRange.GetStartLine() {
code.Lines = append(
code.Lines,
Line{
Content: rawLines[outerRange.GetStartLine()-1],
Highlighted: highlightedLines[outerRange.GetStartLine()-1],
Number: outerRange.GetStartLine(),
},
)
if outerRange.GetStartLine()+1 < innerRange.GetStartLine() {
code.Lines = append(
code.Lines,
Line{
Truncated: true,
Number: outerRange.GetStartLine() + 1,
},
)
}
}
func (r *Result) getHighlightedLines(outerRange, innerRange iacTypes.Range, rawLines []string, settings codeSettings) []string {

for lineNo := innerRange.GetStartLine(); lineNo <= innerRange.GetEndLine(); lineNo++ {
highlightedLines := make([]string, len(rawLines))
if !settings.includeHighlighted {
return highlightedLines
}

if lineNo-1 >= len(rawLines) || lineNo-1 >= len(highlightedLines) {
break
}
content := strings.Join(rawLines, "\n")
fsKey := iacTypes.CreateFSKey(innerRange.GetFS())
highlightedLines = highlight(fsKey, innerRange.GetLocalFilename(),
outerRange.GetStartLine(), outerRange.GetEndLine(), content, settings.theme)

line := Line{
Number: lineNo,
Content: strings.TrimSuffix(rawLines[lineNo-1], "\r"),
Highlighted: strings.TrimSuffix(highlightedLines[lineNo-1], "\r"),
IsCause: true,
}
if len(highlightedLines) < len(rawLines) {
return rawLines
}

if hasAnnotation && lineNo == innerRange.GetStartLine() {
line.Annotation = r.Annotation()
}
return highlightedLines
}

code.Lines = append(code.Lines, line)
}
func (r *Result) getOuterRange() iacTypes.Range {
outer := r.Metadata().Range()
for parent := r.Metadata().Parent(); parent != nil &&
parent.Range().GetFilename() == outer.GetFilename() &&
parent.Range().GetStartLine() > 0; parent = parent.Parent() {
outer = parent.Range()
}
return outer
}

if outerRange.GetEndLine() > innerRange.GetEndLine() {
if outerRange.GetEndLine() > innerRange.GetEndLine()+1 {
code.Lines = append(
code.Lines,
Line{
Truncated: true,
Number: outerRange.GetEndLine() - 1,
},
)
}
code.Lines = append(
code.Lines,
Line{
Content: rawLines[outerRange.GetEndLine()-1],
Highlighted: highlightedLines[outerRange.GetEndLine()-1],
Number: outerRange.GetEndLine(),
},
)
func (r *Result) getTruncatedLines(outerRange, innerRange iacTypes.Range, rawLines, highlightedLines []string) []Line {
var lines []Line

if outerRange.GetStartLine() < innerRange.GetStartLine() {
lines = append(lines, Line{
Content: rawLines[0],
Highlighted: highlightedLines[0],
Number: outerRange.GetStartLine(),
})
if outerRange.GetStartLine()+1 < innerRange.GetStartLine() {
lines = append(lines, Line{
Truncated: true,
Number: outerRange.GetStartLine() + 1,
})
}
}

for lineNo := innerRange.GetStartLine() - outerRange.GetStartLine(); lineNo <= innerRange.GetEndLine()-outerRange.GetStartLine(); lineNo++ {
if lineNo >= len(rawLines) || lineNo >= len(highlightedLines) {
break
}

} else {
for lineNo := outerRange.GetStartLine(); lineNo <= outerRange.GetEndLine(); lineNo++ {
line := Line{
Number: lineNo + outerRange.GetStartLine(),
Content: strings.TrimSuffix(rawLines[lineNo], "\r"),
Highlighted: strings.TrimSuffix(highlightedLines[lineNo], "\r"),
IsCause: true,
}

line := Line{
Number: lineNo,
Content: strings.TrimSuffix(rawLines[lineNo-1], "\r"),
Highlighted: strings.TrimSuffix(highlightedLines[lineNo-1], "\r"),
IsCause: lineNo >= innerRange.GetStartLine() && lineNo <= innerRange.GetEndLine(),
}
if r.Annotation() != "" && lineNo == innerRange.GetStartLine()-outerRange.GetStartLine()-1 {
line.Annotation = r.Annotation()
}

if hasAnnotation && lineNo == innerRange.GetStartLine() {
line.Annotation = r.Annotation()
}
lines = append(lines, line)
}

code.Lines = append(code.Lines, line)
if outerRange.GetEndLine() > innerRange.GetEndLine() {
if outerRange.GetEndLine() > innerRange.GetEndLine()+1 {
lines = append(lines, Line{
Truncated: true,
Number: outerRange.GetEndLine() - 1,
})
}
lines = append(lines, Line{
Content: rawLines[outerRange.GetEndLine()-outerRange.GetStartLine()],
Highlighted: highlightedLines[outerRange.GetEndLine()-outerRange.GetStartLine()],
Number: outerRange.GetEndLine(),
})
}

if settings.allowTruncation && len(code.Lines) > settings.maxLines && settings.maxLines > 0 {
previouslyTruncated := settings.maxLines-1 > 0 && code.Lines[settings.maxLines-2].Truncated
if settings.maxLines-1 > 0 && code.Lines[settings.maxLines-1].LastCause {
code.Lines[settings.maxLines-2].LastCause = true
}
code.Lines[settings.maxLines-1] = Line{
Truncated: true,
Number: code.Lines[settings.maxLines-1].Number,
return lines
}

func (r *Result) getAllLines(outerRange, innerRange iacTypes.Range, rawLines, highlightedLines []string) []Line {
lines := make([]Line, 0, outerRange.GetEndLine()-outerRange.GetStartLine()+1)

for lineNo := 0; lineNo <= outerRange.GetEndLine()-outerRange.GetStartLine(); lineNo++ {
line := Line{
Number: lineNo + outerRange.GetStartLine(),
Content: strings.TrimSuffix(rawLines[lineNo], "\r"),
Highlighted: strings.TrimSuffix(highlightedLines[lineNo], "\r"),
IsCause: lineNo >= innerRange.GetStartLine()-outerRange.GetStartLine() &&
lineNo <= innerRange.GetEndLine()-outerRange.GetStartLine(),
}
if previouslyTruncated {
code.Lines = code.Lines[:settings.maxLines-1]
} else {
code.Lines = code.Lines[:settings.maxLines]

if r.Annotation() != "" && lineNo == innerRange.GetStartLine()-outerRange.GetStartLine()-1 {
line.Annotation = r.Annotation()
}

lines = append(lines, line)
}

var first, last bool
for i, line := range code.Lines {
if line.IsCause && !first {
code.Lines[i].FirstCause = true
first = true
continue
}
if first && !line.IsCause && i > 0 {
code.Lines[i-1].LastCause = true
last = true
break
return lines
}

func readLinesFromFile(fsys fs.FS, path string, from, to int) ([]string, error) {
slashedPath := strings.TrimPrefix(filepath.ToSlash(path), "/")

file, err := fsys.Open(slashedPath)
if err != nil {
return nil, fmt.Errorf("failed to read file from result filesystem: %w", err)
}
defer file.Close()

scanner := bufio.NewScanner(file)
rawLines := make([]string, 0, to-from+1)

for lineNum := 0; scanner.Scan() && lineNum < to; lineNum++ {
if lineNum >= from-1 {
rawLines = append(rawLines, scanner.Text())
}
}
if !last && len(code.Lines) > 0 {
code.Lines[len(code.Lines)-1].LastCause = true

if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("failed to scan file: %w", err)
}

return &code, nil
return rawLines, nil
}
Loading