diff --git a/config/import-control-test.xml b/config/import-control-test.xml index e93ee85..404af56 100644 --- a/config/import-control-test.xml +++ b/config/import-control-test.xml @@ -8,5 +8,7 @@ + + diff --git a/config/import-control.xml b/config/import-control.xml index c2c794a..483abd1 100644 --- a/config/import-control.xml +++ b/config/import-control.xml @@ -8,6 +8,7 @@ + - \ No newline at end of file + diff --git a/config/suppressions.xml b/config/suppressions.xml index 5f36af0..c5dfa13 100644 --- a/config/suppressions.xml +++ b/config/suppressions.xml @@ -4,4 +4,8 @@ "-//Checkstyle//DTD SuppressionFilter Configuration 1.1//EN" "https://checkstyle.org/dtds/suppressions_1_1.dtd"> - + + + + + diff --git a/pom.xml b/pom.xml index e9d2f70..c70ee48 100644 --- a/pom.xml +++ b/pom.xml @@ -44,6 +44,14 @@ + + + + com.puppycrawl.tools + checkstyle + ${checkstyle.version} + + org.openrewrite diff --git a/src/main/java/org/checkstyle/autofix/parser/CheckstyleReportsParser.java b/src/main/java/org/checkstyle/autofix/parser/CheckstyleReportsParser.java new file mode 100644 index 0000000..d44865b --- /dev/null +++ b/src/main/java/org/checkstyle/autofix/parser/CheckstyleReportsParser.java @@ -0,0 +1,145 @@ +/////////////////////////////////////////////////////////////////////////////////////////////// +// checkstyle-openrewrite-recipes: Automatically fix Checkstyle violations with OpenRewrite. +// Copyright (C) 2025 The Checkstyle OpenRewrite Recipes Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/////////////////////////////////////////////////////////////////////////////////////////////// + +package org.checkstyle.autofix.parser; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; + +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.Attribute; +import javax.xml.stream.events.StartElement; +import javax.xml.stream.events.XMLEvent; + +public final class CheckstyleReportsParser { + + private static final String FILE_TAG = "file"; + + private static final String ERROR_TAG = "error"; + + private static final String FILENAME_ATTR = "name"; + + private static final String LINE_ATTR = "line"; + + private static final String COLUMN_ATTR = "column"; + + private static final String SEVERITY_ATTR = "severity"; + + private static final String MESSAGE_ATTR = "message"; + + private static final String SOURCE_ATTR = "source"; + + private CheckstyleReportsParser() { + + } + + public static List parse(Path xmlPath) + throws IOException, XMLStreamException { + + final List result = new ArrayList<>(); + + try (InputStream inputStream = new FileInputStream(xmlPath.toFile())) { + + final XMLInputFactory inputFactory = XMLInputFactory.newInstance(); + final XMLEventReader reader = inputFactory.createXMLEventReader(inputStream); + + try { + String filename = null; + + while (reader.hasNext()) { + final XMLEvent event = reader.nextEvent(); + if (event.isStartElement()) { + final StartElement startElement = event.asStartElement(); + final String startElementName = startElement.getName().getLocalPart(); + + if (FILE_TAG.equals(startElementName)) { + filename = parseFileTag(startElement); + } + else if (ERROR_TAG.equals(startElementName)) { + Objects.requireNonNull(filename, "File name can not be null"); + result.add(parseErrorTag(startElement, filename)); + } + } + } + } + finally { + if (reader != null) { + reader.close(); + } + } + } + + return result; + } + + private static String parseFileTag(StartElement startElement) { + String fileName = null; + final Iterator attributes = startElement.getAttributes(); + while (attributes.hasNext()) { + final Attribute attribute = attributes.next(); + if (FILENAME_ATTR.equals(attribute.getName().getLocalPart())) { + fileName = attribute.getValue(); + break; + } + } + return fileName; + } + + private static CheckstyleViolation parseErrorTag(StartElement startElement, String filename) { + Integer line = null; + Integer column = null; + String source = null; + String message = null; + String severity = null; + final Iterator attributes = startElement + .getAttributes(); + while (attributes.hasNext()) { + final Attribute attribute = attributes.next(); + final String attrName = attribute.getName().getLocalPart(); + switch (attrName) { + case LINE_ATTR: + line = Integer.valueOf(attribute.getValue()); + break; + case COLUMN_ATTR: + column = Integer.parseInt(attribute.getValue()); + break; + case SEVERITY_ATTR: + severity = attribute.getValue(); + break; + case MESSAGE_ATTR: + message = attribute.getValue(); + break; + case SOURCE_ATTR: + source = attribute.getValue(); + break; + default: + break; + } + } + return new CheckstyleViolation( + line, column, severity, source, message, filename); + + } +} diff --git a/src/main/java/org/checkstyle/autofix/parser/CheckstyleViolation.java b/src/main/java/org/checkstyle/autofix/parser/CheckstyleViolation.java new file mode 100644 index 0000000..2b3fc7f --- /dev/null +++ b/src/main/java/org/checkstyle/autofix/parser/CheckstyleViolation.java @@ -0,0 +1,68 @@ +/////////////////////////////////////////////////////////////////////////////////////////////// +// checkstyle-openrewrite-recipes: Automatically fix Checkstyle violations with OpenRewrite. +// Copyright (C) 2025 The Checkstyle OpenRewrite Recipes Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/////////////////////////////////////////////////////////////////////////////////////////////// + +package org.checkstyle.autofix.parser; + +public final class CheckstyleViolation { + + private final Integer line; + + private final Integer column; + + private final String severity; + + private final String source; + + private final String message; + + private final String fileName; + + public CheckstyleViolation(Integer line, Integer column, + String severity, String source, String message, String fileName) { + this.line = line; + this.column = column; + this.severity = severity; + this.source = source; + this.message = message; + this.fileName = fileName; + } + + public int getLine() { + return line; + } + + public int getColumn() { + return column; + } + + public String getSource() { + return source; + } + + public String getMessage() { + return message; + } + + public String getFileName() { + return fileName; + } + + public String getSeverity() { + return severity; + } + +} diff --git a/src/main/java/org/checkstyle/autofix/parser/package-info.java b/src/main/java/org/checkstyle/autofix/parser/package-info.java new file mode 100644 index 0000000..413d9fe --- /dev/null +++ b/src/main/java/org/checkstyle/autofix/parser/package-info.java @@ -0,0 +1,21 @@ +/////////////////////////////////////////////////////////////////////////////////////////////// +// checkstyle-openrewrite-recipes: Automatically fix Checkstyle violations with OpenRewrite. +// Copyright (C) 2025 The Checkstyle OpenRewrite Recipes Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Provides classes to parse Checkstyle XML report files. + */ +package org.checkstyle.autofix.parser; diff --git a/src/test/java/org/checkstyle/autofix/parser/CheckstyleReportsParserTest.java b/src/test/java/org/checkstyle/autofix/parser/CheckstyleReportsParserTest.java new file mode 100644 index 0000000..9371d0d --- /dev/null +++ b/src/test/java/org/checkstyle/autofix/parser/CheckstyleReportsParserTest.java @@ -0,0 +1,75 @@ +/////////////////////////////////////////////////////////////////////////////////////////////// +// checkstyle-openrewrite-recipes: Automatically fix Checkstyle violations with OpenRewrite. +// Copyright (C) 2025 The Checkstyle OpenRewrite Recipes Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/////////////////////////////////////////////////////////////////////////////////////////////// + +package org.checkstyle.autofix.parser; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; + +public class CheckstyleReportsParserTest { + + private static String getPath(String path) { + return "src/test/resources/org/checkstyle/autofix/parser/" + path; + } + + @Test + public void testParseFromResource() throws Exception { + final Path xmlPath = Path.of(getPath("checkstyle-report.xml")); + final List records = CheckstyleReportsParser.parse(xmlPath); + + assertNotNull(records); + assertEquals(1, records.size()); + + final CheckstyleViolation record = records.get(0); + assertEquals(42, record.getLine()); + assertEquals(13, record.getColumn()); + assertEquals("error", record.getSeverity()); + assertEquals("Example message", record.getMessage()); + assertEquals("com.puppycrawl.example.Check", record.getSource()); + assertEquals("Example.java", record.getFileName()); + } + + @Test + public void testParseMultipleFilesReport() throws Exception { + final Path xmlPath = Path.of(getPath("checkstyle-multiple-files.xml")); + final List records = CheckstyleReportsParser.parse(xmlPath); + + assertNotNull(records); + assertEquals(3, records.size()); + + final Map> grouped = records.stream() + .collect(Collectors.groupingBy(CheckstyleViolation::getFileName)); + + assertEquals(2, grouped.size()); + + assertEquals(2, grouped.get("Main.java").size()); + assertEquals(1, grouped.get("Utils.java").size()); + + CheckstyleViolation record = grouped.get("Main.java").get(0); + assertEquals("error", record.getSeverity()); + + record = grouped.get("Utils.java").get(0); + assertEquals("warning", record.getSeverity()); + } +} diff --git a/src/test/resources/org/checkstyle/autofix/parser/checkstyle-multiple-files.xml b/src/test/resources/org/checkstyle/autofix/parser/checkstyle-multiple-files.xml new file mode 100644 index 0000000..5d953d8 --- /dev/null +++ b/src/test/resources/org/checkstyle/autofix/parser/checkstyle-multiple-files.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/test/resources/org/checkstyle/autofix/parser/checkstyle-report.xml b/src/test/resources/org/checkstyle/autofix/parser/checkstyle-report.xml new file mode 100644 index 0000000..d354f6d --- /dev/null +++ b/src/test/resources/org/checkstyle/autofix/parser/checkstyle-report.xml @@ -0,0 +1,8 @@ + + + + + +