diff --git a/CHANGELOG.md b/CHANGELOG.md index c1e897556..e98dab9be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - add documentation link for each rule in RULES.md file +- Changes the file post-processing process to use Java code instead of a JShell script. This reduces build times and also allows for testing the process. ### Deleted diff --git a/maven-build/README.md b/maven-build/README.md new file mode 100644 index 000000000..e81441587 --- /dev/null +++ b/maven-build/README.md @@ -0,0 +1 @@ +This directory contains processing code used during the Maven build. diff --git a/maven-build/main/java/org/greencodeinitiative/mavenbuild/ruleexporter/Main.java b/maven-build/main/java/org/greencodeinitiative/mavenbuild/ruleexporter/Main.java new file mode 100644 index 000000000..242a5b516 --- /dev/null +++ b/maven-build/main/java/org/greencodeinitiative/mavenbuild/ruleexporter/Main.java @@ -0,0 +1,51 @@ +package org.greencodeinitiative.mavenbuild.ruleexporter; + +import org.greencodeinitiative.mavenbuild.ruleexporter.infra.PrepareResources; + +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; + +import static java.util.Collections.emptyList; +import static java.util.Optional.empty; +import static java.util.Optional.ofNullable; + +public class Main implements Runnable { + private final List args; + + public Main(String[] args) { + this.args = ofNullable(args).map(List::of).orElse(emptyList()); + } + + public static void main(String... args) { + new Main(args).run(); + } + + @Override + public void run() { + Path sourceDir = argAsPath(0, "sourceDir"); + Path targetDir = argAsPath(1, "targetDir"); + new PrepareResources( + sourceDir, + targetDir + ).run(); + } + + private Optional optionalArg(int index) { + if (args.size() <= index) { + return empty(); + } + return Optional.of(args.get(index)); + } + + private String arg(int index, String description) { + if (args.size() <= index) { + throw new IllegalArgumentException("argument " + (index + 1) + " is required: " + description); + } + return optionalArg(index).orElseThrow(() -> new IllegalArgumentException("argument " + (index + 1) + " is required: " + description)); + } + + private Path argAsPath(int index, String description) { + return Path.of(arg(index, description)); + } +} diff --git a/maven-build/main/java/org/greencodeinitiative/mavenbuild/ruleexporter/domain/Rule.java b/maven-build/main/java/org/greencodeinitiative/mavenbuild/ruleexporter/domain/Rule.java new file mode 100644 index 000000000..3a32364bc --- /dev/null +++ b/maven-build/main/java/org/greencodeinitiative/mavenbuild/ruleexporter/domain/Rule.java @@ -0,0 +1,74 @@ +package org.greencodeinitiative.mavenbuild.ruleexporter.domain; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static java.util.Optional.empty; +import static java.util.Optional.of; + +public class Rule { + /** + * Resources to include + */ + private static final Pattern TARGET_RESOURCES = Pattern.compile("^.{1,1000}/(?GCI\\d{1,50})/(?[^/]{1,50})/GCI\\d{1,50}\\.html$"); + + public static Optional createFromHtmlDescription(Path htmlDescription) { + final Matcher matcher = TARGET_RESOURCES.matcher(htmlDescription.toString().replace('\\', '/')); + if (!matcher.find()) { + return empty(); + } + final String ruleKey = matcher.group("ruleKey"); + final Path metadata = htmlDescription.getParent().getParent().resolve(ruleKey + ".json"); + final Path specificMetadata = htmlDescription.getParent().resolve(ruleKey + ".json"); + + if (!Files.isRegularFile(htmlDescription) || !Files.isRegularFile(metadata)) { + return empty(); + } + + return of(new Rule( + matcher.group("language"), + htmlDescription, + metadata, + specificMetadata + )); + } + + private final String language; + private final Path htmlDescription; + private final Path metadata; + private final Path specificMetadata; + + public Rule(String language, Path htmlDescription, Path metadata, Path specificMetadata) { + this.language = language; + this.htmlDescription = htmlDescription; + this.metadata = metadata; + this.specificMetadata = specificMetadata; + } + + public Path getHtmlDescriptionTargetPath(Path targetDir) { + return targetDir.resolve(language).resolve(htmlDescription.getFileName()); + } + + public Path getMetadataTargetPath(Path targetDir) { + return targetDir.resolve(language).resolve(metadata.getFileName()); + } + + public String language() { + return language; + } + + public Path htmlDescription() { + return htmlDescription; + } + + public Path metadata() { + return metadata; + } + + public Path specificMetadata() { + return specificMetadata; + } +} diff --git a/maven-build/main/java/org/greencodeinitiative/mavenbuild/ruleexporter/infra/PrepareResources.java b/maven-build/main/java/org/greencodeinitiative/mavenbuild/ruleexporter/infra/PrepareResources.java new file mode 100644 index 000000000..3b3c7e077 --- /dev/null +++ b/maven-build/main/java/org/greencodeinitiative/mavenbuild/ruleexporter/infra/PrepareResources.java @@ -0,0 +1,97 @@ +package org.greencodeinitiative.mavenbuild.ruleexporter.infra; + +import jakarta.json.Json; +import jakarta.json.JsonMergePatch; +import jakarta.json.JsonObject; +import jakarta.json.JsonReader; +import jakarta.json.JsonValue; +import jakarta.json.JsonWriter; +import org.greencodeinitiative.mavenbuild.ruleexporter.domain.Rule; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.lang.System.Logger.Level.DEBUG; + +public class PrepareResources implements Runnable { + private static final System.Logger LOGGER = System.getLogger("PrepareResources"); + + private final Path sourceDir; + private final Path targetDir; + + public PrepareResources(Path sourceDir, Path targetDir) { + this.sourceDir = sourceDir; + this.targetDir = targetDir; + } + + @Override + public void run() { + getResourcesToCopy().forEach(rule -> { + mergeOrCopyJsonMetadata(rule.metadata(), rule.specificMetadata(), rule.getMetadataTargetPath(targetDir)); + copyFile(rule.htmlDescription(), rule.getHtmlDescriptionTargetPath(targetDir)); + }); + } + + private List getResourcesToCopy() { + try (Stream stream = Files.walk(sourceDir)) { + return stream + .filter(Files::isRegularFile) + .map(Rule::createFromHtmlDescription) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + } catch (IOException e) { + throw new IllegalStateException("Unable to read file", e); + } + } + + void mergeOrCopyJsonMetadata(Path source, Path merge, Path target) { + try { + Files.createDirectories(target.getParent()); + } catch (IOException e) { + throw new ProcessException(e); + } + if (Files.isRegularFile(merge)) { + mergeJsonFile(source, merge, target); + } else { + copyFile(source, target); + } + } + + void mergeJsonFile(Path source, Path merge, Path target) { + LOGGER.log(DEBUG, "Merge: {0} and {1} -> {2}", source, merge, target); + + try ( + JsonReader sourceJsonReader = Json.createReader(Files.newBufferedReader(source)); + JsonReader mergeJsonReader = Json.createReader(Files.newBufferedReader(merge)); + JsonWriter resultJsonWriter = Json.createWriter(Files.newBufferedWriter(target)); + ) { + Files.createDirectories(target.getParent()); + + JsonObject sourceJson = sourceJsonReader.readObject(); + JsonObject mergeJson = mergeJsonReader.readObject(); + + JsonMergePatch mergePatch = Json.createMergePatch(mergeJson); + JsonValue result = mergePatch.apply(sourceJson); + + resultJsonWriter.write(result); + } catch (IOException e) { + throw new ProcessException("cannot process source " + source, e); + } + } + + void copyFile(Path source, Path target) { + try { + LOGGER.log(DEBUG, "Copy: {0} -> {1}", source, target); + Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + throw new ProcessException("unable to copy '" + source + "' to '" + target + "'", e); + } + } +} diff --git a/maven-build/main/java/org/greencodeinitiative/mavenbuild/ruleexporter/infra/ProcessException.java b/maven-build/main/java/org/greencodeinitiative/mavenbuild/ruleexporter/infra/ProcessException.java new file mode 100644 index 000000000..25ede96c3 --- /dev/null +++ b/maven-build/main/java/org/greencodeinitiative/mavenbuild/ruleexporter/infra/ProcessException.java @@ -0,0 +1,11 @@ +package org.greencodeinitiative.mavenbuild.ruleexporter.infra; + +public class ProcessException extends RuntimeException { + public ProcessException(Throwable cause) { + super(cause); + } + + public ProcessException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/maven-build/test/java/org/greencodeinitiative/mavenbuild/ruleexporter/MainTest.java b/maven-build/test/java/org/greencodeinitiative/mavenbuild/ruleexporter/MainTest.java new file mode 100644 index 000000000..7d0788992 --- /dev/null +++ b/maven-build/test/java/org/greencodeinitiative/mavenbuild/ruleexporter/MainTest.java @@ -0,0 +1,38 @@ +package org.greencodeinitiative.mavenbuild.ruleexporter; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; + +class MainTest { + + @Test + void shouldThrowIfThereIsNoSourceDir() { + // Given + Main main = new Main(new String[]{}); + + // When + Throwable thrown = catchThrowable(main::run); + + // Then + assertThat(thrown) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("argument 1 is required: sourceDir"); + } + + @Test + void shouldThrowIfThereIsNoTargetDir() { + // Given + Main main = new Main(new String[]{"someSourceDir"}); + + // When + Throwable thrown = catchThrowable(main::run); + + // Then + assertThat(thrown) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("argument 2 is required: targetDir"); + } + +} diff --git a/maven-build/test/java/org/greencodeinitiative/mavenbuild/ruleexporter/domain/RuleTest.java b/maven-build/test/java/org/greencodeinitiative/mavenbuild/ruleexporter/domain/RuleTest.java new file mode 100644 index 000000000..375e62abe --- /dev/null +++ b/maven-build/test/java/org/greencodeinitiative/mavenbuild/ruleexporter/domain/RuleTest.java @@ -0,0 +1,122 @@ +package org.greencodeinitiative.mavenbuild.ruleexporter.domain; + +import org.assertj.core.api.AbstractObjectAssert; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +class RuleTest { + private final Path htmlDescription = Path.of("some/path/file.html"); + private final Path metadata = Path.of("some/path/metadata.json"); + private final Path specificMetadata = Path.of("some/path/specificMetadata.json"); + private final Path targetDir = Path.of("target"); + + @Test + @DisplayName("Should return an empty Optional when the path does not match the expected pattern") + void createFromHtmlDescriptionShouldReturnEmptyWhenPathDoesNotMatchPattern() { + // Given + Path invalidPath = Path.of("invalid/path/to/file.html"); + + // When + Optional result = Rule.createFromHtmlDescription(invalidPath); + + // Then + assertThat(result).isEmpty(); + } + + @Test + @DisplayName("Should return correct values for all getters") + void gettersShouldReturnCorrectValues() { + // Given + Rule rule = new Rule("java", htmlDescription, metadata, specificMetadata); + + // When & Then + assertThat(rule.language()).isEqualTo("java"); + assertThat(rule.htmlDescription()).isEqualTo(htmlDescription); + assertThat(rule.metadata()).isEqualTo(metadata); + assertThat(rule.specificMetadata()).isEqualTo(specificMetadata); + } + + @Test + @DisplayName("Should return the correct target path for the HTML description") + void getHtmlDescriptionTargetPathShouldReturnCorrectPath() { + // Given + Rule rule = new Rule("java", htmlDescription, metadata, specificMetadata); + Path expectedPath = targetDir.resolve("java").resolve("file.html"); + + // When + Path actualPath = rule.getHtmlDescriptionTargetPath(targetDir); + + // Then + assertThat(actualPath).isEqualTo(expectedPath); + } + + @Test + @DisplayName("Should return the correct target path for the metadata file") + void getMetadataTargetPathShouldReturnCorrectPath() { + // Given + Rule rule = new Rule("java", htmlDescription, metadata, specificMetadata); + Path expectedPath = targetDir.resolve("java").resolve("metadata.json"); + + // When + Path actualPath = rule.getMetadataTargetPath(targetDir); + + // Then + assertThat(actualPath).isEqualTo(expectedPath); + } + + @Test + @DisplayName("Should return empty Optional if htmlDescription file does not exist") + void createFromHtmlDescriptionShouldReturnEmptyIfHtmlFileMissing() { + // Given + Path html = Path.of("/GCI123/java/GCI123.html"); + + // When + Optional result = Rule.createFromHtmlDescription(html); + + // Then + assertThat(result).isEmpty(); + } + + @Test + @DisplayName("Should return empty Optional if metadata file does not exist") + void createFromHtmlDescriptionShouldReturnEmptyIfMetadataFileMissing(@TempDir Path tempDir) throws Exception { + // Given + Path htmlPath = tempDir.resolve("GCI123/java/GCI123.html"); + Files.createDirectories(htmlPath.getParent()); + Files.createFile(htmlPath); + + // When + Optional result = Rule.createFromHtmlDescription(htmlPath); + + // Then + assertThat(result).isEmpty(); + } + + @Test + @DisplayName("Should create Rule when files and pattern are valid") + void createFromHtmlDescriptionShouldReturnRuleWhenFilesExist(@TempDir Path tempDir) throws Exception { + // Given + Path ruleDir = Files.createDirectories(tempDir.resolve("GCI123")); + Path languageRuleDir = Files.createDirectories(ruleDir.resolve("java")); + + Path htmlDescriptionPath = Files.createFile(languageRuleDir.resolve("GCI123.html")); + Path metadataPath = Files.createFile(ruleDir.resolve("GCI123.json")); + + // When + Optional result = Rule.createFromHtmlDescription(htmlDescriptionPath); + + // Then + AbstractObjectAssert assertThatRule = assertThat(result).isPresent().get(); + assertThatRule.extracting(Rule::language).isEqualTo("java"); + assertThatRule.extracting(Rule::htmlDescription).isEqualTo(htmlDescriptionPath); + assertThatRule.extracting(Rule::metadata).isEqualTo(metadataPath); + assertThatRule.extracting(Rule::specificMetadata).isEqualTo(languageRuleDir.resolve("GCI123.json")); + } +} diff --git a/maven-build/test/java/org/greencodeinitiative/mavenbuild/ruleexporter/infra/PrepareResourcesTest.java b/maven-build/test/java/org/greencodeinitiative/mavenbuild/ruleexporter/infra/PrepareResourcesTest.java new file mode 100644 index 000000000..d9cee0dae --- /dev/null +++ b/maven-build/test/java/org/greencodeinitiative/mavenbuild/ruleexporter/infra/PrepareResourcesTest.java @@ -0,0 +1,144 @@ +package org.greencodeinitiative.mavenbuild.ruleexporter.infra; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; + +class PrepareResourcesTest { + private static final Path SOURCE_DIR = Paths.get("maven-build/test/resources"); + + @TempDir + private Path targetDir; + private PrepareResources prepareResources; + + @BeforeEach + void setUp() { + prepareResources = new PrepareResources(SOURCE_DIR, targetDir); + } + + @Test + @DisplayName("Should copy files when no merge file is present") + void shouldCopyFileWhenNoMergeFile() { + // When + prepareResources.run(); + + // Then + assertThat(targetDir.resolve("html/GCI36.json")).exists(); + assertThat(targetDir.resolve("html/GCI36.html")).exists(); + assertThat(targetDir.resolve("javascript/GCI36.json")).exists(); + assertThat(targetDir.resolve("javascript/GCI36.html")).exists(); + } + + @Test + @DisplayName("Should throw exception when source directory is not found") + void shouldThrowExceptionWhenSourceDirNotFound() { + // Given + Path invalid = Paths.get("not-existing-dir"); + PrepareResources pr = new PrepareResources(invalid, targetDir); + + // When + Throwable thrown = catchThrowable(pr::run); + + // Then + assertThat(thrown) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Unable to read file") + .hasCauseInstanceOf(NoSuchFileException.class) + .hasRootCauseMessage("not-existing-dir"); + } + + @Test + @DisplayName("Should merge JSON files correctly") + void shouldMergeJsonFilesCorrectly(@TempDir Path tempDir) throws IOException { + // Given + Path sourceJson = Files.createFile(tempDir.resolve("source.json")); + Path mergeJson = Files.createFile(tempDir.resolve("merge.json")); + Path targetJson = tempDir.resolve("result.json"); + + String sourceContent = "{\n" + + "\"key1\": \"value1\",\n" + + "\"key2\": \"value2\"\n" + + "}"; + String mergeContent = "{\n" + + "\"key2\": \"newValue2\",\n" + + "\"key3\": \"value3\"\n" + + "}"; + + Files.writeString(sourceJson, sourceContent); + Files.writeString(mergeJson, mergeContent); + + // When + prepareResources.mergeJsonFile(sourceJson, mergeJson, targetJson); + + // Then + String expectedContent = "{\n" + + "\"key1\": \"value1\",\n" + + "\"key2\": \"newValue2\",\n" + + "\"key3\": \"value3\"\n" + + "}"; + assertThat(targetJson).exists(); + assertThat(Files.readString(targetJson)).isEqualToIgnoringWhitespace(expectedContent); + } + + @Test + @DisplayName("Should throw ProcessException when unable to create directories for merge or copy JSON metadata") + void shouldThrowProcessExceptionWhenCannotCreateDirectoriesForMergeOrCopyJsonMetadata() { + // Given + PrepareResources pr = new PrepareResources(Path.of("/invalid/source"), Path.of("/invalid/target")); + Path source = Path.of("/invalid/source.json"); + Path merge = Path.of("/invalid/merge.json"); + Path target = Path.of("/invalid/target.json"); + + // When + Throwable thrown = catchThrowable(() -> pr.mergeOrCopyJsonMetadata(source, merge, target)); + + // Then + assertThat(thrown) + .isInstanceOf(ProcessException.class); + } + + @Test + @DisplayName("Should throw ProcessException when merging JSON file fails") + void shouldThrowProcessExceptionWhenMergeJsonFileFails() { + // Given + PrepareResources pr = new PrepareResources(Path.of("."), Path.of(".")); + Path source = Path.of("notfound-source.json"); + Path merge = Path.of("notfound-merge.json"); + Path target = Path.of("notfound-target.json"); + + // When + Throwable thrown = catchThrowable(() -> pr.mergeJsonFile(source, merge, target)); + + // Then + assertThat(thrown) + .isInstanceOf(ProcessException.class) + .hasMessageContaining("cannot process source"); + } + + @Test + @DisplayName("Should throw ProcessException when copying file fails") + void shouldThrowProcessExceptionWhenCopyFileFails() { + // Given + PrepareResources pr = new PrepareResources(Path.of("."), Path.of(".")); + Path source = Path.of("notfound-source.txt"); + Path target = Path.of("notfound-target.txt"); + + // When + Throwable thrown = catchThrowable(() -> pr.copyFile(source, target)); + + // Then + assertThat(thrown) + .isInstanceOf(ProcessException.class) + .hasMessageContaining("unable to copy"); + } +} diff --git a/maven-build/test/resources/desc.html b/maven-build/test/resources/desc.html new file mode 100644 index 000000000..e69de29bb diff --git a/maven-build/test/resources/meta.json b/maven-build/test/resources/meta.json new file mode 100644 index 000000000..e69de29bb diff --git a/maven-build/test/resources/rules-html/GCI36/GCI36.json b/maven-build/test/resources/rules-html/GCI36/GCI36.json new file mode 100644 index 000000000..ccb0ecd96 --- /dev/null +++ b/maven-build/test/resources/rules-html/GCI36/GCI36.json @@ -0,0 +1,22 @@ +{ + "title": "Autoplay should be avoided for video and audio content", + "type": "CODE_SMELL", + "code": { + "impacts": { + "RELIABILITY": "MEDIUM" + }, + "attribute": "EFFICIENT" + }, + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [ + "creedengo", + "eco-design", + "video", + "audio" + ], + "defaultSeverity": "Major" +} diff --git a/maven-build/test/resources/rules-html/GCI36/html/GCI36.html b/maven-build/test/resources/rules-html/GCI36/html/GCI36.html new file mode 100644 index 000000000..e8a1451a1 --- /dev/null +++ b/maven-build/test/resources/rules-html/GCI36/html/GCI36.html @@ -0,0 +1,70 @@ +
+

