diff --git a/integration/testdata/helm_testchart.overridden.json.golden b/integration/testdata/helm_testchart.overridden.json.golden index 1b1ada2cd9a3..f4c984daa458 100644 --- a/integration/testdata/helm_testchart.overridden.json.golden +++ b/integration/testdata/helm_testchart.overridden.json.golden @@ -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 } diff --git a/pkg/iac/scan/code.go b/pkg/iac/scan/code.go index e61b547988c6..8388dd4dbf6e 100644 --- a/pkg/iac/scan/code.go +++ b/pkg/iac/scan/code.go @@ -2,7 +2,6 @@ package scan import ( "bufio" - "bytes" "fmt" "io/fs" "path/filepath" @@ -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"` @@ -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 } diff --git a/pkg/iac/scan/code_test.go b/pkg/iac/scan/code_test.go index c3ffe3725ef1..559f1d9dfe69 100644 --- a/pkg/iac/scan/code_test.go +++ b/pkg/iac/scan/code_test.go @@ -1,11 +1,10 @@ package scan import ( - "os" "strings" "testing" + "testing/fstest" - "github.com/liamg/memoryfs" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -13,19 +12,18 @@ import ( ) func TestResult_GetCode(t *testing.T) { - + const line = "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind." tests := []struct { - name string - source string - filename string - start int - end int - outerStart int - outerEnd int - expected []Line - options []CodeOption - wantErr bool - annotation string + name string + source string + filename string + startInnerLine int + endInnerLine int + startOuterLine int + endOuterLine int + expected []Line + options []CodeOption + wantErr bool }{ { name: "basic w/ defaults", @@ -33,9 +31,9 @@ func TestResult_GetCode(t *testing.T) { 2 3 4`, - filename: "test.txt", - start: 2, - end: 3, + filename: "test.txt", + startInnerLine: 2, + endInnerLine: 3, expected: []Line{ { Number: 2, @@ -60,12 +58,12 @@ func TestResult_GetCode(t *testing.T) { source: `resource "aws_s3_bucket" "something" { bucket = "something" }`, - filename: "main.tf", - start: 2, - end: 2, - outerStart: 1, - outerEnd: 3, - options: []CodeOption{OptionCodeWithHighlighted(false)}, + filename: "main.tf", + startInnerLine: 2, + endInnerLine: 2, + startOuterLine: 1, + endOuterLine: 3, + options: []CodeOption{OptionCodeWithHighlighted(false)}, expected: []Line{ { Number: 1, @@ -90,10 +88,10 @@ func TestResult_GetCode(t *testing.T) { 2 3 4`, - filename: "", - start: 2, - end: 3, - wantErr: true, + filename: "", + startInnerLine: 2, + endInnerLine: 3, + wantErr: true, }, { name: "no line numbers", @@ -101,10 +99,10 @@ func TestResult_GetCode(t *testing.T) { 2 3 4`, - filename: "test.txt", - start: 0, - end: 0, - wantErr: true, + filename: "test.txt", + startInnerLine: 0, + endInnerLine: 0, + wantErr: true, }, { name: "negative line numbers", @@ -112,10 +110,10 @@ func TestResult_GetCode(t *testing.T) { 2 3 4`, - filename: "test.txt", - start: -2, - end: -1, - wantErr: true, + filename: "test.txt", + startInnerLine: -2, + endInnerLine: -1, + wantErr: true, }, { name: "invalid line numbers", @@ -123,17 +121,17 @@ func TestResult_GetCode(t *testing.T) { 2 3 4`, - filename: "test.txt", - start: 5, - end: 6, - wantErr: true, + filename: "test.txt", + startInnerLine: 5, + endInnerLine: 6, + wantErr: true, }, { - name: "syntax highlighting", - source: `FROM ubuntu`, - filename: "Dockerfile", - start: 1, - end: 1, + name: "syntax highlighting", + source: `FROM ubuntu`, + filename: "Dockerfile", + startInnerLine: 1, + endInnerLine: 1, expected: []Line{ { Number: 1, @@ -146,81 +144,81 @@ func TestResult_GetCode(t *testing.T) { }, }, { - name: "truncation", - source: strings.Repeat("If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.\n", 100), - filename: "longfile.txt", - start: 1, - end: 100, + name: "truncation", + source: strings.Repeat(line+"\n", 100), + filename: "longfile.txt", + startInnerLine: 1, + endInnerLine: 100, expected: []Line{ { Number: 1, - Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Content: line, IsCause: true, - Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Highlighted: line, FirstCause: true, LastCause: false, }, { Number: 2, - Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Content: line, IsCause: true, - Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Highlighted: line, FirstCause: false, LastCause: false, }, { Number: 3, - Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Content: line, IsCause: true, - Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Highlighted: line, FirstCause: false, LastCause: false, }, { Number: 4, - Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Content: line, IsCause: true, - Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Highlighted: line, FirstCause: false, LastCause: false, }, { Number: 5, - Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Content: line, IsCause: true, - Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Highlighted: line, FirstCause: false, LastCause: false, }, { Number: 6, - Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Content: line, IsCause: true, - Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Highlighted: line, FirstCause: false, LastCause: false, }, { Number: 7, - Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Content: line, IsCause: true, - Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Highlighted: line, FirstCause: false, LastCause: false, }, { Number: 8, - Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Content: line, IsCause: true, - Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Highlighted: line, FirstCause: false, LastCause: false, }, { Number: 9, - Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Content: line, IsCause: true, - Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Highlighted: line, FirstCause: false, LastCause: true, }, @@ -230,26 +228,97 @@ func TestResult_GetCode(t *testing.T) { }, }, }, + { + name: "invalid inner range", + source: `Test`, + filename: "test.txt", + startInnerLine: 0, + endInnerLine: 0, + wantErr: true, + }, + { + name: "invalid outer range", + source: `Test`, + filename: "test.txt", + startInnerLine: 10, + endInnerLine: 12, + startOuterLine: 5, + endOuterLine: 3, + wantErr: true, + }, + { + name: "truncate with outer range", + source: strings.Repeat(line+"\n", 100), + filename: "longfile.txt", + startOuterLine: 1, + endOuterLine: 100, + startInnerLine: 10, + endInnerLine: 12, + options: []CodeOption{OptionCodeWithTruncation(true)}, + expected: []Line{ + { + Number: 1, + Content: line, + Highlighted: line, + }, + { + Number: 2, + Truncated: true, + }, + { + Number: 10, + Content: line, + IsCause: true, + FirstCause: true, + Highlighted: line, + }, + { + Number: 11, + Content: line, + IsCause: true, + Highlighted: line, + }, + { + Number: 12, + Content: line, + IsCause: true, + LastCause: true, + Highlighted: line, + }, + { + Number: 99, + Truncated: true, + }, + { + Number: 100, + Content: line, + Highlighted: line, + }, + }, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - system := memoryfs.New() - require.NoError(t, system.WriteFile(test.filename, []byte(test.source), os.ModePerm)) + fsys := fstest.MapFS{ + test.filename: &fstest.MapFile{ + Data: []byte(test.source), + }, + } + meta := iacTypes.NewMetadata( - iacTypes.NewRange(test.filename, test.start, test.end, "", system), + iacTypes.NewRange(test.filename, test.startInnerLine, test.endInnerLine, "", fsys), "", ) - if test.outerStart > 0 { + if test.startOuterLine > 0 { meta = meta.WithParent(iacTypes.NewMetadata( - iacTypes.NewRange(test.filename, test.outerStart, test.outerEnd, "", system), + iacTypes.NewRange(test.filename, test.startOuterLine, test.endOuterLine, "", fsys), "", )) } result := &Result{ - annotation: test.annotation, - metadata: meta, - fsPath: test.filename, + metadata: meta, + fsPath: test.filename, } code, err := result.GetCode(test.options...) if test.wantErr { @@ -262,3 +331,72 @@ func TestResult_GetCode(t *testing.T) { } } + +func TestCode_IsCauseMultiline(t *testing.T) { + + tests := []struct { + name string + code Code + expected bool + }{ + { + name: "no cause", + code: Code{ + Lines: []Line{ + { + Number: 1, + Content: "Test", + Highlighted: "Test", + }, + }, + }, + expected: false, + }, + { + name: "one cause", + code: Code{ + Lines: []Line{ + { + Number: 1, + Content: "Test", + IsCause: true, + Highlighted: "Test", + }, + }, + }, + expected: false, + }, + { + name: "multiple causes", + code: Code{ + Lines: []Line{ + { + Number: 1, + Content: "Test", + IsCause: true, + Highlighted: "Test", + }, + { + Number: 2, + Content: "Test", + IsCause: true, + Highlighted: "Test", + }, + { + Number: 3, + Content: "Test", + IsCause: true, + Highlighted: "Test", + }, + }, + }, + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, tt.code.IsCauseMultiline()) + }) + } +} diff --git a/pkg/iac/scan/highlighting.go b/pkg/iac/scan/highlighting.go index 12bd3fe086cf..df260130d599 100644 --- a/pkg/iac/scan/highlighting.go +++ b/pkg/iac/scan/highlighting.go @@ -34,9 +34,9 @@ var globalCache = &cache{ data: make(map[string][]string), } -func highlight(fsKey, filename string, input []byte, theme string) []string { +func highlight(fsKey, filename string, startLine, endLine int, input, theme string) []string { - key := fmt.Sprintf("%s|%s", fsKey, filename) + key := fmt.Sprintf("%s|%s|%d-%d", fsKey, filename, startLine, endLine) if lines, ok := globalCache.Get(key); ok { return lines } @@ -56,15 +56,13 @@ func highlight(fsKey, filename string, input []byte, theme string) []string { formatter = formatters.Fallback } - // replace windows line endings - input = bytes.ReplaceAll(input, []byte{0x0d}, []byte{}) - iterator, err := lexer.Tokenise(nil, string(input)) + iterator, err := lexer.Tokenise(nil, input) if err != nil { return nil } - buffer := bytes.NewBuffer([]byte{}) - if err := formatter.Format(buffer, style, iterator); err != nil { + var buffer bytes.Buffer + if err := formatter.Format(&buffer, style, iterator); err != nil { return nil } diff --git a/pkg/iac/types/range.go b/pkg/iac/types/range.go index c06a2bf6fe1a..754ee29c9675 100755 --- a/pkg/iac/types/range.go +++ b/pkg/iac/types/range.go @@ -146,3 +146,10 @@ func (r Range) GetFS() fs.FS { func (r Range) GetSourcePrefix() string { return r.sourcePrefix } + +func (r Range) Validate() error { + if r.startLine < 0 || r.endLine < 0 || r.startLine > r.endLine { + return fmt.Errorf("invalid range: %s", r.String()) + } + return nil +}