From ae28ef120291ae03a9272737ac87f7acba6f9e16 Mon Sep 17 00:00:00 2001 From: Roman Salvador Date: Mon, 11 Mar 2024 13:17:41 +0100 Subject: [PATCH 1/2] Add CustomImportOrganizer --- .../google/errorprone/ImportOrderParser.java | 3 + .../apply/CustomImportOrganizer.java | 135 +++++++++++++++++ .../errorprone/ErrorProneOptionsTest.java | 9 ++ .../apply/CustomImportOrganizerTest.java | 142 ++++++++++++++++++ 4 files changed, 289 insertions(+) create mode 100644 check_api/src/main/java/com/google/errorprone/apply/CustomImportOrganizer.java create mode 100644 check_api/src/test/java/com/google/errorprone/apply/CustomImportOrganizerTest.java diff --git a/check_api/src/main/java/com/google/errorprone/ImportOrderParser.java b/check_api/src/main/java/com/google/errorprone/ImportOrderParser.java index 064bb690e4b..9928460238f 100644 --- a/check_api/src/main/java/com/google/errorprone/ImportOrderParser.java +++ b/check_api/src/main/java/com/google/errorprone/ImportOrderParser.java @@ -15,6 +15,7 @@ */ package com.google.errorprone; +import com.google.errorprone.apply.CustomImportOrganizer; import com.google.errorprone.apply.ImportOrganizer; /** Parse import order strings. */ @@ -39,6 +40,8 @@ public static ImportOrganizer getImportOrganizer(String importOrder) { case "idea": return ImportOrganizer.IDEA_ORGANIZER; default: + if (importOrder.startsWith("custom:")) + return new CustomImportOrganizer(importOrder.substring(7)); throw new IllegalStateException("Unknown import order: '" + importOrder + "'"); } } diff --git a/check_api/src/main/java/com/google/errorprone/apply/CustomImportOrganizer.java b/check_api/src/main/java/com/google/errorprone/apply/CustomImportOrganizer.java new file mode 100644 index 00000000000..fdacd1f8bd9 --- /dev/null +++ b/check_api/src/main/java/com/google/errorprone/apply/CustomImportOrganizer.java @@ -0,0 +1,135 @@ +/* + * Copyright 2017 The Error Prone 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 com.google.errorprone.apply; + +import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSortedSet; +import com.google.errorprone.InvalidCommandLineOptionException; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.stream.Collectors; + +/** + * Organizes imports based on a specified custom Code Style. + * + *

The custom ordering is specified using a -XepPatchImportOrder with the format:

+ * custom:static={first|last},order={':'-separated prefixes} + *

+ * The order parameter is a colon-separated list indicating the desired order of import groups by their package prefixes.
+ * The special OTHER prefix is used to place imports that do not match any specified prefix.

+ * + *

The imports are partitioned into groups in two steps, each step sub-partitions all groups from + * the previous step. The steps are: + * + *

    + *
  1. Split into static and non-static, the static imports come before or after the non static + * imports depending on the static parameter. + *
  2. Further partitioning within each group based on the package prefix order defined in order. + * For example,
    "-XepPatchImportOrder=custom:static=first,order=java:javax:OTHER:org.a:org.b:com"
    + * results in the following order: + *
      + *
    1. {@code java.}
    2. + *
    3. {@code javax.}
    4. + *
    5. AImports not matching any other specified prefix.
    6. + *
    7. {@code org.a.}
    8. + *
    9. {@code org.b.}
    10. + *
    11. {@code com.}
    12. + *
    + *
+ * + *

Each group is separated from the previous/next groups with a blank line. + */ +public class CustomImportOrganizer implements ImportOrganizer { + + private static final String OTHER = "OTHER"; + private static final String STATIC_PREFIX = "static="; + private static final String ORDER_PREFIX = "order="; + + private StaticOrder staticOrder; + private ImmutableList roots; + + public CustomImportOrganizer(String spec) { + for (String keyValue: spec.split(",")) { + if (keyValue.startsWith(STATIC_PREFIX)) { + String value = keyValue.substring(STATIC_PREFIX.length()); + if (value.equals("first")) { + staticOrder = StaticOrder.STATIC_FIRST; + } else if (value.equals("last")) { + staticOrder = StaticOrder.STATIC_LAST; + } + } else if (keyValue.startsWith(ORDER_PREFIX)) { + String value = keyValue.substring(ORDER_PREFIX.length()); + roots = ImmutableList.copyOf(value.split(":")); + } + } + if (staticOrder == null) { + throw new InvalidCommandLineOptionException("missing \"" + STATIC_PREFIX + "\" parameter: " + spec); + } + if (roots == null) { + throw new InvalidCommandLineOptionException("missing \"" + ORDER_PREFIX + "\" parameter: " + spec); + } + if (!roots.contains(OTHER)) { + throw new InvalidCommandLineOptionException("missing \"" + OTHER + "\" order prefix: " + spec); + } + } + + @Override + public OrganizedImports organizeImports(List imports) { + OrganizedImports organized = new OrganizedImports(); + + // Group into static and non-static. + Map> partionedByStatic = + imports.stream().collect(Collectors.partitioningBy(Import::isStatic)); + + for (Boolean key : staticOrder.groupOrder()) { + organizePartition(organized, partionedByStatic.get(key)); + } + + return organized; + } + + private void organizePartition(OrganizedImports organized, List imports) { + + Map> groupedByRoot = + imports.stream() + .collect( + Collectors.groupingBy( + // Group by root package. + anImport -> rootPackage(anImport), + // Ensure that the results are sorted. + TreeMap::new, + // Each group is a set sorted by type. + toImmutableSortedSet(Comparator.comparing(Import::getType)))); + + organized.addGroups(groupedByRoot, roots); + } + + private String rootPackage(Import anImport) { + String type = anImport.getType(); + + for (String root: roots) { + if (type.startsWith(root + ".")) { + return root; + } + } + + return OTHER; + } +} diff --git a/check_api/src/test/java/com/google/errorprone/ErrorProneOptionsTest.java b/check_api/src/test/java/com/google/errorprone/ErrorProneOptionsTest.java index 6465bd2d57b..ee1b36c37b7 100644 --- a/check_api/src/test/java/com/google/errorprone/ErrorProneOptionsTest.java +++ b/check_api/src/test/java/com/google/errorprone/ErrorProneOptionsTest.java @@ -25,6 +25,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.errorprone.ErrorProneOptions.Severity; +import com.google.errorprone.apply.CustomImportOrganizer; import com.google.errorprone.apply.ImportOrganizer; import java.util.Arrays; import java.util.Collection; @@ -303,6 +304,14 @@ public void importOrder_androidStaticLast() { assertThat(options.patchingOptions().importOrganizer()) .isSameInstanceAs(ImportOrganizer.ANDROID_STATIC_LAST_ORGANIZER); } + + @Test + public void importOrder_custom() { + ErrorProneOptions options = + ErrorProneOptions.processArgs(new String[] {"-XepPatchImportOrder:custom:static=first,order=java:javax:OTHER:org"}); + assertThat(options.patchingOptions().importOrganizer()) + .isInstanceOf(CustomImportOrganizer.class); + } @Test public void noSuchXepFlag() { diff --git a/check_api/src/test/java/com/google/errorprone/apply/CustomImportOrganizerTest.java b/check_api/src/test/java/com/google/errorprone/apply/CustomImportOrganizerTest.java new file mode 100644 index 00000000000..1bbec13596c --- /dev/null +++ b/check_api/src/test/java/com/google/errorprone/apply/CustomImportOrganizerTest.java @@ -0,0 +1,142 @@ +/* + * Copyright 2018 The Error Prone 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 com.google.errorprone.apply; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.truth.Truth.assertThat; +import java.util.stream.Stream; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import com.google.common.collect.ImmutableList; + +/** {@link CustomImportOrganizer}Test */ +@RunWith(JUnit4.class) +public class CustomImportOrganizerTest { + + private static final ImmutableList IMPORTS = + Stream.of( + "import com.acme", + "import org.a.example", + "import org.b.example", + "import unknown.fred", + "import java.ping", + "import org.a.sample", + "import org.b.sample", + "import javax.pong", + "import unknown.barney", + "import net.wilma", + "import static com.acme.bla", + "import static java.ping.bla", + "import java.util", + "import static unknown.fred.bla") + .map(ImportOrganizer.Import::importOf) + .collect(toImmutableList()); + + @Test + public void staticFirstOtherLast() { + ImportOrganizer organizer = new CustomImportOrganizer("static=first,order=java:javax:org.a:org.b:com:OTHER"); + ImportOrganizer.OrganizedImports organized = organizer.organizeImports(IMPORTS); + assertThat(organized.asImportBlock()) + .isEqualTo( + "import static java.ping.bla;\n" + + "\n" + + "import static com.acme.bla;\n" + + "\n" + + "import static unknown.fred.bla;\n" + + "\n" + + "import java.ping;\n" + + "import java.util;\n" + + "\n" + + "import javax.pong;\n" + + "\n" + + "import org.a.example;\n" + + "import org.a.sample;\n" + + "\n" + + "import org.b.example;\n" + + "import org.b.sample;\n" + + "\n" + + "import com.acme;\n" + + "\n" + + "import net.wilma;\n" + + "import unknown.barney;\n" + + "import unknown.fred;\n" + ); + } + + @Test + public void staticLastOtherFirst() { + ImportOrganizer organizer = new CustomImportOrganizer("static=last,order=OTHER:java:javax:org.a:org.b:com"); + ImportOrganizer.OrganizedImports organized = organizer.organizeImports(IMPORTS); + assertThat(organized.asImportBlock()) + .isEqualTo( + "import net.wilma;\n" + + "import unknown.barney;\n" + + "import unknown.fred;\n" + + "\n" + + "import java.ping;\n" + + "import java.util;\n" + + "\n" + + "import javax.pong;\n" + + "\n" + + "import org.a.example;\n" + + "import org.a.sample;\n" + + "\n" + + "import org.b.example;\n" + + "import org.b.sample;\n" + + "\n" + + "import com.acme;\n" + + "\n" + + "import static unknown.fred.bla;\n" + + "\n" + + "import static java.ping.bla;\n" + + "\n" + + "import static com.acme.bla;\n" + ); + } + + @Test + public void staticFirstOtherMiddle() { + ImportOrganizer organizer = new CustomImportOrganizer("static=first,order=java:javax:OTHER:org.a:org.b:com"); + ImportOrganizer.OrganizedImports organized = organizer.organizeImports(IMPORTS); + assertThat(organized.asImportBlock()) + .isEqualTo( + "import static java.ping.bla;\n" + + "\n" + + "import static unknown.fred.bla;\n" + + "\n" + + "import static com.acme.bla;\n" + + "\n" + + "import java.ping;\n" + + "import java.util;\n" + + "\n" + + "import javax.pong;\n" + + "\n" + + "import net.wilma;\n" + + "import unknown.barney;\n" + + "import unknown.fred;\n" + + "\n" + + "import org.a.example;\n" + + "import org.a.sample;\n" + + "\n" + + "import org.b.example;\n" + + "import org.b.sample;\n" + + "\n" + + "import com.acme;\n" + ); + } +} From d8a1e5c4cc972b5db1077bbfba7bccc0fc01af41 Mon Sep 17 00:00:00 2001 From: Roman Salvador Date: Wed, 13 Mar 2024 10:27:35 +0100 Subject: [PATCH 2/2] Fixed typo and added javadoc --- .../com/google/errorprone/apply/CustomImportOrganizer.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/check_api/src/main/java/com/google/errorprone/apply/CustomImportOrganizer.java b/check_api/src/main/java/com/google/errorprone/apply/CustomImportOrganizer.java index fdacd1f8bd9..597da6c31e4 100644 --- a/check_api/src/main/java/com/google/errorprone/apply/CustomImportOrganizer.java +++ b/check_api/src/main/java/com/google/errorprone/apply/CustomImportOrganizer.java @@ -47,7 +47,7 @@ *

    *
  1. {@code java.}
  2. *
  3. {@code javax.}
  4. - *
  5. AImports not matching any other specified prefix.
  6. + *
  7. Imports not matching any other specified prefix.
  8. *
  9. {@code org.a.}
  10. *
  11. {@code org.b.}
  12. *
  13. {@code com.}
  14. @@ -65,6 +65,11 @@ public class CustomImportOrganizer implements ImportOrganizer { private StaticOrder staticOrder; private ImmutableList roots; + /** + * Creates a custom import organizer according to the provided specification. + * + * @param spec import order specified as: static={first|last},order={':'-separated prefixes} + */ public CustomImportOrganizer(String spec) { for (String keyValue: spec.split(",")) { if (keyValue.startsWith(STATIC_PREFIX)) {