Skip to content

Commit 8bc9720

Browse files
feature: PMD install and run [PLUTO-1373]
1 parent 557ff3d commit 8bc9720

File tree

11 files changed

+441
-4
lines changed

11 files changed

+441
-4
lines changed

.codacy/codacy.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ runtimes:
44
tools:
55
66
7+
78

.cursor/rules/cursor.mdc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
description:
3+
globs:
4+
alwaysApply: true
5+
---
6+
7+
# Your rule content
8+
9+
## Code Style Guidelines
10+
- **Imports**: Standard lib first, external packages second, internal last
11+
- **Naming**: PascalCase for exported (public), camelCase for unexported (private)
12+
- **Error handling**: Return errors as last value, check with `if err != nil`
13+
- **Testing**: Use testify/assert package for assertions
14+
- **Package organization**: Keep related functionality in dedicated packages
15+
- **Documentation**: Document all exported functions, types, and packages
16+
- **Commit messages**: Start with verb, be concise and descriptive
17+
18+
## Project Structure
19+
- `cmd/`: CLI command implementations
20+
- `config/`: Configuration handling
21+
- `tools/`: Tool-specific implementations
22+
- `utils/`: Utility functions

cmd/analyze.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ var outputFormat string
2020
var sarifPath string
2121
var commitUuid string
2222
var projectToken string
23+
var pmdRulesetFile string
2324

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

@@ -203,6 +205,16 @@ func runTrivyAnalysis(workDirectory string, pathsToCheck []string, outputFile st
203205
}
204206
}
205207

