Skip to content

Commit 4c9cfaa

Browse files
committed
fix incorrect rule conversion, use cloud impl
1 parent 68b648d commit 4c9cfaa

File tree

2 files changed

+188
-35
lines changed

2 files changed

+188
-35
lines changed

tools/pmdConfigCreator.go

Lines changed: 187 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,67 +2,219 @@ package tools
22

33
import (
44
_ "embed"
5+
"encoding/xml"
56
"fmt"
67
"strings"
78
)
89

910
//go:embed pmd/default-ruleset.xml
1011
var defaultPMDRuleset string
1112

13+
// Parameter represents a rule parameter
14+
type Parameter struct {
15+
Name string
16+
Value string
17+
}
18+
19+
// Rule represents a PMD rule
20+
type Rule struct {
21+
PatternID string
22+
Parameters []Parameter
23+
Enabled bool
24+
}
25+
26+
// RuleSet represents the PMD ruleset XML structure
27+
type RuleSet struct {
28+
XMLName xml.Name `xml:"ruleset"`
29+
Name string `xml:"name,attr"`
30+
Description string `xml:"description"`
31+
Rules []Rule
32+
}
33+
34+
// DeprecatedReferences maps deprecated pattern IDs to their new versions
35+
var DeprecatedReferences = map[string]string{
36+
// Add deprecated pattern mappings here
37+
// Example: "rulesets_java_design_ExcessiveClassLength": "category_java_design_ExcessiveClassLength",
38+
}
39+
40+
// prefixPatternID adds the appropriate prefix to a pattern ID
41+
func prefixPatternID(patternID string) string {
42+
parts := strings.Split(patternID, "_")
43+
44+
// Handle different pattern ID formats
45+
switch len(parts) {
46+
case 2:
47+
// Format: "patternCategory_patternName"
48+
return fmt.Sprintf("category_java_%s_%s", parts[0], parts[1])
49+
case 3:
50+
// Format: "langAlias_patternCategory_patternName"
51+
return fmt.Sprintf("category_%s_%s_%s", parts[0], parts[1], parts[2])
52+
case 4:
53+
// Format: "root_langAlias_patternCategory_patternName"
54+
return fmt.Sprintf("%s_%s_%s_%s", parts[0], parts[1], parts[2], parts[3])
55+
default:
56+
// Return as is if format is unknown
57+
return patternID
58+
}
59+
}
60+
61+
// convertPatternIDToPMD converts a Codacy pattern ID to PMD format
62+
func convertPatternIDToPMD(patternID string) (string, error) {
63+
// Check if this is a deprecated pattern
64+
if newID, ok := DeprecatedReferences[patternID]; ok {
65+
patternID = newID
66+
}
67+
68+
// Handle both formats:
69+
// 1. "java/design/NPathComplexity"
70+
// 2. "PMD_category_java_design_NPathComplexity"
71+
// 3. "PMD_category_apex_security_ApexSharingViolations"
72+
// 4. "PMD_category_plsql_errorprone_TO_TIMESTAMPWithoutDateFormat"
73+
74+
var parts []string
75+
if strings.Contains(patternID, "/") {
76+
parts = strings.Split(patternID, "/")
77+
} else {
78+
// Remove PMD_ prefix if present
79+
id := strings.TrimPrefix(patternID, "PMD_")
80+
// Split by underscore and remove "category" if present
81+
parts = strings.Split(id, "_")
82+
if parts[0] == "category" {
83+
parts = parts[1:]
84+
}
85+
}
86+
87+
if len(parts) < 3 {
88+
return "", fmt.Errorf("invalid pattern ID format: %s", patternID)
89+
}
90+
91+
// Extract language, category, and rule
92+
language := parts[0] // java, apex, etc.
93+
category := parts[1] // design, security, etc.
94+
rule := parts[2] // rule name
95+
96+
// If there are more parts, combine them with the rule name
97+
if len(parts) > 3 {
98+
rule = strings.Join(parts[2:], "_")
99+
}
100+
101+
return fmt.Sprintf("category/%s/%s.xml/%s", language, category, rule), nil
102+
}
103+
104+
// generateRuleXML generates XML for a single rule
105+
func generateRuleXML(rule Rule) (string, error) {
106+
pmdRef, err := convertPatternIDToPMD(rule.PatternID)
107+
if err != nil {
108+
return "", err
109+
}
110+
111+
if len(rule.Parameters) == 0 {
112+
return fmt.Sprintf(` <rule ref="%s"/>`, pmdRef), nil
113+
}
114+
115+
// Generate rule with parameters
116+
var params strings.Builder
117+
for _, param := range rule.Parameters {
118+
if param.Name != "enabled" && param.Name != "version" { // Skip enabled and version parameters
119+
params.WriteString(fmt.Sprintf(`
120+
<property name="%s" value="%s"/>`, param.Name, param.Value))
121+
}
122+
}
123+
124+
return fmt.Sprintf(` <rule ref="%s">
125+
<properties>%s
126+
</properties>
127+
</rule>`, pmdRef, params.String()), nil
128+
}
129+
130+
// ConvertToPMDRuleset converts Codacy rules to PMD ruleset format
131+
func ConvertToPMDRuleset(rules []Rule) (string, error) {
132+
var rulesetXML strings.Builder
133+
rulesetXML.WriteString(`<?xml version="1.0"?>
134+
<ruleset name="Codacy PMD Ruleset"
135+
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
136+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
137+
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
138+
<description>Codacy PMD Ruleset</description>
139+
140+
`)
141+
142+
// Track processed rules to avoid duplicates
143+
processedRules := make(map[string]bool)
144+
145+
for _, rule := range rules {
146+
if !rule.Enabled {
147+
continue
148+
}
149+
150+
pmdRef, err := convertPatternIDToPMD(rule.PatternID)
151+
if err != nil {
152+
return "", fmt.Errorf("error converting pattern ID: %w", err)
153+
}
154+
155+
// Skip if we've already processed this rule
156+
if processedRules[pmdRef] {
157+
continue
158+
}
159+
160+
processedRules[pmdRef] = true
161+
162+
ruleXML, err := generateRuleXML(rule)
163+
if err != nil {
164+
return "", fmt.Errorf("error generating rule XML: %w", err)
165+
}
166+
rulesetXML.WriteString(ruleXML + "\n")
167+
}
168+
169+
rulesetXML.WriteString("</ruleset>")
170+
return rulesetXML.String(), nil
171+
}
172+
173+
// CreatePmdConfig creates a PMD configuration from the provided tool configuration
12174
func CreatePmdConfig(configuration ToolConfiguration) string {
13175
// If no patterns provided, return the default ruleset
14176
if len(configuration.PatternsConfiguration) == 0 {
15177
return defaultPMDRuleset
16178
}
17179

18-
var contentBuilder strings.Builder
19-
20-
// Write XML header and ruleset opening with correct name
21-
contentBuilder.WriteString("<?xml version=\"1.0\"?>\n")
22-
contentBuilder.WriteString("<ruleset name=\"Codacy PMD Ruleset\"\n")
23-
contentBuilder.WriteString(" xmlns=\"http://pmd.sourceforge.net/ruleset/2.0.0\"\n")
24-
contentBuilder.WriteString(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n")
25-
contentBuilder.WriteString(" xsi:schemaLocation=\"http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd\">\n\n")
26-
27-
// Process patterns from configuration
180+
// Convert ToolConfiguration to our Rule format
181+
var rules []Rule
28182
for _, pattern := range configuration.PatternsConfiguration {
29183
// Check if pattern is enabled
30184
patternEnabled := true
31-
var properties []string
185+
var parameters []Parameter
32186

33187
for _, param := range pattern.ParameterConfigurations {
34188
if param.Name == "enabled" && param.Value == "false" {
35189
patternEnabled = false
36190
break
37191
} else if param.Name != "enabled" {
38-
// Store non-enabled parameters for properties
39-
properties = append(properties, fmt.Sprintf(" <property name=\"%s\" value=\"%s\"/>", param.Name, param.Value))
192+
// Store non-enabled parameters
193+
parameters = append(parameters, Parameter{
194+
Name: param.Name,
195+
Value: param.Value,
196+
})
40197
}
41198
}
42199

43-
if patternEnabled {
44-
// Convert pattern ID to correct PMD path format
45-
// e.g., "java/codestyle/AtLeastOneConstructor" -> "category/java/codestyle.xml/AtLeastOneConstructor"
46-
parts := strings.Split(pattern.PatternId, "/")
47-
if len(parts) >= 2 {
48-
category := parts[len(parts)-2]
49-
rule := parts[len(parts)-1]
50-
contentBuilder.WriteString(fmt.Sprintf(" <rule ref=\"category/java/%s.xml/%s\"", category, rule))
51-
52-
// Add properties if any exist
53-
if len(properties) > 0 {
54-
contentBuilder.WriteString(">\n <properties>\n")
55-
for _, prop := range properties {
56-
contentBuilder.WriteString(prop + "\n")
57-
}
58-
contentBuilder.WriteString(" </properties>\n </rule>\n")
59-
} else {
60-
contentBuilder.WriteString("/>\n")
61-
}
62-
}
200+
// Apply prefix to pattern ID if needed
201+
patternID := pattern.PatternId
202+
if !strings.HasPrefix(patternID, "PMD_") && !strings.Contains(patternID, "/") {
203+
patternID = prefixPatternID(patternID)
63204
}
205+
206+
rules = append(rules, Rule{
207+
PatternID: patternID,
208+
Parameters: parameters,
209+
Enabled: patternEnabled,
210+
})
211+
}
212+
213+
// Convert rules to PMD ruleset
214+
rulesetXML, err := ConvertToPMDRuleset(rules)
215+
if err != nil {
216+
return defaultPMDRuleset
64217
}
65218

66-
contentBuilder.WriteString("</ruleset>")
67-
return contentBuilder.String()
219+
return rulesetXML
68220
}

tools/testdata/repositories/pmd/expected-ruleset.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
44
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
55
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
6+
<description>Codacy PMD Ruleset</description>
67

78
<rule ref="category/java/codestyle.xml/AtLeastOneConstructor"/>
89
<rule ref="category/java/design.xml/UnusedPrivateField"/>

0 commit comments

Comments
 (0)