This is an automated email from the ASF dual-hosted git repository.

jhyde pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git

commit 54e3faf0618c25a63b1c40c0ec3855ce0b842127
Author: Julian Hyde <[email protected]>
AuthorDate: Wed Jun 14 12:15:35 2023 -0700

    Add various lint checks
    
    (The errors detected by these rules were fixed in the preceding commit.)
    
    Add a lint check '// not followed by space'.
    
    Add a lint rule that ':' in 'for' must be surrounded by spaces.
    
    Add a lint rule disallowing '?' without ':' on an assignment
    line. It Converts
      v = b ? branch1
          : branch2;
    to
      v =
          b ? branch1
              : branch2;
    
    Apply lint rules to all text files, not just Java files.
    
    Detect tabs and trailing spaces.
    
    Add 'lint:skip' directive.
    
    Exempt LintTest from lint rules, because it contains strings
    that are designed to trigger lint rules.
    
    Treat the lint test string as a Java file.
    
    In Puffin, add method Line.matcher(regex).
---
 build.gradle.kts                                   |  5 +-
 .../main/java/org/apache/calcite/util/Puffin.java  | 17 +++--
 .../java/org/apache/calcite/test/LintTest.java     | 72 ++++++++++++++++++++--
 .../java/org/apache/calcite/util/TestUnsafe.java   | 20 +++++-
 4 files changed, 99 insertions(+), 15 deletions(-)

