Skip to content

Commit ca4973a

Browse files
SONARTEXT-66 Update generation process for new rules to remove checks with keys that aren't in use anymore (#196)
1 parent 766fce5 commit ca4973a

File tree

3 files changed

+157
-31
lines changed

3 files changed

+157
-31
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,20 @@ cd ../sonarpedia-text
5050
java -jar ../../sonar-rule-api/target/rule-api-2.4.0-SNAPSHOT.jar update
5151
```
5252

53+
### Generate files to include new secrets
54+
55+
After the change, addition or removal of secret specifications, this script can be run to generate the Java classes that are needed
56+
for the inclusion or deletion of these secrets.
57+
58+
To run this script you should specify the file name of the rule-api jar `<ruleApiFileName>`, otherwise a default value of `rule-api-snap.jar` will be assumed.
59+
The rule-api jar needs to be located in the root folder of the project.
60+
61+
As we use the enforcer plugin to define a file size of the build, this can lead to test failures after adding new secret specifications.
62+
The `<minsize>` and `<maxsize>` can be changed in `sonar-text-plugin/pom.xml`.
63+
```shell
64+
./secretSpecificationInclusionGenerator.sh <ruleApiFileName>
65+
```
66+
5367
### License
5468

5569
Copyright 2012-2021 SonarSource.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/bin/bash
2+
3+
# Specification of the rules-api jar can be done in the call of this script as the first argument
4+
if [[ "$1" ]]; then
5+
ruleapifilename=$1
6+
else
7+
ruleapifilename="rule-api-snap.jar"
8+
fi
9+
10+
echo ""
11+
echo "--------- Generation of Java classes ---------"
12+
echo ""
13+
mvn test -Dtest=UpdatingSpecificationFilesGenerator#firstStep
14+
15+
mvn test -Dtest=UpdatingSpecificationFilesGenerator#secondStep
16+
17+
echo ""
18+
echo "---------Creating current license headers---------"
19+
echo ""
20+
mvn license:format
21+
22+
echo ""
23+
echo "---------Formatting code---------"
24+
echo ""
25+
mvn spotless:apply
26+
27+
cd sonarpedia-secrets
28+
29+
# Generated file where rspec keys to update are stored.
30+
# This file will be generated at the secondStep of the generation of java classes
31+
input="../sonar-text-plugin/src/test/resources/templates/rspecKeysToUpdate.txt"
32+
33+
while IFS= read -r line
34+
do
35+
echo ""
36+
echo "--------- Generating rspec files for: $line ---------"
37+
java -jar ../$ruleapifilename generate -rule $line
38+
echo ""
39+
done < "$input"
40+
41+
rm ../sonar-text-plugin/src/test/resources/templates/rspecKeysToUpdate.txt
42+
43+
echo ""
44+
echo "---------Finished Generation process---------"
45+
echo "If the build process fails, this could be due to the enforcer-plugin."
46+
echo "In this case set the maxSize (sonar-text-plugin/pom.xml) to a higher number"

sonar-text-plugin/src/test/java/org/sonar/plugins/secrets/utils/UpdatingSpecificationFilesGenerator.java

Lines changed: 97 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -30,24 +30,32 @@
3030
import java.util.ArrayList;
3131
import java.util.Collection;
3232
import java.util.Collections;
33+
import java.util.HashMap;
34+
import java.util.HashSet;
3335
import java.util.List;
3436
import java.util.Map;
3537
import java.util.Set;
3638
import java.util.stream.Collectors;
37-
3839
import org.junit.jupiter.api.Test;
39-
import org.sonar.api.internal.apachecommons.io.FileUtils;
4040
import org.slf4j.Logger;
4141
import org.slf4j.LoggerFactory;
42+
import org.sonar.api.internal.apachecommons.io.FileUtils;
4243
import org.sonar.plugins.common.Check;
4344
import org.sonar.plugins.secrets.SecretsRulesDefinition;
4445
import org.sonar.plugins.secrets.api.SpecificationLoader;
4546
import org.sonar.plugins.secrets.configuration.model.Rule;
4647

47-
// This name is intentionally not ending in "Test" to not get picked up automatically
48+
// This name is intentionally not ending in "Test" to not get picked up automatically by maven
4849
@SuppressWarnings("java:S3577")
4950
class UpdatingSpecificationFilesGenerator {
5051

52+
private final static String CHECK_TESTS_PATH_PREFIX = String.join(File.separator, "src", "test", "java", "org", "sonar", "plugins", "secrets", "checks");
53+
private final static String SECRETS_MODULE_PATH_PREFIX = String.join(File.separator, "src", "main", "java", "org", "sonar", "plugins", "secrets");
54+
private final static String SECRETS_MODULE_RESOURCE_PATH_PREFIX = String.join(File.separator, "src", "main", "resources", "org", "sonar");
55+
private final static String SPECIFICATION_FILES_PATH = String.join(File.separator, SECRETS_MODULE_RESOURCE_PATH_PREFIX, "plugins", "secrets");
56+
private final static String CHECK_PATH_PREFIX = String.join(File.separator, SECRETS_MODULE_PATH_PREFIX, "checks");
57+
private final static String TEMPLATE_PATH_PREFIX = String.join(File.separator, "src", "test", "resources", "templates");
58+
private final static String RSPEC_FILES_PATH_PREFIX = String.join(File.separator, SECRETS_MODULE_RESOURCE_PATH_PREFIX, "l10n", "secrets", "rules", "secrets");
5159
private final Charset charset = StandardCharsets.UTF_8;
5260
private static final Logger LOG = LoggerFactory.getLogger(UpdatingSpecificationFilesGenerator.class);
5361

@@ -62,28 +70,34 @@ void firstStep() {
6270
void secondStep() {
6371
SpecificationLoader specificationLoader = new SpecificationLoader();
6472

65-
List<String> listOfAlreadyExistingKeys = retrieveAlreadyExistingKeys();
66-
Set<String> setOfExistingCheckClassNames = SecretsRulesDefinition.checks().stream().map(Class::getSimpleName).collect(Collectors.toSet());
73+
Map<String, String> existingKeysMappedToFileName = retrieveAlreadyExistingKeys();
6774

6875
Map<String, List<Rule>> rulesMappedToKey = specificationLoader.getRulesMappedToKey();
6976

70-
Set<String> keysToImplementChecksFor = rulesMappedToKey.keySet();
71-
keysToImplementChecksFor.removeAll(listOfAlreadyExistingKeys);
77+
Set<String> keysToImplementChecksFor = new HashSet<>(rulesMappedToKey.keySet());
78+
keysToImplementChecksFor.removeAll(existingKeysMappedToFileName.keySet());
79+
80+
Set<String> keysNotUsedAnymore = new HashSet<>(existingKeysMappedToFileName.keySet());
81+
keysNotUsedAnymore.removeAll(rulesMappedToKey.keySet());
7282

7383
List<String> newCheckNames = new ArrayList<>();
7484
for (String rspecKey : keysToImplementChecksFor) {
7585
String checkName = rulesMappedToKey.get(rspecKey).get(0).getProvider().getMetadata().getName();
76-
checkName = sanitizeCheckName(checkName, setOfExistingCheckClassNames);
86+
checkName = sanitizeCheckName(checkName, rspecKey, existingKeysMappedToFileName);
7787
writeCheckFile(checkName, rspecKey);
7888
writeCheckTestFile(checkName);
7989
newCheckNames.add(checkName);
8090
}
8191

82-
writeUpdatedRulesDefinition(newCheckNames);
92+
Set<String> checkNamesNotUsedAnymore = keysNotUsedAnymore.stream().map(existingKeysMappedToFileName::get).collect(Collectors.toSet());
93+
94+
removeUnusedChecks(keysNotUsedAnymore, existingKeysMappedToFileName);
95+
writeUpdatedRulesDefinition(newCheckNames, checkNamesNotUsedAnymore);
96+
constructFileForRulesAPI(keysToImplementChecksFor);
8397
}
8498

85-
private List<String> retrieveAlreadyExistingKeys() {
86-
List<String> rspecKeys = new ArrayList<>();
99+
private Map<String, String> retrieveAlreadyExistingKeys() {
100+
Map<String, String> keyToFileName = new HashMap<>();
87101

88102
List<Class<?>> checks = SecretsRulesDefinition.checks();
89103
for (Class<?> check : checks) {
@@ -95,26 +109,26 @@ private List<String> retrieveAlreadyExistingKeys() {
95109
LOG.error("Error while retrieving already existing keys");
96110
throw new RuntimeException(e);
97111
}
98-
rspecKeys.add(instantiatedCheck.ruleKey.rule());
112+
keyToFileName.put(instantiatedCheck.ruleKey.rule(), check.getSimpleName());
99113
}
100114

101-
return rspecKeys;
115+
return keyToFileName;
102116
}
103117

104-
private String sanitizeCheckName(String checkName, Set<String> existingClassNames) {
118+
private String sanitizeCheckName(String checkName, String rspecKey, Map<String, String> existingClassNames) {
105119
checkName = checkName.replaceAll("[^a-zA-Z]", "");
106120
checkName = checkName + "Check";
107121

108-
if (existingClassNames.contains(checkName)) {
122+
if (existingClassNames.containsValue(checkName)) {
109123
checkName = checkName.replace("Check", "UniqueNameCheck");
110124
}
111-
existingClassNames.add(checkName);
125+
existingClassNames.put(rspecKey, checkName);
112126
return checkName;
113127
}
114128

115129
private void writeCheckFile(String checkName, String rspecKey) {
116-
Path checkPath = Path.of("src", "main", "java", "org", "sonar", "plugins", "secrets", "checks", checkName + ".java");
117-
Path checkTemplatePath = Path.of("src", "test", "resources", "templates", "GenericCheckTemplate.java");
130+
Path checkPath = Path.of(CHECK_PATH_PREFIX, checkName + ".java");
131+
Path checkTemplatePath = Path.of(TEMPLATE_PATH_PREFIX, "GenericCheckTemplate.java");
118132
try {
119133
Files.copy(checkTemplatePath, checkPath);
120134

@@ -127,13 +141,12 @@ private void writeCheckFile(String checkName, String rspecKey) {
127141
throw new RuntimeException(e);
128142
}
129143

130-
String successMessage = String.format("Successfully generated Check \"%s.java\" with rspecKey %s", checkName, rspecKey);
131-
LOG.info(successMessage);
144+
LOG.info("Successfully generated Check \"{}.java\" with rspecKey {}", checkName, rspecKey);
132145
}
133146

134147
private void writeCheckTestFile(String checkName) {
135-
Path checkTestPath = Path.of("src", "test", "java", "org", "sonar", "plugins", "secrets", "checks", checkName + "Test.java");
136-
Path checkTestTemplatePath = Path.of("src", "test", "resources", "templates", "GenericCheckTestTemplate.java");
148+
Path checkTestPath = Path.of(CHECK_TESTS_PATH_PREFIX, checkName + "Test.java");
149+
Path checkTestTemplatePath = Path.of(TEMPLATE_PATH_PREFIX, "GenericCheckTestTemplate.java");
137150
try {
138151
Files.copy(checkTestTemplatePath, checkTestPath);
139152

@@ -146,19 +159,18 @@ private void writeCheckTestFile(String checkName) {
146159
throw new RuntimeException(e);
147160
}
148161

149-
String successMessage = String.format("Successfully generated Check \"%sTest.java\"", checkName);
150-
LOG.info(successMessage);
162+
LOG.info("Successfully generated Check \"{}Test.java\"", checkName);
151163
}
152164

153165
private void writeSpecificationFileDefinition() {
154-
Path specificationsDirectoy = Path.of("src", "main", "resources", "org", "sonar", "plugins", "secrets", "configuration");
166+
Path specificationsDirectory = Path.of(SPECIFICATION_FILES_PATH, "configuration");
155167
String[] extensionsToSearchFor = new String[] {"yaml"};
156-
Collection<File> files = FileUtils.listFiles(new File(specificationsDirectoy.toUri()), extensionsToSearchFor, false);
168+
Collection<File> files = FileUtils.listFiles(new File(specificationsDirectory.toUri()), extensionsToSearchFor, false);
157169

158170
List<String> listOfFileNames = files.stream().map(File::getName).sorted().collect(Collectors.toList());
159171

160-
Path specificationDefinitionPath = Path.of("src", "main", "java", "org", "sonar", "plugins", "secrets", "SecretsSpecificationFilesDefinition.java");
161-
Path specificationDefinitionTemplatePath = Path.of("src", "test", "resources", "templates", "SecretsSpecificationFilesDefinitionTemplate.java");
172+
Path specificationDefinitionPath = Path.of(SECRETS_MODULE_PATH_PREFIX, "SecretsSpecificationFilesDefinition.java");
173+
Path specificationDefinitionTemplatePath = Path.of(TEMPLATE_PATH_PREFIX, "SecretsSpecificationFilesDefinitionTemplate.java");
162174

163175
try {
164176
Files.copy(specificationDefinitionTemplatePath, specificationDefinitionPath, StandardCopyOption.REPLACE_EXISTING);
@@ -174,16 +186,46 @@ private void writeSpecificationFileDefinition() {
174186
LOG.info("Successfully generated SecretsSpecificationFilesDefinition");
175187
}
176188

177-
private void writeUpdatedRulesDefinition(List<String> checkNames) {
189+
private void removeUnusedChecks(Set<String> keysNotUsedAnymore, Map<String, String> rspecKeysMappedToCheckNames) {
190+
for (String key : keysNotUsedAnymore) {
191+
String filename = rspecKeysMappedToCheckNames.get(key);
192+
193+
if (filename != null) {
194+
removeUnusedCheck(filename, key);
195+
}
196+
}
197+
}
198+
199+
private void removeUnusedCheck(String checkName, String rspecKey) {
200+
Path checkPath = Path.of(CHECK_PATH_PREFIX, checkName + ".java");
201+
Path checkTestPath = Path.of(CHECK_TESTS_PATH_PREFIX, checkName + "Test.java");
202+
Path rspecJson = Path.of(RSPEC_FILES_PATH_PREFIX, rspecKey + ".json");
203+
Path rspecHtml = Path.of(RSPEC_FILES_PATH_PREFIX, rspecKey + ".html");
204+
205+
try {
206+
Files.delete(checkPath);
207+
Files.delete(checkTestPath);
208+
Files.delete(rspecJson);
209+
Files.delete(rspecHtml);
210+
} catch (IOException e) {
211+
LOG.error("Error while deleting Check with name \"" + checkName + "\", please fix manually", e);
212+
throw new RuntimeException(e);
213+
}
214+
215+
LOG.info("Successfully removed Check \"{}\" with rspecKey %s", checkName);
216+
}
217+
218+
private void writeUpdatedRulesDefinition(List<String> checkNames, Set<String> checkNamesNotUsedAnymore) {
178219
Set<String> uniqueCheckNames = SecretsRulesDefinition.checks().stream().map(Class::getSimpleName).collect(Collectors.toSet());
179220
uniqueCheckNames.addAll(checkNames);
221+
uniqueCheckNames.removeAll(checkNamesNotUsedAnymore);
180222

181223
List<String> updatedCheckNames = new ArrayList<>(uniqueCheckNames);
182224

183225
Collections.sort(updatedCheckNames);
184226

185-
Path checkTestTemplatePath = Path.of("src", "test", "resources", "templates", "SecretsRulesDefinitionTemplate.java");
186-
Path rulesDefPath = Path.of("src", "main", "java", "org", "sonar", "plugins", "secrets", "SecretsRulesDefinition.java");
227+
Path checkTestTemplatePath = Path.of(TEMPLATE_PATH_PREFIX, "SecretsRulesDefinitionTemplate.java");
228+
Path rulesDefPath = Path.of(SECRETS_MODULE_PATH_PREFIX, "SecretsRulesDefinition.java");
187229
try {
188230
Files.copy(checkTestTemplatePath, rulesDefPath, StandardCopyOption.REPLACE_EXISTING);
189231

@@ -252,4 +294,28 @@ private String generateImportsFor(List<String> checkNames) {
252294
}
253295
return sb.toString();
254296
}
297+
298+
private void constructFileForRulesAPI(Set<String> keysToUpdateRuleAPIFor) {
299+
Path pathToWriteUpdateFileTo = Path.of(TEMPLATE_PATH_PREFIX, "rspecKeysToUpdate.txt");
300+
String content = generateContentForRulesAPIUpdateFile(keysToUpdateRuleAPIFor);
301+
302+
try {
303+
Files.write(pathToWriteUpdateFileTo, content.getBytes(charset));
304+
} catch (IOException e) {
305+
LOG.error("Error when trying to write update file ruleAPI, please fix manually", e);
306+
throw new RuntimeException(e);
307+
}
308+
309+
LOG.info("Successfully generated rule-api update file");
310+
}
311+
312+
private String generateContentForRulesAPIUpdateFile(Set<String> keysToUpdateRuleAPIFor) {
313+
StringBuilder sb = new StringBuilder();
314+
315+
for (String key : keysToUpdateRuleAPIFor) {
316+
sb.append(key);
317+
sb.append(System.lineSeparator());
318+
}
319+
return sb.toString();
320+
}
255321
}

0 commit comments

Comments
 (0)