Why is this an issue?

+
+
+

Autoplaying media consumes unnecessary energy, especially when users might not be actively engaging with the content. +This can drain battery life on devices, particularly on mobile devices, leading to increased energy consumption and potentially contributing to environmental impact. +It can also consume data, particularly for users on limited data plans or in areas with poor internet connectivity. +This can lead to unnecessary data usage and potentially increased costs for users.

+
+
+

However, even without autoplay, segments of video or audio files might still download. +This leads to unnecessary data usage, especially if users don’t commence playback. +To mitigate this, it’s crucial to prevent browsers from preloading any content by configuring the appropriate settings.

+
+
+

Video:

+
+
+
+
<video src="videofile.webm" autoplay></video> // Noncompliant
+
+
+
+
+
<video src="videofile.webm" preload="none"></video> // Compliant
+
+
+
+

Audio:

+
+
+
+
<audio controls src="audiofile.mp3" autoplay></audio> // Noncompliant
+
+
+
+
+
<audio controls src="audiofile.mp3" preload="none"></audio> // Compliant
+
+
+
+
+
+

Resources

+
+
+

Documentation

+
+ +
+
+ +
+
\ No newline at end of file diff --git a/maven-build/test/resources/rules-html/GCI36/javascript/GCI36.html b/maven-build/test/resources/rules-html/GCI36/javascript/GCI36.html new file mode 100644 index 000000000..8bb85ae80 --- /dev/null +++ b/maven-build/test/resources/rules-html/GCI36/javascript/GCI36.html @@ -0,0 +1,82 @@ +
+

