@@ -2,67 +2,219 @@ package tools
22
33import (
44 _ "embed"
5+ "encoding/xml"
56 "fmt"
67 "strings"
78)
89
910//go:embed pmd/default-ruleset.xml
1011var 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
12174func 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}
0 commit comments