Skip to content
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ go.work.sum
# Codacy CLI
cli-v2
codacy-cli
**/.codacy/logs/
.codacy/
.codacy/*
!.codacy/codacy.yaml
.github/instructions/


#Ignore cursor AI rules
Expand Down
46 changes: 36 additions & 10 deletions cmd/analyze.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ func loadsToolAndPatterns(toolName string, onlyEnabledPatterns bool) (domain.Too
}
var tool domain.Tool
for _, t := range toolsResponse {
if t.ShortName == toolName {
if t.Name == toolName {
tool = t
break
}
Expand All @@ -264,19 +264,19 @@ func getToolName(toolName string, version string) string {
if toolName == "eslint" {
switch majorVersion {
case 7:
return "eslint"
return "ESLint (deprecated)"
case 8:
return "eslint-8"
return "ESLint"
case 9:
return "eslint-9"
return "ESLint9"
}
} else {
if toolName == "pmd" {
switch majorVersion {
case 6:
return "pmd"
return "PMD"
case 7:
return "pmd-7"
return "PMD7"
}
}
}
Expand Down Expand Up @@ -347,14 +347,40 @@ func checkIfConfigExistsAndIsNeeded(toolName string, cliLocalMode bool) error {
}

func runToolByName(toolName string, workDirectory string, pathsToCheck []string, autoFix bool, outputFile string, outputFormat string, tool *plugins.ToolInfo, runtime *plugins.RuntimeInfo, cliLocalMode bool) error {
err := checkIfConfigExistsAndIsNeeded(toolName, cliLocalMode)
if err != nil {
return err
var t *domain.Tool
var usesConfigurationFile = false

//Check if the user is using the repository configuration file
// If the user doesn't provide init flags, we skip fetching repository tools
if initFlags != (domain.InitFlags{}) {
// Get the tool name with the right version e.g. ESLint9, PMD7, etc.
toolRightVersion := getToolName(toolName, tool.Version)

// Get the repository tools to access tool settings
repositoryTools, _ := codacyclient.GetRepositoryTools(initFlags)

// Find the matching tool in repositoryTools
for i, tool := range repositoryTools {
if tool.Name == toolRightVersion {
t = &repositoryTools[i]
usesConfigurationFile = t.Settings.UsesConfigurationFile
break
}
}
}

// If the user is not using configuration file from repository, we check if there's a local one
if !usesConfigurationFile {
err := checkIfConfigExistsAndIsNeeded(toolName, cliLocalMode)
if err != nil {
return err
}
}

switch toolName {
case "eslint":
binaryPath := runtime.Binaries[tool.Runtime]
return tools.RunEslint(workDirectory, tool.InstallDir, binaryPath, pathsToCheck, autoFix, outputFile, outputFormat)
return tools.RunEslint(workDirectory, tool.InstallDir, binaryPath, pathsToCheck, autoFix, outputFile, outputFormat, usesConfigurationFile)
case "trivy":
binaryPath := tool.Binaries[toolName]
return tools.RunTrivy(workDirectory, binaryPath, pathsToCheck, outputFile, outputFormat)
Expand Down
10 changes: 5 additions & 5 deletions cmd/analyze_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,14 @@ func TestGetToolName(t *testing.T) {
expected string
}{
// ESLint cases
{"eslint v7", "eslint", "7.32.0", "eslint"},
{"eslint v8", "eslint", "8.15.0", "eslint-8"},
{"eslint v9", "eslint", "9.1.0", "eslint-9"},
{"eslint v7", "eslint", "7.32.0", "ESLint (deprecated)"},
{"eslint v8", "eslint", "8.15.0", "ESLint"},
{"eslint v9", "eslint", "9.1.0", "ESLint9"},
{"eslint unknown version", "eslint", "10.0.0", "eslint"},

// PMD cases
{"pmd v6", "pmd", "6.55.0", "pmd"},
{"pmd v7", "pmd", "7.0.0", "pmd-7"},
{"pmd v6", "pmd", "6.55.0", "PMD"},
{"pmd v7", "pmd", "7.0.0", "PMD7"},
{"pmd unknown version", "pmd", "8.0.0", "pmd"},

// Other tools should remain unchanged
Expand Down
1 change: 0 additions & 1 deletion cmd/configsetup/repository_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ func createToolConfigurationFiles(tools []domain.Tool, flags domain.InitFlags) e
}

// CreateToolConfigurationFile creates a configuration file for a single tool
// CreateToolConfigurationFile generates a configuration file for a single tool.
func CreateToolConfigurationFile(toolName string, flags domain.InitFlags) error {
// Find the tool UUID by tool name
toolUUID := getToolUUIDByName(toolName)
Expand Down
14 changes: 8 additions & 6 deletions tools/eslintRunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,21 @@ import (
// * Run from the root of the repo we want to analyse
// * NODE_PATH="<the installed eslint path>/node_modules"
// * The local installed ESLint should have the @microsoft/eslint-formatter-sarif installed
func RunEslint(repositoryToAnalyseDirectory string, eslintInstallationDirectory string, nodeBinary string, pathsToCheck []string, autoFix bool, outputFile string, outputFormat string) error {
func RunEslint(repositoryToAnalyseDirectory string, eslintInstallationDirectory string, nodeBinary string, pathsToCheck []string, autoFix bool, outputFile string, outputFormat string, usesConfigurationFile bool) error {
eslintInstallationNodeModules := filepath.Join(eslintInstallationDirectory, "node_modules")
eslintJsPath := filepath.Join(eslintInstallationNodeModules, ".bin", "eslint")

cmd := exec.Command(nodeBinary, eslintJsPath)

// Add config file from tools-configs directory if it exists
if configFile, exists := ConfigFileExists(config.Config, "eslint.config.mjs"); exists {
// For Eslint compatibility with version 8.
// https://eslint.org/docs/v8.x/use/configure/configuration-files-new
cmd.Env = append(cmd.Env, "ESLINT_USE_FLAT_CONFIG=true")
if !usesConfigurationFile {
if configFile, exists := ConfigFileExists(config.Config, "eslint.config.mjs"); exists {
// For Eslint compatibility with version 8.
// https://eslint.org/docs/v8.x/use/configure/configuration-files-new
cmd.Env = append(cmd.Env, "ESLINT_USE_FLAT_CONFIG=true")

cmd.Args = append(cmd.Args, "-c", configFile)
cmd.Args = append(cmd.Args, "-c", configFile)
}
}

if autoFix {
Expand Down
14 changes: 5 additions & 9 deletions tools/lizard/lizardRunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import (
"codacy/cli-v2/domain"
"codacy/cli-v2/tools"
"codacy/cli-v2/utils/logger"
"github.com/sirupsen/logrus"
"encoding/json"
"fmt"
"os"
"os/exec"

"github.com/sirupsen/logrus"
)

// RunLizard runs the Lizard tool and returns any issues found
Expand All @@ -21,7 +22,6 @@ func RunLizard(workDirectory string, binary string, files []string, outputFile s
var patterns []domain.PatternDefinition
var errConfigs error


if exists {
// Configuration exists, read from file
patterns, errConfigs = ReadConfig(configFile)
Expand All @@ -35,11 +35,10 @@ func RunLizard(workDirectory string, binary string, files []string, outputFile s
return fmt.Errorf("failed to fetch default patterns: %v", errConfigs)
}
}

if len(patterns) == 0 {
return fmt.Errorf("no valid patterns found in configuration")
}


// Construct base command with lizard module
args := []string{"-m", "lizard", "-V"}

Expand All @@ -50,23 +49,20 @@ func RunLizard(workDirectory string, binary string, files []string, outputFile s
args = append(args, ".")
}


// For non-SARIF output, let Lizard handle file output directly
if outputFormat != "sarif" && outputFile != "" {
args = append(args, "-o", outputFile)
}


// Run the command
cmd := exec.Command(binary, args...)
cmd.Dir = workDirectory


var err error
var stderr bytes.Buffer

cmd.Stderr = &stderr

// For SARIF output, we need to capture and parse the output
if outputFormat == "sarif" {
var stdout bytes.Buffer
Expand Down
50 changes: 25 additions & 25 deletions tools/runnerUtils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func TestConfigFileExistsInToolsConfigDirectory(t *testing.T) {
"Config path should be correctly formed relative path")
}

func TestConfigFileExistsInRepositoryDirectory(t *testing.T) {
func TestConfigFilePrefersToolsConfigDirectory(t *testing.T) {
// Create a test directory structure
tempDir := t.TempDir()
repoDir := filepath.Join(tempDir, "src")
Expand All @@ -49,19 +49,24 @@ func TestConfigFileExistsInRepositoryDirectory(t *testing.T) {
err := os.MkdirAll(configDir, 0755)
assert.NoError(t, err, "Failed to create test directory structure")

// Create a test config file on the repository directory
existingConfigFile := filepath.Join(repoDir, "existing-config.yaml")
err = os.WriteFile(existingConfigFile, []byte("test content"), 0644)
assert.NoError(t, err, "Failed to create test config file")
// Create a test config file in both locations
generatedConfigFile := filepath.Join(configDir, "some-config.yaml")
existingConfigFile := filepath.Join(repoDir, "some-config.yaml")

// Test case: The existing config file gets picked up
configPath, exists := ConfigFileExists(config, "existing-config.yaml")
assert.True(t, exists, "Config file should exist in tools config directory")
assert.Equal(t, filepath.Join(config.RepositoryDirectory(), "existing-config.yaml"), configPath,
"Config path should be correctly formed relative path")
err = os.WriteFile(generatedConfigFile, []byte("tools config content"), 0644)
assert.NoError(t, err, "Failed to create test config file in tools config directory")

err = os.WriteFile(existingConfigFile, []byte("repository config content"), 0644)
assert.NoError(t, err, "Failed to create test config file in repository directory")

// Test case: Config file in tools config directory is preferred
configPath, exists := ConfigFileExists(config, "some-config.yaml")
assert.True(t, exists, "Config file should exist")
assert.Equal(t, filepath.Join(config.ToolsConfigDirectory(), "some-config.yaml"), configPath,
"Config path should prefer tools config directory")
}

func TestConfigFilePrefersToolsConfigDirectory(t *testing.T) {
func TestConfigFileExistsInRepositoryDirectory(t *testing.T) {
// Create a test directory structure
tempDir := t.TempDir()
repoDir := filepath.Join(tempDir, "src")
Expand All @@ -75,21 +80,16 @@ func TestConfigFilePrefersToolsConfigDirectory(t *testing.T) {
err := os.MkdirAll(configDir, 0755)
assert.NoError(t, err, "Failed to create test directory structure")

// Create a test config file in both locations
generatedConfigFile := filepath.Join(configDir, "some-config.yaml")
existingConfigFile := filepath.Join(repoDir, "some-config.yaml")

err = os.WriteFile(generatedConfigFile, []byte("tools config content"), 0644)
assert.NoError(t, err, "Failed to create test config file in tools config directory")

err = os.WriteFile(existingConfigFile, []byte("repository config content"), 0644)
assert.NoError(t, err, "Failed to create test config file in repository directory")
// Create a test config file on the repository directory
existingConfigFile := filepath.Join(repoDir, "existing-config.yaml")
err = os.WriteFile(existingConfigFile, []byte("test content"), 0644)
assert.NoError(t, err, "Failed to create test config file")

// Test case: Config file in tools config directory is preferred
configPath, exists := ConfigFileExists(config, "some-config.yaml")
assert.True(t, exists, "Config file should exist")
assert.Equal(t, filepath.Join(config.ToolsConfigDirectory(), "some-config.yaml"), configPath,
"Config path should prefer tools config directory")
// Test case: The existing config file gets picked up
configPath, exists := ConfigFileExists(config, "existing-config.yaml")
assert.True(t, exists, "Config file should exist in tools config directory")
assert.Equal(t, filepath.Join(config.RepositoryDirectory(), "existing-config.yaml"), configPath,
"Config path should be correctly formed relative path")
}

func TestConfigFileDoesNotExist(t *testing.T) {
Expand Down