From d7d50a61c1e871b2ca030bb44e5dd4194f8ec7a8 Mon Sep 17 00:00:00 2001 From: PheonBest Date: Wed, 5 Apr 2023 21:21:09 +0200 Subject: [PATCH] :sparkles: Optimized Image vectors size --- .../java/checks/OptimizeImageVectorsSize.java | 140 ++++++++++++++++++ .../test/files/OptimizeImageVectorsSize.java | 38 +++++ .../checks/OptimizeImageVectorsSizeTest.java | 16 ++ 3 files changed, 194 insertions(+) create mode 100644 java-plugin/src/main/java/fr/greencodeinitiative/java/checks/OptimizeImageVectorsSize.java create mode 100644 java-plugin/src/test/files/OptimizeImageVectorsSize.java create mode 100644 java-plugin/src/test/java/fr/greencodeinitiative/java/checks/OptimizeImageVectorsSizeTest.java diff --git a/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/OptimizeImageVectorsSize.java b/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/OptimizeImageVectorsSize.java new file mode 100644 index 000000000..d4f88b853 --- /dev/null +++ b/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/OptimizeImageVectorsSize.java @@ -0,0 +1,140 @@ +package fr.greencodeinitiative.java.checks; + +import org.sonar.check.Priority; +import org.sonar.check.Rule; +import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.tree.ForEachStatement; +import org.sonar.plugins.java.api.tree.LiteralTree; +import org.sonar.plugins.java.api.tree.Tree; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +import java.lang.reflect.Field; +import java.rmi.UnmarshalException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@Rule( + key = "EC53", + name = "Developpement", + description = OptimizeImageVectorsSize.MESSAGERULE, + priority = Priority.MINOR, + tags = {"bug"}) +public class OptimizeImageVectorsSize extends IssuableSubscriptionVisitor { + protected final String SVG_BEGIN = ", . Redundant tags. Superfluous attributes (xmlns:dc, xmlns:cc, xmlns:rdv, xmlns: svg, xmlns, xmlns:sodipodi, xmlns:inkscape, most of id attributes, version, inkscape:version, inkscape:label, inkscape:groupmode, sodipodi:docname)"; + + @Override + public List nodesToVisit() { + return List.of(Tree.Kind.STRING_LITERAL); + } + + @Override + public void visitNode(Tree tree) { + + String value = ((LiteralTree) tree).value(); + // convert to lower case (not case sensitve anymore) + value = value.toLowerCase(); + // trim a beginning and ending double quote (") + value = value.replaceAll("^\"|\"$", ""); + // stop escaping double quotes + value = value.replaceAll("\\\\\"", "\"");; + + // search for svg beginning tag + int beginIndex = value.indexOf(SVG_BEGIN); + if (beginIndex < 0) { + // the string doesn't contain any svg + return; + } + + // Parse svg as xml and explore its tree + try { + // Build the doc from the XML file + InputSource source = new InputSource(new StringReader(value)); + + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + Document doc = db.parse(source); + + // the string is a valid xml file + + XPath xpath = XPathFactory.newInstance().newXPath(); + + // Note to developers: check xpath expressions by using xpather: http://xpather.com/ + + // Superfluous tags, e.g: , + if (((Double) xpath.evaluate("count(*//sodipodi:namedview)", + doc, XPathConstants.NUMBER)).intValue() > 0 || ((Double) xpath.evaluate("count(*//metadata)", + doc, XPathConstants.NUMBER)).intValue() > 0) { + reportIssue(tree, MESSAGERULE); + return; + } + + int nbId = ((Double) xpath.evaluate("count(//@id)", doc, XPathConstants.NUMBER)).intValue(); + int nbDistinctId = ((Double) xpath.evaluate("count(//@id[not(. = preceding::*/@id)])", doc, XPathConstants.NUMBER)).intValue(); + int nbHref = ((Double) xpath.evaluate("count(//@xlink:href)", doc, XPathConstants.NUMBER)).intValue(); + int nbDistinctHref = ((Double) xpath.evaluate("count(//@xlink:href[not(. = preceding::*/@xlink:href)])", doc, XPathConstants.NUMBER)).intValue(); + + // Duplicated tags (tags with the same xlink:href or same id attributes) + if (nbId != nbDistinctId || nbHref != nbDistinctHref) { + // count(//@id) returns the number of elements having an attribute "id" + // count(//@id[not(. = preceding::*/@id)]) returns the number of unique "id" attribute values + // if the number of "id" attributes and the number of unique "id" attributes values + // aren't equal, that means at least two elements have an "id" attribute. + reportIssue(tree, MESSAGERULE); + return; + } + + // The attribute value length can be reduced + if ((boolean) xpath.evaluate("boolean(//@id[string-length() > 3])", + doc, XPathConstants.BOOLEAN)) { + reportIssue(tree, MESSAGERULE); + return; + } + + // The attributes' value should be approximated (there are attributes with a numeric value that have more than 3 decimals) + // @* selects all the attributes of the document + // [number(.) = .] is a predicate that filters all attributes whose value can be converted to a numeric one. + if ((boolean) xpath.evaluate("boolean(//@*[number(.) = . and contains(., '.') and string-length(substring-after(., '.')) > 3])", doc, XPathConstants.BOOLEAN)) { + reportIssue(tree, MESSAGERULE); + return; + } + + // Avoid superfluous attributes + // TODO: make it work with namespaces (xmlns). + for (String superfluousAttribute : SUPERFLUOUS_ATTRIBUTES) { + String expression = String.format("boolean(//@*[name()=%s])\n", superfluousAttribute); + + if ((boolean) xpath.evaluate(expression, doc, XPathConstants.BOOLEAN)) { + reportIssue(tree, MESSAGERULE); + return; + } + } + } catch (ParserConfigurationException | IOException | SAXException | XPathExpressionException e) { + throw new RuntimeException(e); + } + } +} diff --git a/java-plugin/src/test/files/OptimizeImageVectorsSize.java b/java-plugin/src/test/files/OptimizeImageVectorsSize.java new file mode 100644 index 000000000..4a981c773 --- /dev/null +++ b/java-plugin/src/test/files/OptimizeImageVectorsSize.java @@ -0,0 +1,38 @@ +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +class TestClass { + + TestClass(TestClass tc) { + } + + int compliantCase() { + return ""; + } + + int idCanBeReduced() { + return ""; // Noncompliant {{Optimize your svg by using open source tools such as SVGGO, Compressor.io or SVG Cleaner}} + } + + int superfluousTag() { + return ""; // Noncompliant {{Optimize your svg by using open source tools such as SVGGO, Compressor.io or SVG Cleaner}} + } + + int duplicatedTags() { + return ""; // Noncompliant {{Optimize your svg by using open source tools such as SVGGO, Compressor.io or SVG Cleaner}} + } + + int unapproximatedNumericValues() { + return ""; // Noncompliant {{Optimize your svg by using open source tools such as SVGGO, Compressor.io or SVG Cleaner}} + } + + int superfluousAttributes1() { + return "
"; // Noncompliant {{Optimize your svg by using open source tools such as SVGGO, Compressor.io or SVG Cleaner}} + } + + int superfluousAttributes2() { + return "
"; // Noncompliant {{Optimize your svg by using open source tools such as SVGGO, Compressor.io or SVG Cleaner}} + } + +} \ No newline at end of file diff --git a/java-plugin/src/test/java/fr/greencodeinitiative/java/checks/OptimizeImageVectorsSizeTest.java b/java-plugin/src/test/java/fr/greencodeinitiative/java/checks/OptimizeImageVectorsSizeTest.java new file mode 100644 index 000000000..dfa13f741 --- /dev/null +++ b/java-plugin/src/test/java/fr/greencodeinitiative/java/checks/OptimizeImageVectorsSizeTest.java @@ -0,0 +1,16 @@ +package fr.greencodeinitiative.java.checks; + +import org.junit.jupiter.api.Test; +import org.sonar.java.checks.verifier.CheckVerifier; + +class OptimizeImageVectorsSizeTest { + + @Test + void test() { + CheckVerifier.newVerifier() + .onFile("src/test/files/OptimizeImageVectorsSize.java") + .withCheck(new OptimizeImageVectorsSize()) + .verifyIssues(); + } + +} \ No newline at end of file