208+
func runPmdAnalysis(workDirectory string, pathsToCheck []string, outputFile string, outputFormat string) {
209+
pmd := config.Config.Tools()["pmd"]
210+
pmdBinary := pmd.Binaries["pmd"]
211+
212+
err := tools.RunPmd(workDirectory, pmdBinary, pathsToCheck, outputFile, outputFormat, pmdRulesetFile)
213+
if err != nil {
214+
log.Fatalf("Error running PMD: %v", err)
215+
}
216+
}
217+
206218
func runPylintAnalysis(workDirectory string, pathsToCheck []string, outputFile string, outputFormat string) {
207219
pylint := config.Config.Tools()["pylint"]
208220

@@ -235,6 +247,8 @@ var analyzeCmd = &cobra.Command{
235247
runEslintAnalysis(workDirectory, args, autoFix, outputFile, outputFormat)
236248
case "trivy":
237249
runTrivyAnalysis(workDirectory, args, outputFile, outputFormat)
250+
case "pmd":
251+
runPmdAnalysis(workDirectory, args, outputFile, outputFormat)
238252
case "pylint":
239253
runPylintAnalysis(workDirectory, args, outputFile, outputFormat)
240254
case "":

plugins/tool-utils.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,23 @@ func ProcessTools(configs []ToolConfig, toolDir string) (map[string]*ToolInfo, e
174174

175175
// Process binary paths
176176
for _, binary := range pluginConfig.Binaries {
177-
binaryPath := path.Join(installDir, binary.Path)
177+
// Process template variables in binary path
178+
tmpl, err := template.New("binary_path").Parse(binary.Path)
179+
if err != nil {
180+
return nil, fmt.Errorf("error parsing binary path template for %s: %w", config.Name, err)
181+
}
182+
183+
var buf bytes.Buffer
184+
err = tmpl.Execute(&buf, struct {
185+
Version string
186+
}{
187+
Version: config.Version,
188+
})
189+
if err != nil {
190+
return nil, fmt.Errorf("error executing binary path template for %s: %w", config.Name, err)
191+
}
192+
193+
binaryPath := filepath.Join(installDir, buf.String())
178194
info.Binaries[binary.Name] = binaryPath
179195
}
180196

plugins/tools/pmd/plugin.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
name: pmd
2+
description: PMD - An extensible cross-language static code analyzer
3+
download:
4+
url_template: https://github.com/pmd/pmd/releases/download/pmd_releases%2F{{.Version}}/pmd-dist-{{.Version}}-bin.zip
5+
file_name_template: pmd-dist-{{.Version}}-bin.zip
6+
extension:
7+
default: .zip
8+
binaries:
9+
- name: pmd
10+
path: pmd-bin-{{.Version}}/bin/pmd

tools/pmdRunner.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package tools
2+
3+
import (
4+
"os"
5+
"os/exec"
6+
)
7+
8+
// RunPmd executes PMD static code analyzer with the specified options
9+
func RunPmd(repositoryToAnalyseDirectory string, pmdBinary string, pathsToCheck []string, outputFile string, outputFormat string, rulesetFile string) error {
10+
cmd := exec.Command(pmdBinary, "check")
11+
12+
// Add ruleset file if provided
13+
if rulesetFile != "" {
14+
cmd.Args = append(cmd.Args, "--rulesets", rulesetFile)
15+
}
16+
17+
// Add format options
18+
if outputFormat == "sarif" {
19+
cmd.Args = append(cmd.Args, "--format", "sarif")
20+
}
21+
22+
if outputFile != "" {
23+
cmd.Args = append(cmd.Args, "--report-file", outputFile)
24+
}
25+
26+
// Add directory to scan
27+
cmd.Args = append(cmd.Args, "--dir", repositoryToAnalyseDirectory)
28+
29+
cmd.Dir = repositoryToAnalyseDirectory
30+
cmd.Stderr = os.Stderr
31+
cmd.Stdout = os.Stdout
32+
33+
err := cmd.Run()
34+
if err != nil {
35+
if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 4 {
36+
// Exit status 4 means violations were found, which is not an error
37+
return nil
38+
}
39+
return err
40+
}
41+
return nil
42+
}

tools/pmdRunner_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package tools
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"os"
7+
"os/exec"
8+
"path/filepath"
9+
"strings"
10+
"testing"
11+
12+
"github.com/stretchr/testify/assert"
13+
)
14+
15+
func TestRunPmdToFile(t *testing.T) {
16+
homeDirectory, err := os.UserHomeDir()
17+
if err != nil {
18+
log.Fatal(err.Error())
19+
}
20+
currentDirectory, err := os.Getwd()
21+
if err != nil {
22+
log.Fatal(err.Error())
23+
}
24+
25+
// Get absolute paths for test files
26+
testDirectory := filepath.Join(currentDirectory, "testdata/repositories/pmd")
27+
tempResultFile := filepath.Join(os.TempDir(), "pmd.sarif")
28+
defer os.Remove(tempResultFile)
29+
30+
// Use absolute paths for repository and ruleset
31+
repositoryToAnalyze := testDirectory
32+
rulesetFile := filepath.Join(testDirectory, "pmd-ruleset.xml")
33+
34+
pmdBinary := filepath.Join(homeDirectory, ".cache/codacy/tools/[email protected]/pmd-bin-7.12.0/bin/pmd")
35+
36+
err = RunPmd(repositoryToAnalyze, pmdBinary, nil, tempResultFile, "sarif", rulesetFile)
37+
// PMD returns exit status 4 when violations are found, which is expected in our test
38+
if err != nil {
39+
if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() != 4 {
40+
t.Fatalf("Failed to run PMD: %v", err)
41+
}
42+
}
43+
44+
// Check if the output file was created
45+
obtainedSarifBytes, err := os.ReadFile(tempResultFile)
46+
if err != nil {
47+
t.Fatalf("Failed to read output file: %v", err)
48+
}
49+
obtainedSarif := string(obtainedSarifBytes)
50+
filePrefix := "file://" + currentDirectory + "/"
51+
fmt.Println(filePrefix)
52+
actualSarif := strings.ReplaceAll(obtainedSarif, filePrefix, "")
53+
actualSarif = strings.TrimSpace(actualSarif)
54+
55+
// Read the expected SARIF
56+
expectedSarifFile := filepath.Join(testDirectory, "expected.sarif")
57+
expectedSarifBytes, err := os.ReadFile(expectedSarifFile)
58+
if err != nil {
59+
log.Fatal(err)
60+
}
61+
expectedSarif := strings.TrimSpace(string(expectedSarifBytes))
62+
63+
assert.Equal(t, expectedSarif, actualSarif, "output did not match expected")
64+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
2+
/**
3+
* This class demonstrates various PMD rule violations.
4+
* It is used for testing PMD's rule detection capabilities.
5+
*
6+
* @author Codacy Test Team
7+
* @version 1.0
8+
*/
9+
10+
package tools.testdata.repositories.pmd;
11+
12+
/**
13+
* A class that demonstrates PMD rule violations.
14+
*/
15+
public class RulesBreaker {
16+
17+
// Breaking naming convention rules
18+
private int x;
19+
20+
}

0 commit comments

Comments
 (0)