Skip to content

Commit c606392

Browse files
committed
feature: Respect the file information for languages from the API CF-1742
1 parent d5b5ef2 commit c606392

File tree

5 files changed

+113
-37
lines changed

5 files changed

+113
-37
lines changed

cmd/analyze.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ type LanguagesConfig struct {
4141
Name string `yaml:"name" json:"name"`
4242
Languages []string `yaml:"languages" json:"languages"`
4343
Extensions []string `yaml:"extensions" json:"extensions"`
44+
Files []string `yaml:"files" json:"files"`
4445
} `yaml:"tools" json:"tools"`
4546
}
4647

@@ -90,18 +91,15 @@ func GetFileExtension(filePath string) string {
9091
return strings.ToLower(filepath.Ext(filePath))
9192
}
9293

93-
// IsToolSupportedForFile checks if a tool supports a given file based on its extension
94+
// IsToolSupportedForFile checks if a tool supports a given file based on its extension or filename
9495
func IsToolSupportedForFile(toolName string, filePath string, langConfig *LanguagesConfig) bool {
9596
if langConfig == nil {
9697
// If no language config is available, assume all tools are supported
9798
return true
9899
}
99100

100101
fileExt := GetFileExtension(filePath)
101-
if fileExt == "" {
102-
// If file has no extension, assume tool is supported
103-
return true
104-
}
102+
fileName := filepath.Base(filePath)
105103

106104
for _, tool := range langConfig.Tools {
107105
if tool.Name == toolName {
@@ -117,6 +115,13 @@ func IsToolSupportedForFile(toolName string, filePath string, langConfig *Langua
117115
}
118116
}
119117

118+
// Check if filename is supported by this tool (exact match)
119+
for _, file := range tool.Files {
120+
if strings.EqualFold(file, fileName) {
121+
return true
122+
}
123+
}
124+
120125
// Extension not found in tool's supported extensions
121126
return false
122127
}

cmd/analyze_test.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,21 +62,31 @@ func TestIsToolSupportedForFile(t *testing.T) {
6262
Name string `yaml:"name" json:"name"`
6363
Languages []string `yaml:"languages" json:"languages"`
6464
Extensions []string `yaml:"extensions" json:"extensions"`
65+
Files []string `yaml:"files" json:"files"`
6566
}{
6667
{
6768
Name: "pylint",
6869
Languages: []string{"Python"},
6970
Extensions: []string{".py"},
71+
Files: []string{},
72+
},
73+
{
74+
Name: "eslint",
75+
Languages: []string{"JavaScript", "TypeScript"},
76+
Extensions: []string{},
77+
Files: []string{},
7078
},
7179
{
7280
Name: "cppcheck",
7381
Languages: []string{"C", "CPP"},
7482
Extensions: []string{".c", ".cpp", ".h", ".hpp"},
83+
Files: []string{},
7584
},
7685
{
7786
Name: "trivy",
7887
Languages: []string{"Multiple"},
79-
Extensions: []string{},
88+
Extensions: []string{".yaml", ".yml"},
89+
Files: []string{"requirements.txt"},
8090
},
8191
},
8292
}
@@ -111,7 +121,7 @@ func TestIsToolSupportedForFile(t *testing.T) {
111121
},
112122
{
113123
name: "Tool with no extensions specified",
114-
toolName: "trivy",
124+
toolName: "eslint",
115125
filePath: "any.file",
116126
config: langConfig,
117127
want: true,
@@ -130,6 +140,13 @@ func TestIsToolSupportedForFile(t *testing.T) {
130140
config: nil,
131141
want: true,
132142
},
143+
{
144+
name: "Trivy with requirements.txt",
145+
toolName: "trivy",
146+
filePath: "requirements.txt",
147+
config: langConfig,
148+
want: true,
149+
},
133150
}
134151

