Skip to content

Commit f0a5bd7

Browse files
fix upload sarif file (#185)
* fix an issue with the comparison between the tool name and the short name. This is particularly important for the SARIF files during the upload phase. * fix an issue with the URI parser that now takes into consideration the work directory, which is essential to compare with the names of the files in the repository. * created new tests
1 parent 6b54110 commit f0a5bd7

File tree

7 files changed

+233
-85
lines changed

7 files changed

+233
-85
lines changed

cmd/analyze.go

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -251,36 +251,49 @@ func loadsToolAndPatterns(toolName string, onlyEnabledPatterns bool) (domain.Too
251251
}
252252
}
253253
var patterns []domain.PatternConfiguration
254-
patterns, err = codacyclient.GetDefaultToolPatternsConfig(domain.InitFlags{}, tool.Uuid, onlyEnabledPatterns)
254+
patterns, err = codacyclient.GetToolPatternsConfig(domain.InitFlags{}, tool.Uuid, onlyEnabledPatterns)
255255
if err != nil {
256256
fmt.Println("Error:", err)
257257
return domain.Tool{}, []domain.PatternConfiguration{}
258258
}
259259
return tool, patterns
260260
}
261261

262+
var versionedToolNames = map[string]map[int]string{
263+
"eslint": {
264+
7: "ESLint (deprecated)",
265+
8: "ESLint",
266+
9: "ESLint9",
267+
},
268+
"pmd": {
269+
6: "PMD",
270+
7: "PMD7",
271+
},
272+
}
273+
274+
var simpleToolAliases = map[string]string{
275+
"lizard": "Lizard",
276+
"semgrep": "Semgrep",
277+
"pylint": "pylintpython3",
278+
"trivy": "Trivy",
279+
}
280+
262281
func getToolName(toolName string, version string) string {
263282
majorVersion := getMajorVersion(version)
264-
if toolName == "eslint" {
265-
switch majorVersion {
266-
case 7:
267-
return "ESLint (deprecated)"
268-
case 8:
269-
return "ESLint"
270-
case 9:
271-
return "ESLint9"
272-
}
273-
} else {
274-
if toolName == "pmd" {
275-
switch majorVersion {
276-
case 6:
277-
return "PMD"
278-
case 7:
279-
return "PMD7"
280-
}
283+
284+
// Check for version-specific tool name: for eslint and pmd
285+
if versions, ok := versionedToolNames[toolName]; ok {
286+
if name, ok := versions[majorVersion]; ok {
287+
return name
281288
}
282289
}
283290

291+
// Check for non-versioned tool name alias
292+
if codacyToolName, ok := simpleToolAliases[toolName]; ok {
293+
return codacyToolName
294+
}
295+
296+
// Default: Return the original tool name if no map or version matches
284297
return toolName
285298
}
286299

cmd/configsetup/default_config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ func createDefaultConfigurationsForSpecificTools(discoveredToolNames map[string]
200200
// createToolConfigurationsForUUIDs creates tool configurations for specific UUIDs
201201
func createToolConfigurationsForUUIDs(uuids []string, toolsConfigDir string, initFlags domain.InitFlags) error {
202202
for _, uuid := range uuids {
203-
patternsConfig, err := codacyclient.GetDefaultToolPatternsConfig(initFlags, uuid, true)
203+
patternsConfig, err := codacyclient.GetToolPatternsConfig(initFlags, uuid, true)
204204
if err != nil {
205205
logToolConfigWarning(uuid, "Failed to get default patterns", err)
206206
continue

cmd/configsetup/repository_config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ func CreateToolConfigurationFile(toolName string, flags domain.InitFlags) error
9898
return fmt.Errorf("tool '%s' not found in supported tools", toolName)
9999
}
100100

101-
patternsConfig, err := codacyclient.GetDefaultToolPatternsConfig(flags, toolUUID, true)
101+
patternsConfig, err := codacyclient.GetToolPatternsConfig(flags, toolUUID, true)
102102
if err != nil {
103103
return fmt.Errorf("failed to get default patterns: %w", err)
104104
}

cmd/upload.go

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import (
99
"fmt"
1010
"io"
1111
"net/http"
12+
"net/url"
1213
"os"
14+
"path/filepath"
1315
"strconv"
1416
"strings"
1517

@@ -43,6 +45,47 @@ var uploadResultsCmd = &cobra.Command{
4345
},
4446
}
4547

48+
var sarifShortNameMap = map[string]string{
49+
// The keys here MUST match the exact string found in run.Tool.Driver.Name
50+
"ESLint (deprecated)": "eslint",
51+
"ESLint": "eslint-8",
52+
"ESLint9": "eslint-9",
53+
"PMD": "pmd",
54+
"PMD7": "pmd-7",
55+
"Trivy": "trivy",
56+
"Pylint": "pylintpython3",
57+
"dartanalyzer": "dartanalyzer",
58+
"Semgrep": "semgrep",
59+
"Lizard": "lizard",
60+
"revive": "revive",
61+
}
62+
63+
func getToolShortName(fullName string) string {
64+
if shortName, ok := sarifShortNameMap[fullName]; ok {
65+
return shortName
66+
}
67+
// Fallback: Use the original name if no mapping is found
68+
return fullName
69+
}
70+
71+
func getRelativePath(baseDir string, fullURI string) string {
72+
73+
localPath := fullURI
74+
u, err := url.Parse(fullURI)
75+
if err == nil && u.Scheme == "file" {
76+
// url.Path extracts the local path component correctly
77+
localPath = u.Path
78+
}
79+
relativePath, err := filepath.Rel(baseDir, localPath)
80+
if err != nil {
81+
// Fallback to the normalized absolute path if calculation fails
82+
fmt.Printf("Warning: Could not get relative path for '%s' relative to '%s': %v. Using absolute path.\n", localPath, baseDir, err)
83+
return localPath
84+
}
85+
86+
return relativePath
87+
}
88+
4689
func processSarifAndSendResults(sarifPath string, commitUUID string, projectToken string, apiToken string, tools map[string]*plugins.ToolInfo) {
4790
if projectToken == "" && apiToken == "" && provider == "" && repository == "" {
4891
fmt.Println("Error: api-token, provider and repository are required when project-token is not provided")
@@ -86,7 +129,15 @@ func processSarif(sarif Sarif, tools map[string]*plugins.ToolInfo) [][]map[strin
86129
var codacyIssues []map[string]interface{}
87130
var payloads [][]map[string]interface{}
88131

132+
baseDir, err := os.Getwd()
133+
if err != nil {
134+
fmt.Printf("Error getting current working directory: %v\n", err)
135+
os.Exit(1)
136+
}
137+
89138
for _, run := range sarif.Runs {
139+
//getToolName will take care of mapping sarif tool names to codacy tool names
140+
//especially for eslint and pmd that have multiple versions
90141
var toolName = getToolName(strings.ToLower(run.Tool.Driver.Name), run.Tool.Driver.Version)
91142
tool, patterns := loadsToolAndPatterns(toolName, false)
92143

@@ -98,8 +149,12 @@ func processSarif(sarif Sarif, tools map[string]*plugins.ToolInfo) [][]map[strin
98149
continue
99150
}
100151
for _, location := range result.Locations {
152+
153+
fullURI := location.PhysicalLocation.ArtifactLocation.URI
154+
relativePath := getRelativePath(baseDir, fullURI)
155+
101156
issue := map[string]interface{}{
102-
"source": location.PhysicalLocation.ArtifactLocation.URI,
157+
"source": relativePath,
103158
"line": location.PhysicalLocation.Region.StartLine,
104159
"type": pattern.ID,
105160
"message": result.Message.Text,
@@ -119,8 +174,12 @@ func processSarif(sarif Sarif, tools map[string]*plugins.ToolInfo) [][]map[strin
119174
// Iterate through run.Artifacts and create entries in the results object
120175
for _, artifact := range run.Artifacts {
121176
if artifact.Location.URI != "" {
177+
178+
fullURI := artifact.Location.URI
179+
relativePath := getRelativePath(baseDir, fullURI)
180+
122181
results = append(results, map[string]interface{}{
123-
"filename": artifact.Location.URI,
182+
"filename": relativePath,
124183
"results": []map[string]interface{}{},
125184
})
126185
}
@@ -169,10 +228,10 @@ func processSarif(sarif Sarif, tools map[string]*plugins.ToolInfo) [][]map[strin
169228
}
170229

171230
}
172-
231+
var toolShortName = getToolShortName(toolName)
173232
payload := []map[string]interface{}{
174233
{
175-
"tool": toolName,
234+
"tool": toolShortName,
176235
"issues": map[string]interface{}{
177236
"Success": map[string]interface{}{
178237
"results": results,

cmd/upload_test.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package cmd
2+
3+
import (
4+
"path/filepath"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestGetRelativePath(t *testing.T) {
11+
const baseDir = "/home/user/project/src"
12+
13+
tests := []struct {
14+
name string
15+
baseDir string
16+
fullURI string
17+
expected string
18+
}{
19+
{
20+
name: "1. File URI with standard path",
21+
baseDir: baseDir,
22+
fullURI: "file:///home/user/project/src/lib/file.go",
23+
expected: "lib/file.go",
24+
},
25+
{
26+
name: "2. File URI with baseDir as the file path",
27+
baseDir: baseDir,
28+
fullURI: "file:///home/user/project/src",
29+
expected: ".",
30+
},
31+
{
32+
name: "3. Simple path (no scheme)",
33+
baseDir: baseDir,
34+
fullURI: "/home/user/project/src/main.go",
35+
expected: "main.go",
36+
},
37+
{
38+
name: "4. URI outside baseDir (should return absolute path if relative fails)",
39+
baseDir: baseDir,
40+
fullURI: "file:///etc/config/app.json",
41+
// This is outside of baseDir, so we expect the absolute path starting from the baseDir root
42+
expected: "../../../../etc/config/app.json",
43+
},
44+
{
45+
name: "5. Plain URI with different scheme (should be treated as plain path)",
46+
baseDir: baseDir,
47+
fullURI: "http://example.com/api/v1/file.go",
48+
expected: "http://example.com/api/v1/file.go",
49+
},
50+
{
51+
name: "6. Empty URI",
52+
baseDir: baseDir,
53+
fullURI: "",
54+
expected: "",
55+
},
56+
{
57+
name: "7. Windows path on a file URI (should correctly strip the leading slash from the path component)",
58+
baseDir: "C:\\Users\\dev\\repo",
59+
fullURI: "file:///C:/Users/dev/repo/app/main.go",
60+
expected: "/C:/Users/dev/repo/app/main.go",
61+
},
62+
{
63+
name: "8. URI with spaces (URL encoded)",
64+
baseDir: baseDir,
65+
fullURI: "file:///home/user/project/src/file%20with%20spaces.go",
66+
expected: "file with spaces.go",
67+
},
68+
}
69+
70+
for _, tt := range tests {
71+
t.Run(tt.name, func(t *testing.T) {
72+
actual := getRelativePath(tt.baseDir, tt.fullURI)
73+
expectedNormalized := filepath.FromSlash(tt.expected)
74+
assert.Equal(t, expectedNormalized, actual, "Relative path should match expected")
75+
})
76+
}
77+
}
78+
func TestGetToolShortName(t *testing.T) {
79+
tests := []struct {
80+
name string
81+
input string
82+
expected string
83+
}{
84+
{
85+
name: "MappedTool_ESLint8",
86+
input: "ESLint",
87+
expected: "eslint-8",
88+
},
89+
{
90+
name: "MappedTool_PMD7",
91+
input: "PMD7",
92+
expected: "pmd-7",
93+
},
94+
{
95+
name: "MappedTool_Pylint",
96+
input: "Pylint",
97+
expected: "pylintpython3",
98+
},
99+
{
100+
name: "UnmappedTool_Fallback",
101+
input: "NewToolName",
102+
expected: "NewToolName",
103+
},
104+
{
105+
name: "UnmappedTool_AnotherFallback",
106+
input: "SomeAnalyzer",
107+
expected: "SomeAnalyzer",
108+
},
109+
{
110+
name: "EmptyInput_Fallback",
111+
input: "",
112+
expected: "",
113+
},
114+
{
115+
name: "MappedTool_Deprecated",
116+
input: "ESLint (deprecated)",
117+
expected: "eslint",
118+
},
119+
}
120+
121+
for _, tt := range tests {
122+
t.Run(tt.name, func(t *testing.T) {
123+
actual := getToolShortName(tt.input)
124+
if actual != tt.expected {
125+
t.Errorf("getToolShortName(%q) = %q; want %q", tt.input, actual, tt.expected)
126+
}
127+
})
128+
}
129+
}

codacy-client/client.go

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -170,13 +170,13 @@ func parsePatternConfigurations(response []byte) ([]domain.PatternConfiguration,
170170
return patternConfigurations, pagination.Cursor, nil
171171
}
172172

173-
// GetDefaultToolPatternsConfig fetches the default patterns for a tool
174-
func GetDefaultToolPatternsConfig(initFlags domain.InitFlags, toolUUID string, onlyEnabledPatterns bool) ([]domain.PatternConfiguration, error) {
175-
return GetDefaultToolPatternsConfigWithCodacyAPIBase(CodacyApiBase, initFlags, toolUUID, onlyEnabledPatterns)
173+
// GetToolPatternsConfig fetches the default patterns for a tool
174+
func GetToolPatternsConfig(initFlags domain.InitFlags, toolUUID string, onlyEnabledPatterns bool) ([]domain.PatternConfiguration, error) {
175+
return GetToolPatternsConfigWithCodacyAPIBase(CodacyApiBase, initFlags, toolUUID, onlyEnabledPatterns)
176176
}
177177

178-
// GetDefaultToolPatternsConfigWithCodacyAPIBase fetches the default patterns for a tool, and a base api url
179-
func GetDefaultToolPatternsConfigWithCodacyAPIBase(codacyAPIBaseURL string, initFlags domain.InitFlags, toolUUID string, onlyEnabledPatterns bool) ([]domain.PatternConfiguration, error) {
178+
// GetToolPatternsConfigWithCodacyAPIBase fetches the default patterns for a tool, and a base api url
179+
func GetToolPatternsConfigWithCodacyAPIBase(codacyAPIBaseURL string, initFlags domain.InitFlags, toolUUID string, onlyEnabledPatterns bool) ([]domain.PatternConfiguration, error) {
180180
baseURL := fmt.Sprintf("%s/api/v3/tools/%s/patterns", codacyAPIBaseURL, toolUUID)
181181
if onlyEnabledPatterns {
182182
baseURL += "?enabled=true"
@@ -187,14 +187,7 @@ func GetDefaultToolPatternsConfigWithCodacyAPIBase(codacyAPIBaseURL string, init
187187
return nil, err
188188
}
189189

190-
onlyRecommendedPatterns := make([]domain.PatternConfiguration, 0)
191-
for _, pattern := range allPaterns {
192-
if pattern.PatternDefinition.Enabled {
193-
onlyRecommendedPatterns = append(onlyRecommendedPatterns, pattern)
194-
}
195-
}
196-
197-
return onlyRecommendedPatterns, nil
190+
return allPaterns, nil
198191
}
199192

200193
// GetRepositoryToolPatterns fetches the patterns for a tool in a repository

0 commit comments

Comments
 (0)