Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .codacy/codacy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ runtimes:
tools:
- [email protected]
- [email protected]
- [email protected]
- [email protected]
22 changes: 22 additions & 0 deletions .cursor/rules/cursor.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
description:
globs:
alwaysApply: true
---

# Your rule content

## Code Style Guidelines
- **Imports**: Standard lib first, external packages second, internal last
- **Naming**: PascalCase for exported (public), camelCase for unexported (private)
- **Error handling**: Return errors as last value, check with `if err != nil`
- **Testing**: Use testify/assert package for assertions
- **Package organization**: Keep related functionality in dedicated packages
- **Documentation**: Document all exported functions, types, and packages
- **Commit messages**: Start with verb, be concise and descriptive

## Project Structure
- `cmd/`: CLI command implementations
- `config/`: Configuration handling
- `tools/`: Tool-specific implementations
- `utils/`: Utility functions
14 changes: 14 additions & 0 deletions cmd/analyze.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ var outputFormat string
var sarifPath string
var commitUuid string
var projectToken string
var pmdRulesetFile string

type Sarif struct {
Runs []struct {
Expand Down Expand Up @@ -95,6 +96,7 @@ func init() {
analyzeCmd.Flags().StringVarP(&toolToAnalyze, "tool", "t", "", "Which tool to run analysis with")
analyzeCmd.Flags().StringVar(&outputFormat, "format", "", "Output format (use 'sarif' for SARIF format)")
analyzeCmd.Flags().BoolVar(&autoFix, "fix", false, "Apply auto fix to your issues when available")
analyzeCmd.Flags().StringVar(&pmdRulesetFile, "rulesets", "", "Path to PMD ruleset file")
rootCmd.AddCommand(analyzeCmd)
}

Expand Down Expand Up @@ -203,6 +205,16 @@ func runTrivyAnalysis(workDirectory string, pathsToCheck []string, outputFile st
}
}

func runPmdAnalysis(workDirectory string, pathsToCheck []string, outputFile string, outputFormat string) {
pmd := config.Config.Tools()["pmd"]
pmdBinary := pmd.Binaries["pmd"]

err := tools.RunPmd(workDirectory, pmdBinary, pathsToCheck, outputFile, outputFormat, pmdRulesetFile)
if err != nil {
log.Fatalf("Error running PMD: %v", err)
}
}

func runPylintAnalysis(workDirectory string, pathsToCheck []string, outputFile string, outputFormat string) {
pylint := config.Config.Tools()["pylint"]

Expand Down Expand Up @@ -235,6 +247,8 @@ var analyzeCmd = &cobra.Command{
runEslintAnalysis(workDirectory, args, autoFix, outputFile, outputFormat)
case "trivy":
runTrivyAnalysis(workDirectory, args, outputFile, outputFormat)
case "pmd":
runPmdAnalysis(workDirectory, args, outputFile, outputFormat)
case "pylint":
runPylintAnalysis(workDirectory, args, outputFile, outputFormat)
case "":
Expand Down
18 changes: 17 additions & 1 deletion plugins/tool-utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,23 @@ func ProcessTools(configs []ToolConfig, toolDir string) (map[string]*ToolInfo, e

// Process binary paths
for _, binary := range pluginConfig.Binaries {
binaryPath := path.Join(installDir, binary.Path)
// Process template variables in binary path
tmpl, err := template.New("binary_path").Parse(binary.Path)
if err != nil {
return nil, fmt.Errorf("error parsing binary path template for %s: %w", config.Name, err)
}

var buf bytes.Buffer
err = tmpl.Execute(&buf, struct {
Version string
}{
Version: config.Version,
})
if err != nil {
return nil, fmt.Errorf("error executing binary path template for %s: %w", config.Name, err)
}

binaryPath := filepath.Join(installDir, buf.String())
info.Binaries[binary.Name] = binaryPath
}

Expand Down
10 changes: 10 additions & 0 deletions plugins/tools/pmd/plugin.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name: pmd
description: PMD - An extensible cross-language static code analyzer
download:
url_template: https://github.com/pmd/pmd/releases/download/pmd_releases%2F{{.Version}}/pmd-dist-{{.Version}}-bin.zip
file_name_template: pmd-dist-{{.Version}}-bin.zip
extension:
default: .zip
binaries:
- name: pmd
path: pmd-bin-{{.Version}}/bin/pmd
42 changes: 42 additions & 0 deletions tools/pmdRunner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package tools

import (
"os"
"os/exec"
)

// RunPmd executes PMD static code analyzer with the specified options
func RunPmd(repositoryToAnalyseDirectory string, pmdBinary string, pathsToCheck []string, outputFile string, outputFormat string, rulesetFile string) error {

Check warning on line 9 in tools/pmdRunner.go

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tools/pmdRunner.go#L9

Method RunPmd has 6 parameters (limit is 5)

Check notice on line 9 in tools/pmdRunner.go

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tools/pmdRunner.go#L9

parameter 'pathsToCheck' seems to be unused, consider removing or renaming it as _
cmd := exec.Command(pmdBinary, "check")

Check failure on line 10 in tools/pmdRunner.go

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tools/pmdRunner.go#L10

Detected non-static command inside Command.

Check failure on line 10 in tools/pmdRunner.go

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tools/pmdRunner.go#L10

OS command injection is a critical vulnerability that can lead to a full system compromise as it may allow an adversary to pass in arbitrary commands or arguments to be executed.

// Add ruleset file if provided
if rulesetFile != "" {
cmd.Args = append(cmd.Args, "--rulesets", rulesetFile)
}

// Add format options
if outputFormat == "sarif" {
cmd.Args = append(cmd.Args, "--format", "sarif")
}

if outputFile != "" {
cmd.Args = append(cmd.Args, "--report-file", outputFile)
}

// Add directory to scan
cmd.Args = append(cmd.Args, "--dir", repositoryToAnalyseDirectory)

cmd.Dir = repositoryToAnalyseDirectory
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout

err := cmd.Run()
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 4 {
// Exit status 4 means violations were found, which is not an error
return nil
}
return err
}
return nil
}
64 changes: 64 additions & 0 deletions tools/pmdRunner_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package tools

import (
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"

"github.com/stretchr/testify/assert"
)

func TestRunPmdToFile(t *testing.T) {

Check warning on line 15 in tools/pmdRunner_test.go

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tools/pmdRunner_test.go#L15

Method TestRunPmdToFile has a cyclomatic complexity of 8 (limit is 7)
homeDirectory, err := os.UserHomeDir()
if err != nil {
log.Fatal(err.Error())
}
currentDirectory, err := os.Getwd()
if err != nil {
log.Fatal(err.Error())
}

// Get absolute paths for test files
testDirectory := filepath.Join(currentDirectory, "testdata/repositories/pmd")
tempResultFile := filepath.Join(os.TempDir(), "pmd.sarif")
defer os.Remove(tempResultFile)

// Use absolute paths for repository and ruleset
repositoryToAnalyze := testDirectory
rulesetFile := filepath.Join(testDirectory, "pmd-ruleset.xml")

pmdBinary := filepath.Join(homeDirectory, ".cache/codacy/tools/[email protected]/pmd-bin-7.12.0/bin/pmd")

err = RunPmd(repositoryToAnalyze, pmdBinary, nil, tempResultFile, "sarif", rulesetFile)
// PMD returns exit status 4 when violations are found, which is expected in our test
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() != 4 {
t.Fatalf("Failed to run PMD: %v", err)
}
}

// Check if the output file was created
obtainedSarifBytes, err := os.ReadFile(tempResultFile)
if err != nil {
t.Fatalf("Failed to read output file: %v", err)
}
obtainedSarif := string(obtainedSarifBytes)
filePrefix := "file://" + currentDirectory + "/"
fmt.Println(filePrefix)
actualSarif := strings.ReplaceAll(obtainedSarif, filePrefix, "")
actualSarif = strings.TrimSpace(actualSarif)

// Read the expected SARIF
expectedSarifFile := filepath.Join(testDirectory, "expected.sarif")
expectedSarifBytes, err := os.ReadFile(expectedSarifFile)
if err != nil {
log.Fatal(err)
}
expectedSarif := strings.TrimSpace(string(expectedSarifBytes))

assert.Equal(t, expectedSarif, actualSarif, "output did not match expected")
}
20 changes: 20 additions & 0 deletions tools/testdata/repositories/pmd/RulesBreaker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

/**
* This class demonstrates various PMD rule violations.
* It is used for testing PMD's rule detection capabilities.
*
* @author Codacy Test Team
* @version 1.0
*/

package tools.testdata.repositories.pmd;

/**
* A class that demonstrates PMD rule violations.
*/
public class RulesBreaker {

// Breaking naming convention rules
private int x;

Check warning on line 18 in tools/testdata/repositories/pmd/RulesBreaker.java

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tools/testdata/repositories/pmd/RulesBreaker.java#L18

Avoid unused private fields such as 'x'.

}
Loading