Why is this an issue?

+
+
+

Autoplaying media consumes unnecessary energy, especially when users might not be actively engaging with the content. +This can drain battery life on devices, particularly on mobile devices, leading to increased energy consumption and potentially contributing to environmental impact. +It can also consume data, particularly for users on limited data plans or in areas with poor internet connectivity. +This can lead to unnecessary data usage and potentially increased costs for users.

+
+
+

However, even without autoplay, segments of video or audio files might still download. +This leads to unnecessary data usage, especially if users don’t commence playback. +To mitigate this, it’s crucial to prevent browsers from preloading any content by configuring the appropriate settings.

+
+
+

Video:

+
+
+
+
return (
+  <>
+    <video src="video.mp4" autoplay/> // Noncompliant
+    <video src="video.mp4" preload="auto"/> // Noncompliant
+    <video src="video.mp4" autoplay preload="auto"/> // Noncompliant
+  </>
+)
+
+
+
+
+
return (
+  <video src="video.mp4" preload="none"/> // Compliant
+)
+
+
+
+

Audio:

+
+
+
+
return (
+  <audio controls src="audiofile.mp3" autoplay></audio> // Noncompliant
+)
+
+
+
+
+
return (
+  <audio controls src="audiofile.mp3" preload="none"></audio> // Compliant
+)
+
+
+
+
+
+

