Skip to content

Commit 5b38545

Browse files
wip filtering work
1 parent 7fdf983 commit 5b38545

File tree

3 files changed

+287
-1
lines changed

3 files changed

+287
-1
lines changed

cmd/analyze.go

Lines changed: 138 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ import (
1111
"net/http"
1212
"os"
1313
"path/filepath"
14+
"strings"
1415

1516
"codacy/cli-v2/utils"
1617

1718
"github.com/spf13/cobra"
19+
"gopkg.in/yaml.v3"
1820
)
1921

2022
var outputFile string
@@ -25,6 +27,133 @@ var sarifPath string
2527
var commitUuid string
2628
var projectToken string
2729

30+
// LanguagesConfig represents the structure of the languages configuration file
31+
type LanguagesConfig struct {
32+
Tools []struct {
33+
Name string `yaml:"name" json:"name"`
34+
Languages []string `yaml:"languages" json:"languages"`
35+
Extensions []string `yaml:"extensions" json:"extensions"`
36+
} `yaml:"tools" json:"tools"`
37+
}
38+
39+
// LoadLanguageConfig loads the language configuration from the file
40+
func LoadLanguageConfig() (*LanguagesConfig, error) {
41+
// First, try to load the YAML config
42+
yamlPath := filepath.Join(config.Config.ToolsConfigDirectory(), "languages-config.yaml")
43+
44+
// Check if the YAML file exists
45+
if _, err := os.Stat(yamlPath); err == nil {
46+
data, err := os.ReadFile(yamlPath)
47+
if err != nil {
48+
return nil, fmt.Errorf("failed to read languages configuration file: %w", err)
49+
}
50+
51+
var config LanguagesConfig
52+
if err := yaml.Unmarshal(data, &config); err != nil {
53+
return nil, fmt.Errorf("failed to parse YAML languages configuration file: %w", err)
54+
}
55+
56+
return &config, nil
57+
}
58+
59+
// If YAML file doesn't exist, try the JSON config for backward compatibility
60+
jsonPath := filepath.Join(config.Config.ToolsConfigDirectory(), "languages-config.json")
61+
62+
// Check if the JSON file exists
63+
if _, err := os.Stat(jsonPath); os.IsNotExist(err) {
64+
return nil, fmt.Errorf("languages configuration file not found: neither %s nor %s exists", yamlPath, jsonPath)
65+
}
66+
67+
data, err := os.ReadFile(jsonPath)
68+
if err != nil {
69+
return nil, fmt.Errorf("failed to read JSON languages configuration file: %w", err)
70+
}
71+
72+
var config LanguagesConfig
73+
if err := json.Unmarshal(data, &config); err != nil {
74+
return nil, fmt.Errorf("failed to parse JSON languages configuration file: %w", err)
75+
}
76+
77+
return &config, nil
78+
}
79+
80+
// GetFileExtension extracts the file extension from a path
81+
func GetFileExtension(filePath string) string {
82+
return strings.ToLower(filepath.Ext(filePath))
83+
}
84+
85+
// IsToolSupportedForFile checks if a tool supports a given file based on its extension
86+
func IsToolSupportedForFile(toolName string, filePath string, langConfig *LanguagesConfig) bool {
87+
if langConfig == nil {
88+
// If no language config is available, assume all tools are supported
89+
return true
90+
}
91+
92+
fileExt := GetFileExtension(filePath)
93+
if fileExt == "" {
94+
// If file has no extension, assume tool is supported
95+
return true
96+
}
97+
98+
for _, tool := range langConfig.Tools {
99+
if tool.Name == toolName {
100+
// If tool has no extensions defined, assume it supports all files
101+
if len(tool.Extensions) == 0 {
102+
return true
103+
}
104+
105+
// Check if file extension is supported by this tool
106+
for _, ext := range tool.Extensions {
107+
if strings.EqualFold(ext, fileExt) {
108+
return true
109+
}
110+
}
111+
112+
// Extension not found in tool's supported extensions
113+
return false
114+
}
115+
}
116+
117+
// If tool not found in config, assume it's supported
118+
return true
119+
}
120+
121+
// FilterToolsByLanguageSupport filters tools by language support for the given files
122+
func FilterToolsByLanguageSupport(tools map[string]*plugins.ToolInfo, files []string) map[string]*plugins.ToolInfo {
123+
if len(files) == 0 {
124+
// If no files specified, return all tools
125+
return tools
126+
}
127+
128+
langConfig, err := LoadLanguageConfig()
129+
if err != nil {
130+
log.Printf("Warning: Failed to load language configuration: %v. Running all tools.", err)
131+
return tools
132+
}
133+
134+
result := make(map[string]*plugins.ToolInfo)
135+
136+
// For each tool, check if it supports at least one of the files
137+
for toolName, toolInfo := range tools {
138+
supported := false
139+
140+
for _, file := range files {
141+
if IsToolSupportedForFile(toolName, file, langConfig) {
142+
supported = true
143+
break
144+
}
145+
}
146+
147+
if supported {
148+
result[toolName] = toolInfo
149+
} else {
150+
log.Printf("Skipping %s as it doesn't support the specified file(s)", toolName)
151+
}
152+
}
153+
154+
return result
155+
}
156+
28157
type Sarif struct {
29158
Runs []struct {
30159
Tool struct {
@@ -247,7 +376,15 @@ var analyzeCmd = &cobra.Command{
247376
log.Fatal("No tools configured. Please run 'codacy-cli init' and 'codacy-cli install' first")
248377
}
249378

250-
log.Println("Running all configured tools...")
379+
// Filter tools by language support
380+
toolsToRun = FilterToolsByLanguageSupport(toolsToRun, args)
381+
382+
if len(toolsToRun) == 0 {
383+
log.Println("No tools support the specified file(s). Skipping analysis.")
384+
return
385+
}
386+
387+
log.Println("Running tools for the specified file(s)...")
251388

252389
if outputFormat == "sarif" {
253390
// Create temporary directory for individual tool outputs

cmd/analyze_test.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package cmd
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestGetFileExtension(t *testing.T) {
8+
tests := []struct {
9+
name string
10+
filePath string
11+
want string
12+
}{
13+
{
14+
name: "Python file",
15+
filePath: "test.py",
16+
want: ".py",
17+
},
18+
{
19+
name: "C++ file",
20+
filePath: "test.cpp",
21+
want: ".cpp",
22+
},
23+
{
24+
name: "File with path",
25+
filePath: "/path/to/file.js",
26+
want: ".js",
27+
},
28+
{
29+
name: "File without extension",
30+
filePath: "noextension",
31+
want: "",
32+
},
33+
{
34+
name: "File with uppercase extension",
35+
filePath: "test.PY",
36+
want: ".py",
37+
},
38+
}
39+
40+
for _, tt := range tests {
41+
t.Run(tt.name, func(t *testing.T) {
42+
if got := GetFileExtension(tt.filePath); got != tt.want {
43+
t.Errorf("GetFileExtension() = %v, want %v", got, tt.want)
44+
}
45+
})
46+
}
47+
}
48+
49+
func TestIsToolSupportedForFile(t *testing.T) {
50+
langConfig := &LanguagesConfig{
51+
Tools: []struct {
52+
Name string `yaml:"name" json:"name"`
53+
Languages []string `yaml:"languages" json:"languages"`
54+
Extensions []string `yaml:"extensions" json:"extensions"`
55+
}{
56+
{
57+
Name: "pylint",
58+
Languages: []string{"Python"},
59+
Extensions: []string{".py"},
60+
},
61+
{
62+
Name: "cppcheck",
63+
Languages: []string{"C", "CPP"},
64+
Extensions: []string{".c", ".cpp", ".h", ".hpp"},
65+
},
66+
{
67+
Name: "trivy",
68+
Languages: []string{"Multiple"},
69+
Extensions: []string{},
70+
},
71+
},
72+
}
73+
74+
tests := []struct {
75+
name string
76+
toolName string
77+
filePath string
78+
config *LanguagesConfig
79+
want bool
80+
}{
81+
{
82+
name: "Pylint with Python file",
83+
toolName: "pylint",
84+
filePath: "test.py",
85+
config: langConfig,
86+
want: true,
87+
},
88+
{
89+
name: "Pylint with C++ file",
90+
toolName: "pylint",
91+
filePath: "test.cpp",
92+
config: langConfig,
93+
want: false,
94+
},
95+
{
96+
name: "Cppcheck with C++ file",
97+
toolName: "cppcheck",
98+
filePath: "test.cpp",
99+
config: langConfig,
100+
want: true,
101+
},
102+
{
103+
name: "Tool with no extensions specified",
104+
toolName: "trivy",
105+
filePath: "any.file",
106+
config: langConfig,
107+
want: true,
108+
},
109+
{
110+
name: "Unknown tool",
111+
toolName: "unknown",
112+
filePath: "test.py",
113+
config: langConfig,
114+
want: true,
115+
},
116+
{
117+
name: "Nil config",
118+
toolName: "pylint",
119+
filePath: "test.cpp",
120+
config: nil,
121+
want: true,
122+
},
123+
}
124+
125+
for _, tt := range tests {
126+
t.Run(tt.name, func(t *testing.T) {
127+
if got := IsToolSupportedForFile(tt.toolName, tt.filePath, tt.config); got != tt.want {
128+
t.Errorf("IsToolSupportedForFile() = %v, want %v", got, tt.want)
129+
}
130+
})
131+
}
132+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#include <iostream>
2+
#include <string>
3+
#include <unordered map>
4+
using namespace std;
5+
6+
int main() {
7+
string str;
8+
cin >> str;
9+
10+
for (int i = 0; i < str.length(); i++) {
11+
if (str[i] == 'a') {
12+
str[i] = 'b';
13+
}
14+
}
15+
cout << str << endl;
16+
return 0;
17+
}

0 commit comments

Comments
 (0)