diff --git a/mug-errorprone/BUILD b/mug-errorprone/BUILD index ee47bac1d2..d5ea715423 100644 --- a/mug-errorprone/BUILD +++ b/mug-errorprone/BUILD @@ -10,6 +10,7 @@ java_library( "@maven//:com_google_errorprone_error_prone_check_api", "@maven//:com_google_guava_guava", "//mug:base", + "//mug:time", "//mug-guava", ], ) @@ -66,6 +67,7 @@ junit_tests( deps = [ ":errorprone", "//mug:format", + "//mug:time", "//mug-guava:safe_sql", "@maven//:com_google_guava_guava", "@maven//:com_google_guava_guava_testlib", diff --git a/mug-errorprone/src/main/java/com/google/mu/errorprone/DateTimeExampleStringCheck.java b/mug-errorprone/src/main/java/com/google/mu/errorprone/DateTimeExampleStringCheck.java new file mode 100644 index 0000000000..be4036579f --- /dev/null +++ b/mug-errorprone/src/main/java/com/google/mu/errorprone/DateTimeExampleStringCheck.java @@ -0,0 +1,44 @@ +package com.google.mu.errorprone; + +import static com.google.errorprone.BugPattern.SeverityLevel.ERROR; +import static com.google.errorprone.matchers.Matchers.staticMethod; + +import com.google.auto.service.AutoService; +import com.google.mu.time.DateTimeFormats; +import com.google.errorprone.BugPattern; +import com.google.errorprone.BugPattern.LinkType; +import com.google.errorprone.VisitorState; +import com.google.errorprone.bugpatterns.BugChecker; +import com.google.errorprone.matchers.Matcher; +import com.google.errorprone.util.ASTHelpers; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.MethodInvocationTree; + +/** Validate the example datetime string passed to {@code DateTimeFormats.formatOf()}. */ +@BugPattern( + summary = "Checks that the format string passed to DateTimeFormats.formatOf() is supported.", + link = "go/java-tips/024#safer-string-format-reuse", + linkType = LinkType.CUSTOM, + severity = ERROR) +@AutoService(BugChecker.class) +public final class DateTimeExampleStringCheck extends AbstractBugChecker + implements AbstractBugChecker.MethodInvocationCheck { + private static final Matcher MATCHER = + staticMethod().onClass("com.google.mu.time.DateTimeFormats").named("formatOf"); + + @Override + public void checkMethodInvocation(MethodInvocationTree tree, VisitorState state) + throws ErrorReport { + if (!MATCHER.matches(tree, state) || tree.getArguments().isEmpty()) { + return; + } + ExpressionTree exampleArg = tree.getArguments().get(0); + String exampleString = ASTHelpers.constValue(exampleArg, String.class); + checkingOn(exampleArg).require(exampleString != null, "compile-time string constant expected"); + try { + Object verified = DateTimeFormats.formatOf(exampleString); + } catch (IllegalArgumentException e) { + throw checkingOn(exampleArg).report(e.getMessage()); + } + } +} \ No newline at end of file diff --git a/mug-errorprone/src/test/java/com/google/mu/errorprone/DateTimeExampleStringCheckTest.java b/mug-errorprone/src/test/java/com/google/mu/errorprone/DateTimeExampleStringCheckTest.java new file mode 100644 index 0000000000..5fcc215f4c --- /dev/null +++ b/mug-errorprone/src/test/java/com/google/mu/errorprone/DateTimeExampleStringCheckTest.java @@ -0,0 +1,27 @@ +package com.google.mu.errorprone; + +import com.google.errorprone.CompilationTestHelper; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class DateTimeExampleStringCheckTest { + private final CompilationTestHelper helper = + CompilationTestHelper.newInstance(DateTimeExampleStringCheck.class, getClass()); + + @Test + public void mmddyyNotSupported() { + helper + .addSourceLines( + "Test.java", + "import static com.google.mu.time.DateTimeFormats.formatOf;", + "import java.time.format.DateTimeFormatter;", + "class Test {", + " private static final DateTimeFormatter FORMAT = formatOf(", + " // BUG: Diagnostic contains: unsupported date time example: 10/20/2023", + " \"10/20/2023 10:10:10\");", + "}") + .doTest(); + } +} \ No newline at end of file diff --git a/mug/BUILD b/mug/BUILD index 864bb12eb9..94b66a0dbd 100644 --- a/mug/BUILD +++ b/mug/BUILD @@ -5,6 +5,7 @@ java_library( "src/main/java/com/google/mu/util/*.java", "src/main/java/com/google/mu/util/stream/*.java", "src/main/java/com/google/mu/function/*.java", + "src/main/java/com/google/mu/collect/*.java", ], exclude = ["src/main/java/com/google/mu/util/StringFormat.java"]), ) @@ -31,6 +32,14 @@ java_library( exported_plugins = ["//mug-errorprone:plugin"], ) +java_library( + name = "time", + visibility = ["//visibility:public"], + srcs = glob(["src/main/java/com/google/mu/time/*.java"]), + deps = [":base"], + exported_plugins = ["//mug-errorprone:plugin"], +) + load("@com_googlesource_gerrit_bazlets//tools:junit.bzl", "junit_tests") java_library( diff --git a/mug/src/main/java/com/google/mu/time/DateTimeFormats.java b/mug/src/main/java/com/google/mu/time/DateTimeFormats.java index 0518c5ab8f..b126009c9a 100644 --- a/mug/src/main/java/com/google/mu/time/DateTimeFormats.java +++ b/mug/src/main/java/com/google/mu/time/DateTimeFormats.java @@ -245,7 +245,7 @@ static DateTimeFormatter inferFromExample(String example) { DateTimeFormatter fmt = DateTimeFormatter.ofPattern(pattern); fmt.parse(example); return fmt; - } catch (DateTimeParseException | IllegalArgumentException e) { + } catch (DateTimeParseException e) { throw new IllegalArgumentException( "invalid date time example: " + example + " (" + pattern + ")", e); } @@ -280,7 +280,7 @@ private static String inferDateTimePattern(String example, List signature) { }) .orElse(0); if (consumed <= 0) { - throw new IllegalArgumentException("unsupported example: " + example); + throw new IllegalArgumentException("unsupported date time example: " + example); } matched += consumed; }