From 2638d9face009c12153caf88c49361865aae8efa Mon Sep 17 00:00:00 2001 From: slipknois Date: Mon, 1 Dec 2025 11:20:38 -0300 Subject: [PATCH 1/5] Update codequality.go adjust lines properties on gitlab report --- internal/gitlab/codequality.go | 64 +++++++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 13 deletions(-) diff --git a/internal/gitlab/codequality.go b/internal/gitlab/codequality.go index e03de2d..0b8f2eb 100644 --- a/internal/gitlab/codequality.go +++ b/internal/gitlab/codequality.go @@ -18,13 +18,12 @@ package gitlab import ( "crypto/sha256" "fmt" + "strings" "github.com/google/yamlfmt" ) -// CodeQuality represents a single code quality finding. -// -// Documentation: https://docs.gitlab.com/ee/ci/testing/code_quality.html#code-quality-report-format +// CodeQuality represents a Code Quality finding. type CodeQuality struct { Description string `json:"description,omitempty"` Name string `json:"check_name,omitempty"` @@ -33,19 +32,26 @@ type CodeQuality struct { Location Location `json:"location,omitempty"` } -// Location is the location of a Code Quality finding. +// Location now includes Lines for GitLab compatibility. type Location struct { - Path string `json:"path,omitempty"` + Path string `json:"path,omitempty"` + Lines *Lines `json:"lines,omitempty"` } -// NewCodeQuality creates a new CodeQuality object from a yamlfmt.FileDiff. -// -// If the file did not change, i.e. the diff is empty, an empty struct and false is returned. +// Lines follows the GitLab Code Quality schema. +type Lines struct { + Begin int `json:"begin"` + End int `json:"end"` +} + +// NewCodeQuality creates a new report entry based on a diff. func NewCodeQuality(diff yamlfmt.FileDiff) (CodeQuality, bool) { if !diff.Diff.Changed() { return CodeQuality{}, false } + begin, end := detectChangedLine(&diff) + return CodeQuality{ Description: "Not formatted correctly, run yamlfmt to resolve.", Name: "yamlfmt", @@ -53,21 +59,52 @@ func NewCodeQuality(diff yamlfmt.FileDiff) (CodeQuality, bool) { Severity: Major, Location: Location{ Path: diff.Path, + Lines: &Lines{ + Begin: begin, + End: end, + }, }, }, true } -// fingerprint returns a 256-bit SHA256 hash of the original unformatted file. -// This is used to uniquely identify a code quality finding. +// detectChangedLine finds the first line that differs between original and formatted content. +func detectChangedLine(diff *yamlfmt.FileDiff) (begin int, end int) { + original := strings.Split(diff.Diff.Original, "\n") + formatted := strings.Split(diff.Diff.Formatted, "\n") + + max := len(original) + if len(formatted) > max { + max = len(formatted) + } + + for i := 0; i < max; i++ { + origLine := "" + fmtLine := "" + + if i < len(original) { + origLine = original[i] + } + if i < len(formatted) { + fmtLine = formatted[i] + } + + if origLine != fmtLine { + lineNumber := i + 1 + return lineNumber, lineNumber + } + } + + // fallback (should not happen because diff.Changed() was true) + return 1, 1 +} + +// fingerprint returns SHA256 of original file. func fingerprint(diff yamlfmt.FileDiff) string { hash := sha256.New() - fmt.Fprint(hash, diff.Diff.Original) - return fmt.Sprintf("%x", hash.Sum(nil)) //nolint:perfsprint } -// Severity is the severity of a code quality finding. type Severity string const ( @@ -77,3 +114,4 @@ const ( Critical Severity = "critical" Blocker Severity = "blocker" ) + From 01d83f371ab79f5cdd6d2589943ab070e971d40a Mon Sep 17 00:00:00 2001 From: slipknois Date: Mon, 1 Dec 2025 11:41:04 -0300 Subject: [PATCH 2/5] Solving missing line param A compliant report need to output lines on "location" object to work properly as doc in link show us: https://docs.gitlab.com/ci/testing/code_quality/ This commit write de "lines" property properly. If "location" object don't output the line, the gitlab show a message saying "Failed to load Code Quality report" --- internal/gitlab/codequality.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/internal/gitlab/codequality.go b/internal/gitlab/codequality.go index 0b8f2eb..e107bd5 100644 --- a/internal/gitlab/codequality.go +++ b/internal/gitlab/codequality.go @@ -32,7 +32,7 @@ type CodeQuality struct { Location Location `json:"location,omitempty"` } -// Location now includes Lines for GitLab compatibility. +// Location is the location of a Code Quality finding. type Location struct { Path string `json:"path,omitempty"` Lines *Lines `json:"lines,omitempty"` @@ -44,7 +44,9 @@ type Lines struct { End int `json:"end"` } -// NewCodeQuality creates a new report entry based on a diff. +// NewCodeQuality creates a new CodeQuality object from a yamlfmt.FileDiff. +// +// If the file did not change, i.e. the diff is empty, an empty struct and false is returned. func NewCodeQuality(diff yamlfmt.FileDiff) (CodeQuality, bool) { if !diff.Diff.Changed() { return CodeQuality{}, false @@ -98,13 +100,15 @@ func detectChangedLine(diff *yamlfmt.FileDiff) (begin int, end int) { return 1, 1 } -// fingerprint returns SHA256 of original file. +// fingerprint returns a 256-bit SHA256 hash of the original unformatted file. +// This is used to uniquely identify a code quality finding. func fingerprint(diff yamlfmt.FileDiff) string { hash := sha256.New() fmt.Fprint(hash, diff.Diff.Original) return fmt.Sprintf("%x", hash.Sum(nil)) //nolint:perfsprint } +// Severity is the severity of a code quality finding. type Severity string const ( From d62fd98d552241252efdd821d8d5cf7d3c0aab9a Mon Sep 17 00:00:00 2001 From: slipknois Date: Mon, 1 Dec 2025 14:26:54 -0300 Subject: [PATCH 3/5] restored the comments to their original state. --- internal/gitlab/codequality.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/gitlab/codequality.go b/internal/gitlab/codequality.go index e107bd5..bec40a6 100644 --- a/internal/gitlab/codequality.go +++ b/internal/gitlab/codequality.go @@ -23,7 +23,9 @@ import ( "github.com/google/yamlfmt" ) -// CodeQuality represents a Code Quality finding. +// CodeQuality represents a single code quality finding. +// +// Documentation: https://docs.gitlab.com/ee/ci/testing/code_quality.html#code-quality-report-format type CodeQuality struct { Description string `json:"description,omitempty"` Name string `json:"check_name,omitempty"` From 2553c2d1ecdc2fbc29e989e053e432b9279cf670 Mon Sep 17 00:00:00 2001 From: Marcio Vieira Date: Tue, 2 Dec 2025 09:18:24 -0300 Subject: [PATCH 4/5] removing propertie end line and adjusting begin int tyoe --- internal/gitlab/codequality.go | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/internal/gitlab/codequality.go b/internal/gitlab/codequality.go index bec40a6..e82cfab 100644 --- a/internal/gitlab/codequality.go +++ b/internal/gitlab/codequality.go @@ -42,8 +42,7 @@ type Location struct { // Lines follows the GitLab Code Quality schema. type Lines struct { - Begin int `json:"begin"` - End int `json:"end"` + Begin *int `json:"begin,omitempty"` } // NewCodeQuality creates a new CodeQuality object from a yamlfmt.FileDiff. @@ -54,7 +53,7 @@ func NewCodeQuality(diff yamlfmt.FileDiff) (CodeQuality, bool) { return CodeQuality{}, false } - begin, end := detectChangedLine(&diff) + begin := detectChangedLine(&diff) return CodeQuality{ Description: "Not formatted correctly, run yamlfmt to resolve.", @@ -64,15 +63,14 @@ func NewCodeQuality(diff yamlfmt.FileDiff) (CodeQuality, bool) { Location: Location{ Path: diff.Path, Lines: &Lines{ - Begin: begin, - End: end, + Begin: &begin, }, }, }, true } // detectChangedLine finds the first line that differs between original and formatted content. -func detectChangedLine(diff *yamlfmt.FileDiff) (begin int, end int) { +func detectChangedLine(diff *yamlfmt.FileDiff) (begin int) { original := strings.Split(diff.Diff.Original, "\n") formatted := strings.Split(diff.Diff.Formatted, "\n") @@ -94,12 +92,12 @@ func detectChangedLine(diff *yamlfmt.FileDiff) (begin int, end int) { if origLine != fmtLine { lineNumber := i + 1 - return lineNumber, lineNumber + return lineNumber } } // fallback (should not happen because diff.Changed() was true) - return 1, 1 + return 1 } // fingerprint returns a 256-bit SHA256 hash of the original unformatted file. @@ -120,4 +118,3 @@ const ( Critical Severity = "critical" Blocker Severity = "blocker" ) - From 53d0c233b621b4287783620a36e34db2f3775ac9 Mon Sep 17 00:00:00 2001 From: Marcio Vieira Date: Tue, 2 Dec 2025 14:33:59 -0300 Subject: [PATCH 5/5] including test files, adjustind begin and end lines to work properly --- internal/gitlab/codequality.go | 26 ++- internal/gitlab/codequality_test.go | 193 ++++++++++++++++++++ testdata/gitlab/changed_line/formatted.yaml | 18 ++ testdata/gitlab/changed_line/original.yaml | 18 ++ 4 files changed, 249 insertions(+), 6 deletions(-) create mode 100644 testdata/gitlab/changed_line/formatted.yaml create mode 100644 testdata/gitlab/changed_line/original.yaml diff --git a/internal/gitlab/codequality.go b/internal/gitlab/codequality.go index e82cfab..21ac511 100644 --- a/internal/gitlab/codequality.go +++ b/internal/gitlab/codequality.go @@ -43,6 +43,7 @@ type Location struct { // Lines follows the GitLab Code Quality schema. type Lines struct { Begin *int `json:"begin,omitempty"` + End *int `json:"end,omitempty"` } // NewCodeQuality creates a new CodeQuality object from a yamlfmt.FileDiff. @@ -53,7 +54,7 @@ func NewCodeQuality(diff yamlfmt.FileDiff) (CodeQuality, bool) { return CodeQuality{}, false } - begin := detectChangedLine(&diff) + begin, end := detectChangedLines(&diff) return CodeQuality{ Description: "Not formatted correctly, run yamlfmt to resolve.", @@ -64,13 +65,14 @@ func NewCodeQuality(diff yamlfmt.FileDiff) (CodeQuality, bool) { Path: diff.Path, Lines: &Lines{ Begin: &begin, + End: &end, }, }, }, true } -// detectChangedLine finds the first line that differs between original and formatted content. -func detectChangedLine(diff *yamlfmt.FileDiff) (begin int) { +// detectChangedLines finds the first and last lines that differ between original and formatted content. +func detectChangedLines(diff *yamlfmt.FileDiff) (begin int, end int) { original := strings.Split(diff.Diff.Original, "\n") formatted := strings.Split(diff.Diff.Formatted, "\n") @@ -79,6 +81,9 @@ func detectChangedLine(diff *yamlfmt.FileDiff) (begin int) { max = len(formatted) } + begin = -1 + end = -1 + for i := 0; i < max; i++ { origLine := "" fmtLine := "" @@ -91,13 +96,22 @@ func detectChangedLine(diff *yamlfmt.FileDiff) (begin int) { } if origLine != fmtLine { - lineNumber := i + 1 - return lineNumber + if begin == -1 { + begin = i + 1 + } + end = i + 1 } } // fallback (should not happen because diff.Changed() was true) - return 1 + if begin == -1 { + begin = 1 + } + if end == -1 { + end = 1 + } + + return begin, end } // fingerprint returns a 256-bit SHA256 hash of the original unformatted file. diff --git a/internal/gitlab/codequality_test.go b/internal/gitlab/codequality_test.go index 2ad0513..1936b26 100644 --- a/internal/gitlab/codequality_test.go +++ b/internal/gitlab/codequality_test.go @@ -16,6 +16,8 @@ package gitlab_test import ( "encoding/json" + "os" + "path/filepath" "testing" "github.com/google/go-cmp/cmp" @@ -93,3 +95,194 @@ func TestCodeQuality(t *testing.T) { }) } } + +func TestCodeQuality_DetectChangedLines_MultipleCases(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + original string + formatted string + wantBegin int + wantEnd int + }{ + { + name: "single line change", + original: "a: b", + formatted: "a: b", + wantBegin: 1, + wantEnd: 1, + }, + { + name: "multiple consecutive lines", + original: `line1 +line2: value +line3: value +line4`, + formatted: `line1 +line2: value +line3: value +line4`, + wantBegin: 2, + wantEnd: 3, + }, + { + name: "non-consecutive changes", + original: `line1 +line2: value +line3 +line4: value +line5`, + formatted: `line1 +line2: value +line3 +line4: value +line5`, + wantBegin: 2, + wantEnd: 4, + }, + { + name: "change at beginning", + original: `key: value +line2 +line3`, + formatted: `key: value +line2 +line3`, + wantBegin: 1, + wantEnd: 1, + }, + { + name: "change at end", + original: `line1 +line2 +key: value`, + formatted: `line1 +line2 +key: value`, + wantBegin: 3, + wantEnd: 3, + }, + } + + for _, tc := range cases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + diff := yamlfmt.FileDiff{ + Path: "test.yaml", + Diff: &yamlfmt.FormatDiff{ + Original: tc.original, + Formatted: tc.formatted, + }, + } + + cq, ok := gitlab.NewCodeQuality(diff) + if !ok { + t.Fatal("NewCodeQuality() returned false, expected true") + } + + if cq.Location.Lines == nil { + t.Fatal("Location.Lines is nil") + } + + if cq.Location.Lines.Begin == nil { + t.Fatal("Location.Lines.Begin is nil") + } + + if cq.Location.Lines.End == nil { + t.Fatal("Location.Lines.End is nil") + } + + gotBegin := *cq.Location.Lines.Begin + if gotBegin != tc.wantBegin { + t.Errorf("Location.Lines.Begin = %d, want %d", gotBegin, tc.wantBegin) + } + + gotEnd := *cq.Location.Lines.End + if gotEnd != tc.wantEnd { + t.Errorf("Location.Lines.End = %d, want %d", gotEnd, tc.wantEnd) + } + }) + } +} + +func TestCodeQuality_DetectChangedLines(t *testing.T) { + t.Parallel() + + testdataDir := "testdata/gitlab/changed_line" + print(testdataDir) + originalPath := filepath.Join(testdataDir, "original.yaml") + formattedPath := filepath.Join(testdataDir, "formatted.yaml") + + original, err := os.ReadFile(originalPath) + if err != nil { + t.Fatalf("failed to read original file: %v", err) + } + + formatted, err := os.ReadFile(formattedPath) + if err != nil { + t.Fatalf("failed to read formatted file: %v", err) + } + + diff := yamlfmt.FileDiff{ + Path: "testdata/original.yaml", + Diff: &yamlfmt.FormatDiff{ + Original: string(original), + Formatted: string(formatted), + }, + } + + cq, ok := gitlab.NewCodeQuality(diff) + if !ok { + t.Fatal("NewCodeQuality() returned false, expected true") + } + + if cq.Location.Lines == nil { + t.Fatal("Location.Lines is nil") + } + + if cq.Location.Lines.Begin == nil { + t.Fatal("Location.Lines.Begin is nil") + } + + if cq.Location.Lines.End == nil { + t.Fatal("Location.Lines.End is nil") + } + + wantBeginLine := 6 + gotBeginLine := *cq.Location.Lines.Begin + + if gotBeginLine != wantBeginLine { + t.Errorf("Location.Lines.Begin = %d, want %d", gotBeginLine, wantBeginLine) + } + + wantEndLine := 7 + gotEndLine := *cq.Location.Lines.End + + if gotEndLine != wantEndLine { + t.Errorf("Location.Lines.End = %d, want %d", gotEndLine, wantEndLine) + } + + if cq.Location.Path != diff.Path { + t.Errorf("Location.Path = %q, want %q", cq.Location.Path, diff.Path) + } + + if cq.Description == "" { + t.Error("Description is empty") + } + + if cq.Name == "" { + t.Error("Name is empty") + } + + if cq.Fingerprint == "" { + t.Error("Fingerprint is empty") + } + + if cq.Severity == "" { + t.Error("Severity is empty") + } +} diff --git a/testdata/gitlab/changed_line/formatted.yaml b/testdata/gitlab/changed_line/formatted.yaml new file mode 100644 index 0000000..8c237a5 --- /dev/null +++ b/testdata/gitlab/changed_line/formatted.yaml @@ -0,0 +1,18 @@ +# Example configuration file +version: 1.0 + +services: + web: + image: nginx:latest + ports: + - 80:80 + - 443:443 + environment: + - ENV=production + - DEBUG=false + + database: + image: postgres:14 + environment: + - POSTGRES_DB=myapp + - POSTGRES_USER=admin \ No newline at end of file diff --git a/testdata/gitlab/changed_line/original.yaml b/testdata/gitlab/changed_line/original.yaml new file mode 100644 index 0000000..8c237a5 --- /dev/null +++ b/testdata/gitlab/changed_line/original.yaml @@ -0,0 +1,18 @@ +# Example configuration file +version: 1.0 + +services: + web: + image: nginx:latest + ports: + - 80:80 + - 443:443 + environment: + - ENV=production + - DEBUG=false + + database: + image: postgres:14 + environment: + - POSTGRES_DB=myapp + - POSTGRES_USER=admin \ No newline at end of file