diff --git a/build.gradle.kts b/build.gradle.kts
index 4b78f37ed7..90ce625516 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -603,8 +603,9 @@ allprojects {
                     replaceRegex("Method parameter list should not end in 
whitespace or newline", "(?<!;)\\s+\\) \\{", ") {")
                     replaceRegex("Method argument list should not end in 
whitespace or newline", "(?<!;)\\s+(\\)[);,])", "$1")
                     replaceRegex("Method argument list should not end in 
whitespace or newline", "(?<!;)(\\s+)(\\)+)[.]", "$2$1.")
-                    replaceRegex("Long assignment should be broken after '='", 
"^([^/*\"\\n]*) = ([^@\\n]*)\\(\n( *)", "$1 =\n$3$2(")
-                    replaceRegex("Long assignment should be broken after '='", 
"^([^/*\"\\n]*) = ([^@\\n]*)\\((.*,)\n( *)", "$1 =\n$4$2($3 ")
+                    replaceRegex("Long assignment should be broken after '=' 
(#1)", "^([^/*\"\\n]*) = ([^@\\n]*)\\(\n( *)", "$1 =\n$3$2(")
+                    replaceRegex("Long assignment should be broken after '=' 
(#2)", "^([^/*\"\\n]*) = ([^@\\n]*)\\((.*,)\n( *)", "$1 =\n$4$2($3 ")
+                    replaceRegex("Long assignment should be broken after '=' 
(#3)", "^([^/*\"\\n]*) = ([^@\\n\"?]*[ ][?][ ][^@\\n:]*)\n( *)", "$1 
=\n$3$2\n$3    ")
                     replaceRegex("trailing keyword: implements", "^([^*]*) 
(implements)\\n( *)", "$1\n$3$2 ")
                     // Assume developer copy-pasted the link, and updated text 
only, so the url is old, and we replace it with the proper one
                     replaceRegex(">[CALCITE-...] link styles: 1", 
"<a(?:(?!CALCITE-)[^>])++CALCITE-\\d+[^>]++>\\s*+\\[?(CALCITE-\\d+)\\]?", "<a 
href=\"https://issues.apache.org/jira/browse/\$1\";>[\$1]")
diff --git a/core/src/main/java/org/apache/calcite/util/Puffin.java 
b/core/src/main/java/org/apache/calcite/util/Puffin.java
index 8b5d6e286c..cf52a92d57 100644
--- a/core/src/main/java/org/apache/calcite/util/Puffin.java
+++ b/core/src/main/java/org/apache/calcite/util/Puffin.java
@@ -35,6 +35,7 @@ import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
+import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Stream;
 
@@ -171,6 +172,8 @@ public class Puffin {
     /** Returns whether the current line matches {@code regex}. */
     boolean matches(String regex);
 
+    Matcher matcher(String regex);
+
     /** Returns the text of the current line. */
     String line();
 
@@ -248,14 +251,14 @@ public class Puffin {
       return fnr;
     }
 
-    @Override public String filename() {
-      return source().toString();
-    }
-
     @Override public boolean isLast() {
       return last;
     }
 
+    @Override public String filename() {
+      return source().toString();
+    }
+
     @Override public Source source() {
       return source;
     }
@@ -272,8 +275,12 @@ public class Puffin {
       return line.endsWith(suffix);
     }
 
+    @Override public Matcher matcher(String regex) {
+      return pattern(regex).matcher(line);
+    }
+
     @Override public boolean matches(String regex) {
-      return pattern(regex).matcher(line).matches();
+      return matcher(regex).matches();
     }
 
     @Override public String line() {
diff --git a/core/src/test/java/org/apache/calcite/test/LintTest.java 
b/core/src/test/java/org/apache/calcite/test/LintTest.java
index bb1119d298..591ccb0f61 100644
--- a/core/src/test/java/org/apache/calcite/test/LintTest.java
+++ b/core/src/test/java/org/apache/calcite/test/LintTest.java
@@ -55,6 +55,26 @@ class LintTest {
         .add(line -> line.fnr() == 1,
             line -> line.globalState().fileCount++)
 
+        // Skip directive
+        .add(line -> line.matches(".* lint:skip ([0-9]+).*"),
+            line -> {
+              final Matcher matcher = line.matcher(".* lint:skip ([0-9]+).*");
+              if (matcher.matches()) {
+                int n = Integer.parseInt(matcher.group(1));
+                line.state().skipToLine = line.fnr() + n;
+              }
+            })
+
+        // Trailing space
+        .add(line -> line.endsWith(" "),
+            line -> line.state().message("Trailing space", line))
+
+        // Tab
+        .add(line -> line.contains("\t")
+                && !line.filename().endsWith(".txt")
+                && !skipping(line),
+            line -> line.state().message("Tab", line))
+
         // Comment without space
         .add(line -> line.matches(".* //[^ ].*")
                 && !line.source().fileOpt()
@@ -65,13 +85,19 @@ class LintTest {
                 && !line.contains("//CHECKSTYLE"),
             line -> line.state().message("'//' must be followed by ' '", line))
 
+        // In 'for (int i : list)', colon must be surrounded by space.
+        .add(line -> line.matches("^ *for \\(.*:.*")
+                && !line.matches(".*[^ ][ ][:][ ][^ ].*")
+                && isJava(line.filename()),
+            line -> line.state().message("':' must be surrounded by ' '", 
line))
+
         // Javadoc does not require '</p>', so we do not allow '</p>'
         .add(line -> line.state().inJavadoc()
                 && line.contains("</p>"),
             line -> line.state().message("no '</p>'", line))
 
         // No "**/"
-        .add(line -> line.contains("**/")
+        .add(line -> line.contains(" **/")
                 && line.state().inJavadoc(),
             line ->
                 line.state().message("no '**/'; use '*/'",
@@ -105,7 +131,8 @@ class LintTest {
                 && line.state().blockquoteCount == 0
                 && line.contains("* ")
                 && line.fnr() - 1 == line.state().starLine
-                && line.matches("^ *\\* [^<@].*"),
+                && line.matches("^ *\\* [^<@].*")
+                && isJava(line.filename()),
             line -> line.state().message("missing '<p>'", line))
 
         // The first "@param" of a javadoc block must be preceded by a blank
@@ -141,6 +168,22 @@ class LintTest {
         .build();
   }
 
+  /** Returns whether we are currently in a region where lint rules should not
+   * be applied. */
+  private static boolean skipping(Puffin.Line<GlobalState, FileState> line) {
+    return line.state().skipToLine >= 0
+        && line.fnr() < line.state().skipToLine;
+  }
+
+  /** Returns whether we are in a file that contains Java code. */
+  private static boolean isJava(String filename) {
+    return filename.endsWith(".java")
+        || filename.endsWith(".jj")
+        || filename.endsWith(".fmpp")
+        || filename.endsWith(".ftl")
+        || filename.equals("GuavaCharSource{memory}"); // for testing
+  }
+
   @Test void testProgramWorks() {
     final String code = "class MyClass {\n"
         + "  /** Paragraph.\n"
@@ -155,7 +198,17 @@ class LintTest {
         + "   * @see java.lang.String (should be preceded by blank line)\n"
         + "   **/\n"
         + "  String x = \"ok because it's not in javadoc:</p>\";\n"
-        + "  //comment without space\n"
+        + "  for (Map.Entry<String, Integer> e: entries) {\n"
+        + "    //comment without space\n"
+        + "  }\n"
+        + "  for (int i :tooFewSpacesAfter) {\n"
+        + "  }\n"
+        + "  for (int i  : tooManySpacesBefore) {\n"
+        + "  }\n"
+        + "  for (int i :   tooManySpacesAfter) {\n"
+        + "  }\n"
+        + "  for (int i : justRight) {\n"
+        + "  }\n"
         + "}\n";
     final String expectedMessages = "["
         + "GuavaCharSource{memory}:4:"
@@ -173,7 +226,15 @@ class LintTest {
         + "GuavaCharSource{memory}:12:"
         + "no '**/'; use '*/'\n"
         + "GuavaCharSource{memory}:14:"
+        + "':' must be surrounded by ' '\n"
+        + "GuavaCharSource{memory}:15:"
         + "'//' must be followed by ' '\n"
+        + "GuavaCharSource{memory}:17:"
+        + "':' must be surrounded by ' '\n"
+        + "GuavaCharSource{memory}:19:"
+        + "':' must be surrounded by ' '\n"
+        + "GuavaCharSource{memory}:21:"
+        + "':' must be surrounded by ' '\n"
         + "";
     final Puffin.Program<GlobalState> program = makeProgram();
     final StringWriter sw = new StringWriter();
@@ -191,11 +252,11 @@ class LintTest {
     assumeTrue(TestUnsafe.haveGit(), "Invalid git environment");
 
     final Puffin.Program<GlobalState> program = makeProgram();
-    final List<File> javaFiles = TestUnsafe.getJavaFiles();
+    final List<File> files = TestUnsafe.getTextFiles();
 
     final GlobalState g;
     try (PrintWriter pw = Util.printWriter(System.out)) {
-      g = program.execute(javaFiles.parallelStream().map(Sources::of), pw);
+      g = program.execute(files.parallelStream().map(Sources::of), pw);
     }
 
     g.messages.forEach(System.out::println);
@@ -338,6 +399,7 @@ class LintTest {
   /** Internal state of the lint rules, per file. */
   private static class FileState {
     final GlobalState global;
+    int skipToLine;
     int starLine;
     int atLine;
     int javadocStartLine;
diff --git a/core/src/test/java/org/apache/calcite/util/TestUnsafe.java 
b/core/src/test/java/org/apache/calcite/util/TestUnsafe.java
index a2ae509f7b..e9e24118c0 100644
--- a/core/src/test/java/org/apache/calcite/util/TestUnsafe.java
+++ b/core/src/test/java/org/apache/calcite/util/TestUnsafe.java
@@ -148,14 +148,28 @@ public abstract class TestUnsafe {
     return true;
   }
 
-  /** Returns a list of Java files in git under a given directory.
+  /** Returns a list of Java files in git. */
+  public static List<File> getJavaFiles() {
+    return getGitFiles("*.java");
+  }
+
+  /** Returns a list of text files in git. */
+  public static List<File> getTextFiles() {
+    return getGitFiles("*.bat", "*.cmd", "*.csv", "*.fmpp", "*.ftl",
+        "*.iq", "*.java", "*.json", "*.jj", "*.kt", "*.kts", "*.md",
+        "*.properties", "*.sh", "*.sql", "*.txt", "*.xml", "*.yaml",
+        "*.yml");
+  }
+
+  /** Returns a list of files in git matching a given pattern or patterns.
    *
    * <p>Assumes running Linux or macOS, and that git is available. */
-  public static List<File> getJavaFiles() {
+  public static List<File> getGitFiles(String... patterns) {
     String s;
     try {
       final List<String> argumentList =
-          ImmutableList.of("git", "ls-files", "*.java");
+          ImmutableList.<String>builder().add("git").add("ls-files")
+              .add(patterns).build();
       final File base = TestUtil.getBaseDir(TestUnsafe.class);
       try {
         final StringWriter sw = new StringWriter();

Reply via email to