Resources

+
+
+

Documentation

+
+ +
+
+ +
+
\ No newline at end of file diff --git a/maven-build/test/resources/rules-html/GCI36/javascript/GCI36.json b/maven-build/test/resources/rules-html/GCI36/javascript/GCI36.json new file mode 100644 index 000000000..adcb19366 --- /dev/null +++ b/maven-build/test/resources/rules-html/GCI36/javascript/GCI36.json @@ -0,0 +1,13 @@ +{ + "tags": [ + "creedengo", + "eco-design", + "react-native", + "video", + "audio" + ], + "compatibleLanguages": [ + "JAVASCRIPT", + "TYPESCRIPT" + ] +} diff --git a/pom.xml b/pom.xml index 001e77c79..f8e4816f1 100644 --- a/pom.xml +++ b/pom.xml @@ -133,9 +133,53 @@ UTF-8 ${encoding} ${encoding} - + + + + org.junit + junit-bom + 5.12.2 + pom + import + + + org.assertj + assertj-bom + 3.27.3 + pom + import + + + + + + + jakarta.json + jakarta.json-api + 2.1.3 + provided + + + org.glassfish + jakarta.json + 2.0.1 + module + provided + + + org.junit.jupiter + junit-jupiter-api + test + + + org.assertj + assertj-core + test + + + @@ -143,7 +187,7 @@ org.sonarsource.scanner.maven sonar-maven-plugin - 5.0.0.4389 + 5.2.0.4988 @@ -171,19 +215,19 @@ + This plugin convert ASCIIDOC rule specification in HTML format + ASCIIDOC format is used with custom features such as : + - syntax highlighting (see code blocks on ASCIIDOC rules) + - inclusions (see: php/EC74.asciidoc) + - table data generation from CSV (see: php/EC69.asciidoc) + --> org.asciidoctor asciidoctor-maven-plugin - 3.1.1 + 3.2.0 convert-to-html - generate-resources + prepare-package process-asciidoc @@ -207,50 +251,78 @@ + + org.codehaus.mojo + build-helper-maven-plugin + 3.6.0 + + + add-build-code-source + + add-source + + + + ${project.basedir}/maven-build/main/java + + + + + add-test-build-code-source + + add-test-source + + + + ${project.basedir}/maven-build/test/java + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.4.2 + + + org/greencodeinitiative/mavenbuildtool/** + + + - com.github.johnpoth - jshell-maven-plugin - 1.4 - - - jakarta.json - jakarta.json-api - 2.1.3 - - - org.glassfish - jakarta.json - 2.0.1 - module - - + Prepare resources tree needed by language. + Each metadata JSON file must be in the same folder as the HTML description file for the corresponding language. + --> + org.codehaus.mojo + exec-maven-plugin + 3.5.0 - prepare-rules-resources - generate-resources + run-build-code + prepare-package - run + java - - - - - - - + org.greencodeinitiative.mavenbuild.ruleexporter.Main + + ${project.build.directory}/rules-html + ${project.build.outputDirectory}/org/green-code-initiative/rules + + + ${project.build.directory}/maven-build-classes + + provided + This module produce one artifact by language (with corresponding classifier) + --> org.apache.maven.plugins maven-assembly-plugin 3.7.1 @@ -378,6 +450,30 @@ + + org.jacoco + jacoco-maven-plugin + 0.8.13 + + + prepare-agent + + prepare-agent + + + + report + + report + + + + XML + + + + + @@ -455,7 +551,7 @@ org.sonatype.central central-publishing-maven-plugin - 0.6.0 + 0.7.0 true central diff --git a/sonar-project.properties b/sonar-project.properties index 402259c9e..b3d9df45a 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,4 +1,4 @@ sonar.host.url=https://sonarcloud.io sonar.organization=green-code-initiative sonar.projectKey=green-code-initiative_creedengo-rules-specifications -sonar.sources=. \ No newline at end of file +sonar.sources=maven-build/main diff --git a/src/main/script/PrepareResources.jsh b/src/main/script/PrepareResources.jsh deleted file mode 100644 index 1d3d09f9d..000000000 --- a/src/main/script/PrepareResources.jsh +++ /dev/null @@ -1,163 +0,0 @@ -//usr/bin/env jshell -v "$@" "$0"; exit $? - - -import jakarta.json.Json; -import jakarta.json.JsonMergePatch; -import jakarta.json.JsonObject; -import jakarta.json.JsonReader; -import jakarta.json.JsonValue; -import jakarta.json.JsonWriter; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static java.lang.System.Logger.Level.DEBUG; -import static java.util.Optional.empty; -import static java.util.Optional.of; - - class PrepareResources implements Runnable { - private static final System.Logger LOGGER = System.getLogger("PrepareResources"); - - private final Path sourceDir; - private final Path targetDir; - - public static void main(String... args) { - new PrepareResources( - Path.of(Objects.requireNonNull(System.getProperty("sourceDir"), "system property: sourceDir")), - Path.of(Objects.requireNonNull(System.getProperty("targetDir"), "system property: targetDir")) - ).run(); - } - - PrepareResources(Path sourceDir, Path targetDir) { - this.sourceDir = sourceDir; - this.targetDir = targetDir; - } - - @Override - public void run() { - getResourcesToCopy().forEach(rule -> { - mergeOrCopyJsonMetadata(rule.metadata, rule.specificMetadata, rule.getMetadataTargetPath(targetDir)); - copyFile(rule.htmlDescription, rule.getHtmlDescriptionTargetPath(targetDir)); - }); - } - - private List getResourcesToCopy() { - try (Stream stream = Files.walk(sourceDir)) { - return stream - .filter(Files::isRegularFile) - .map(Rule::createFromHtmlDescription) - .filter(Optional::isPresent) - .map(Optional::get) - .collect(Collectors.toList()); - } catch (IOException e) { - throw new IllegalStateException(e); - } - } - - private void mergeOrCopyJsonMetadata(Path source, Path merge, Path target) { - try { - Files.createDirectories(target.getParent()); - } catch (IOException e) { - throw new RuntimeException(e); - } - if (Files.isRegularFile(merge)) { - mergeJsonFile(source, merge, target); - } else { - copyFile(source, target); - } - } - - private void mergeJsonFile(Path source, Path merge, Path target) { - LOGGER.log(DEBUG, "Merge: {0} and {1} -> {2}", source, merge, target); - - try ( - JsonReader sourceJsonReader = Json.createReader(Files.newBufferedReader(source)); - JsonReader mergeJsonReader = Json.createReader(Files.newBufferedReader(merge)); - JsonWriter resultJsonWriter = Json.createWriter(Files.newBufferedWriter(target)); - ) { - Files.createDirectories(target.getParent()); - - JsonObject sourceJson = sourceJsonReader.readObject(); - JsonObject mergeJson = mergeJsonReader.readObject(); - - JsonMergePatch mergePatch = Json.createMergePatch(mergeJson); - JsonValue result = mergePatch.apply(sourceJson); - - resultJsonWriter.write(result); - } catch (IOException e) { - throw new RuntimeException("cannot process source " + source, e); - } - } - - private void copyFile(Path source, Path target) { - try { - LOGGER.log(DEBUG, "Copy: {0} -> {1}", source, target); - Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); - } catch (IOException e) { - throw new RuntimeException("unable to copy '" + source + "' to '"+ target +"'", e); - } - } - - - private static class Rule { - /** - * Resources to include - */ - private static final Pattern TARGET_RESOURCES = Pattern.compile("^.*/(?GCI\\d+)/(?[^/]*)/.*\\.html$"); - - static Optional createFromHtmlDescription(Path htmlDescription) { - final Matcher matcher = TARGET_RESOURCES.matcher(htmlDescription.toString().replace('\\', '/')); - if (!matcher.find()) { - return empty(); - } - final String ruleKey = matcher.group("ruleKey"); - final Path metadata = htmlDescription.getParent().getParent().resolve(ruleKey + ".json"); - final Path specificMetadata = htmlDescription.getParent().resolve(ruleKey + ".json"); - - if (!Files.isRegularFile(htmlDescription) || !Files.isRegularFile(metadata)) { - return empty(); - } - - return of(new Rule( - matcher.group("language"), - htmlDescription, - metadata, - specificMetadata - )); - } - - private final String language; - private final Path htmlDescription; - private final Path metadata; - private final Path specificMetadata; - - Rule(String language, Path htmlDescription, Path metadata, Path specificMetadata) { - this.language = language; - this.htmlDescription = htmlDescription; - this.metadata = metadata; - this.specificMetadata = specificMetadata; - } - - Path getHtmlDescriptionTargetPath(Path targetDir) { - return targetDir.resolve(language).resolve(htmlDescription.getFileName()); - } - - Path getMetadataTargetPath(Path targetDir) { - return targetDir.resolve(language).resolve(metadata.getFileName()); - } - } - } - - PrepareResources.main(); - - // @formatter:off -/exit