]> source.dussan.org Git - sonarqube.git/blob
fc854c79dc1a41969caa8768c269f3a43a2cd33d
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2024 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 3 of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20 package org.sonar.scanner.externalissue;
21
22 import java.nio.file.Path;
23 import java.util.HashSet;
24 import java.util.Set;
25 import javax.annotation.Nullable;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28 import org.sonar.api.scanner.ScannerSide;
29 import org.sonar.core.documentation.DocumentationLinkGenerator;
30
31 @ScannerSide
32 public class ExternalIssueReportValidator {
33   private static final Logger LOGGER = LoggerFactory.getLogger(ExternalIssueReportValidator.class);
34   private static final String ISSUE_RULE_ID = "ruleId";
35   private static final String SEVERITY = "severity";
36   private static final String TYPE = "type";
37   private static final String DOCUMENTATION_SUFFIX = "/analyzing-source-code/importing-external-issues/generic-issue-import-format/";
38   private final DocumentationLinkGenerator documentationLinkGenerator;
39
40   ExternalIssueReportValidator(DocumentationLinkGenerator documentationLinkGenerator) {
41     this.documentationLinkGenerator = documentationLinkGenerator;
42   }
43
44   /**
45    * <p>Since we are supporting deprecated format, we decide which format it is in order by:
46    *   <ul>
47    *     <li>if both 'rules' and 'issues' fields are present, we assume it is CCT format</li>
48    *     <li>if only 'issues' field is present, we assume it is deprecated format</li>
49    *     <li>otherwise we throw exception as an invalid report was detected</li>
50    *   </ul>
51    * </p>
52    */
53   public void validate(ExternalIssueReport report, Path reportPath) {
54     if (report.rules != null && report.issues != null) {
55       Set<String> ruleIds = validateRules(report.rules, reportPath);
56       validateIssuesCctFormat(report.issues, ruleIds, reportPath);
57     } else if (report.rules == null && report.issues != null) {
58       String documentationLink = documentationLinkGenerator.getDocumentationLink(DOCUMENTATION_SUFFIX);
59       LOGGER.warn("External issues were imported with a deprecated format which will be removed soon. " +
60         "Please switch to the newest format to fully benefit from Clean Code: {}", documentationLink);
61       validateIssuesDeprecatedFormat(report.issues, reportPath);
62     } else {
63       throw new IllegalStateException(String.format("Failed to parse report '%s': invalid report detected.", reportPath));
64     }
65   }
66
67   private static void validateIssuesCctFormat(ExternalIssueReport.Issue[] issues, Set<String> ruleIds, Path reportPath) {
68     for (ExternalIssueReport.Issue issue : issues) {
69       mandatoryField(issue.ruleId, ISSUE_RULE_ID, reportPath);
70       checkRuleExistsInReport(ruleIds, issue, reportPath);
71       checkNoField(issue.severity, SEVERITY, reportPath);
72       checkNoField(issue.type, TYPE, reportPath);
73       validateAlwaysRequiredIssueFields(issue, reportPath);
74     }
75   }
76
77   private static void validateIssuesDeprecatedFormat(ExternalIssueReport.Issue[] issues, Path reportPath) {
78     for (ExternalIssueReport.Issue issue : issues) {
79       mandatoryField(issue.ruleId, ISSUE_RULE_ID, reportPath);
80       mandatoryField(issue.severity, SEVERITY, reportPath);
81       mandatoryField(issue.type, TYPE, reportPath);
82       mandatoryField(issue.engineId, "engineId", reportPath);
83       validateAlwaysRequiredIssueFields(issue, reportPath);
84     }
85   }
86
87   private static Set<String> validateRules(ExternalIssueReport.Rule[] rules, Path reportPath) {
88     Set<String> ruleIds = new HashSet<>();
89     for (ExternalIssueReport.Rule rule : rules) {
90       mandatoryField(rule.id, "id", reportPath);
91       mandatoryField(rule.name, "name", reportPath);
92       mandatoryField(rule.engineId, "engineId", reportPath);
93       mandatoryField(rule.cleanCodeAttribute, "cleanCodeAttribute", reportPath);
94       checkImpactsArray(rule.impacts, reportPath);
95
96       if (!ruleIds.add(rule.id)) {
97         throw new IllegalStateException(String.format("Failed to parse report '%s': found duplicate rule ID '%s'.", reportPath, rule.id));
98       }
99     }
100
101     return ruleIds;
102   }
103
104   private static void checkNoField(@Nullable Object value, String fieldName, Path reportPath) {
105     if (value != null) {
106       throw new IllegalStateException(String.format("Deprecated '%s' field found in the following report: '%s'.", fieldName, reportPath));
107     }
108   }
109
110   private static void validateAlwaysRequiredIssueFields(ExternalIssueReport.Issue issue, Path reportPath) {
111     mandatoryField(issue.primaryLocation, "primaryLocation", reportPath);
112     mandatoryFieldPrimaryLocation(issue.primaryLocation.filePath, "filePath", reportPath);
113     mandatoryFieldPrimaryLocation(issue.primaryLocation.message, "message", reportPath);
114
115     if (issue.primaryLocation.textRange != null) {
116       mandatoryFieldPrimaryLocation(issue.primaryLocation.textRange.startLine, "startLine of the text range", reportPath);
117     }
118
119     if (issue.secondaryLocations != null) {
120       for (ExternalIssueReport.Location l : issue.secondaryLocations) {
121         mandatoryFieldSecondaryLocation(l.filePath, "filePath", reportPath);
122         mandatoryFieldSecondaryLocation(l.textRange, "textRange", reportPath);
123         mandatoryFieldSecondaryLocation(l.textRange.startLine, "startLine of the text range", reportPath);
124       }
125     }
126   }
127
128   private static void mandatoryFieldPrimaryLocation(@Nullable Object value, String fieldName, Path reportPath) {
129     if (value == null) {
130       throw new IllegalStateException(String.format("Failed to parse report '%s': missing mandatory field '%s' in the primary location of" +
131         " the issue.", reportPath, fieldName));
132     }
133   }
134
135   private static void mandatoryFieldSecondaryLocation(@Nullable Object value, String fieldName, Path reportPath) {
136     if (value == null) {
137       throw new IllegalStateException(String.format("Failed to parse report '%s': missing mandatory field '%s' in a secondary location of" +
138         " the issue.", reportPath, fieldName));
139     }
140   }
141
142   private static void mandatoryField(@Nullable Object value, String fieldName, Path reportPath) {
143     if (value == null || (value instanceof String string && string.isEmpty())) {
144       throw new IllegalStateException(String.format("Failed to parse report '%s': missing mandatory field '%s'.", reportPath, fieldName));
145     }
146   }
147
148   private static void checkImpactsArray(@Nullable Object[] value, Path reportPath) {
149     mandatoryField(value, "impacts", reportPath);
150     if (value.length == 0) {
151       throw new IllegalStateException(String.format("Failed to parse report '%s': mandatory array '%s' not populated.", reportPath,
152         "impacts"));
153     }
154   }
155
156   private static void checkRuleExistsInReport(Set<String> ruleIds, ExternalIssueReport.Issue issue, Path reportPath) {
157     if (!ruleIds.contains(issue.ruleId)) {
158       throw new IllegalStateException(String.format("Failed to parse report '%s': rule with '%s' not present.", reportPath, issue.ruleId));
159     }
160   }
161 }