135152
for _, tt := range tests {

codacy-client/client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ func GetToolsVersions() ([]domain.Tool, error) {
253253
}
254254

255255
// GetRepositoryLanguages fetches the languages for a repository
256-
func GetRepositoryLanguages(initFlags domain.InitFlags) ([]domain.Language, error) {
256+
func GetRepositoryLanguages(initFlags domain.InitFlags) ([]domain.RepositoryLanguage, error) {
257257
baseURL := fmt.Sprintf("%s/api/v3/organizations/%s/%s/repositories/%s/settings/languages",
258258
CodacyApiBase,
259259
initFlags.Provider,

domain/language.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,32 @@
11
package domain
22

3-
// Language represents a language in the Codacy API
4-
type Language struct {
3+
// RepositoryLanguage represents a language in the Codacy API
4+
type RepositoryLanguage struct {
55
Name string `json:"name"`
66
CodacyDefaults []string `json:"codacyDefaults"`
77
Extensions []string `json:"extensions"`
8+
DefaultFiles []string `json:"defaultFiles"`
89
Enabled bool `json:"enabled"`
910
Detected bool `json:"detected"`
1011
}
1112

1213
// LanguagesResponse represents the structure of the languages response
1314
type LanguagesResponse struct {
14-
Languages []Language `json:"languages"`
15+
Languages []RepositoryLanguage `json:"languages"`
16+
}
17+
18+
// Language represents a processed language with combined extensions and files
19+
type Language struct {
20+
Name string
21+
Extensions []string
22+
Files []string
1523
}
1624

1725
// LanguageTool represents a language tool with its file extensions from the API
1826
type LanguageTool struct {
1927
Name string `json:"name"`
2028
FileExtensions []string `json:"fileExtensions"`
29+
Files []string `json:"files"`
2130
}
2231

2332
// LanguageToolsResponse represents the structure of the language tools API response
@@ -30,6 +39,7 @@ type ToolLanguageInfo struct {
3039
Name string `yaml:"name"`
3140
Languages []string `yaml:"languages,flow"`
3241
Extensions []string `yaml:"extensions,flow"`
42+
Files []string `yaml:"files,flow"`
3343
}
3444

3545
// LanguagesConfig represents the structure of the languages configuration file

tools/language_config.go

Lines changed: 70 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ func buildToolLanguageInfoFromAPI() (map[string]domain.ToolLanguageInfo, error)
4444
languageExtensionsMap[strings.ToLower(langTool.Name)] = langTool.FileExtensions
4545
}
4646

47+
// Create map of language name to files
48+
languageFilesMap := make(map[string][]string)
49+
for _, langTool := range languageTools {
50+
languageFilesMap[strings.ToLower(langTool.Name)] = langTool.Files
51+
}
52+
4753
// Build tool language configurations from API data
4854
result := make(map[string]domain.ToolLanguageInfo)
4955
supportedToolNames := make(map[string]bool)
@@ -72,22 +78,33 @@ func buildToolLanguageInfoFromAPI() (map[string]domain.ToolLanguageInfo, error)
7278
Extensions: []string{},
7379
}
7480

75-
// Build extensions from API language data
81+
// Build extensions and files from API language data
7682
extensionsSet := make(map[string]struct{})
83+
filesSet := make(map[string]struct{})
84+
7785
for _, apiLang := range tool.Languages {
7886
lowerLang := strings.ToLower(apiLang)
7987
if extensions, exists := languageExtensionsMap[lowerLang]; exists {
8088
for _, ext := range extensions {
8189
extensionsSet[ext] = struct{}{}
8290
}
8391
}
92+
if files, exists := languageFilesMap[lowerLang]; exists {
93+
for _, file := range files {
94+
filesSet[file] = struct{}{}
95+
}
96+
}
8497
}
8598

86-
// Convert set to sorted slice
99+
// Convert sets to sorted slices
87100
for ext := range extensionsSet {
88101
configTool.Extensions = append(configTool.Extensions, ext)
89102
}
90103
slices.Sort(configTool.Extensions)
104+
for file := range filesSet {
105+
configTool.Files = append(configTool.Files, file)
106+
}
107+
slices.Sort(configTool.Files)
91108

92109
// Sort languages alphabetically
93110
slices.Sort(configTool.Languages)
@@ -201,18 +218,6 @@ func CreateLanguagesConfigFile(apiTools []domain.Tool, toolsConfigDir string, to
201218

202219
// buildRemoteModeLanguagesConfig builds the languages config for remote mode using repository languages as source of truth
203220
func buildRemoteModeLanguagesConfig(apiTools []domain.Tool, toolIDMap map[string]string, initFlags domain.InitFlags) ([]domain.ToolLanguageInfo, error) {
204-
// Get language file extensions from API
205-
languageTools, err := codacyclient.GetLanguageTools()
206-
if err != nil {
207-
return nil, fmt.Errorf("failed to get language tools from API: %w", err)
208-
}
209-
210-
// Create map of language name to file extensions
211-
languageExtensionsMap := make(map[string][]string)
212-
for _, langTool := range languageTools {
213-
languageExtensionsMap[strings.ToLower(langTool.Name)] = langTool.FileExtensions
214-
}
215-
216221
// Get repository languages - this is the single source of truth for remote mode
217222
repositoryLanguages, err := getRepositoryLanguages(initFlags)
218223
if err != nil {
@@ -232,18 +237,36 @@ func buildRemoteModeLanguagesConfig(apiTools []domain.Tool, toolIDMap map[string
232237
Name: shortName,
233238
Languages: []string{},
234239
Extensions: []string{},
240+
Files: []string{},
235241
}
236242

237243
// Use only languages that exist in the repository
238244
extensionsSet := make(map[string]struct{})
245+
filesSet := make(map[string]struct{})
239246

240247
for _, lang := range tool.Languages {
241248
lowerLang := strings.ToLower(lang)
242-
if repoExts, exists := repositoryLanguages[lowerLang]; exists && len(repoExts) > 0 {
243-
configTool.Languages = append(configTool.Languages, lang)
244-
// Add repository-specific extensions
245-
for _, ext := range repoExts {
246-
extensionsSet[ext] = struct{}{}
249+
if repoLang, exists := repositoryLanguages[lowerLang]; exists {
250+
// Check if this language has either extensions or files
251+
hasExtensions := len(repoLang.Extensions) > 0
252+
hasFiles := len(repoLang.Files) > 0
253+
254+
if hasExtensions || hasFiles {
255+
configTool.Languages = append(configTool.Languages, lang)
256+
257+
// Add repository-specific extensions if they exist
258+
if hasExtensions {
259+
for _, ext := range repoLang.Extensions {
260+
extensionsSet[ext] = struct{}{}
261+
}
262+
}
263+
264+
// Add repository-specific files if they exist
265+
if hasFiles {
266+
for _, file := range repoLang.Files {
267+
filesSet[file] = struct{}{}
268+
}
269+
}
247270
}
248271
}
249272
}
@@ -254,6 +277,12 @@ func buildRemoteModeLanguagesConfig(apiTools []domain.Tool, toolIDMap map[string
254277
}
255278
slices.Sort(configTool.Extensions)
256279

280+
// Convert files set to sorted slice
281+
for file := range filesSet {
282+
configTool.Files = append(configTool.Files, file)
283+
}
284+
slices.Sort(configTool.Files)
285+
257286
// Sort languages alphabetically
258287
slices.Sort(configTool.Languages)
259288

@@ -264,14 +293,14 @@ func buildRemoteModeLanguagesConfig(apiTools []domain.Tool, toolIDMap map[string
264293
return configTools, nil
265294
}
266295

267-
func getRepositoryLanguages(initFlags domain.InitFlags) (map[string][]string, error) {
296+
func getRepositoryLanguages(initFlags domain.InitFlags) (map[string]domain.Language, error) {
268297
response, err := codacyclient.GetRepositoryLanguages(initFlags)
269298
if err != nil {
270299
return nil, fmt.Errorf("failed to get repository languages: %w", err)
271300
}
272301

273-
// Create map to store language name -> combined extensions
274-
result := make(map[string][]string)
302+
// Create map to store language name -> Language struct
303+
result := make(map[string]domain.Language)
275304

276305
// Filter and process languages
277306
for _, lang := range response {
@@ -285,17 +314,32 @@ func getRepositoryLanguages(initFlags domain.InitFlags) (map[string][]string, er
285314
extensions[ext] = struct{}{}
286315
}
287316

288-
// Convert map to slice
317+
// Combine and deduplicate files
318+
files := make(map[string]struct{})
319+
for _, file := range lang.DefaultFiles {
320+
files[file] = struct{}{}
321+
}
322+
323+
// Convert extension map to slice
289324
extSlice := make([]string, 0, len(extensions))
290325
for ext := range extensions {
291326
extSlice = append(extSlice, ext)
292327
}
293-
294-
// Sort extensions for consistent ordering in the config file
295328
slices.Sort(extSlice)
296329

330+
// Convert files map to slice
331+
fileSlice := make([]string, 0, len(files))
332+
for file := range files {
333+
fileSlice = append(fileSlice, file)
334+
}
335+
slices.Sort(fileSlice)
336+
297337
// Add to result map with lowercase key for case-insensitive matching
298-
result[strings.ToLower(lang.Name)] = extSlice
338+
result[strings.ToLower(lang.Name)] = domain.Language{
339+
Name: lang.Name,
340+
Extensions: extSlice,
341+
Files: fileSlice,
342+
}
299343
}
300344
}
301345

0 commit comments

Comments
 (0)