Merge "Make doclava support baselining of lint issues" into main
diff --git a/src/com/google/doclava/Doclava.java b/src/com/google/doclava/Doclava.java
index dfdc428..5ab2b65 100644
--- a/src/com/google/doclava/Doclava.java
+++ b/src/com/google/doclava/Doclava.java
@@ -16,12 +16,15 @@
 
 package com.google.doclava;
 
+import static java.util.stream.Collectors.toList;
+
 import com.google.clearsilver.jsilver.JSilver;
 import com.google.clearsilver.jsilver.data.Data;
 import com.google.clearsilver.jsilver.resourceloader.ClassResourceLoader;
 import com.google.clearsilver.jsilver.resourceloader.CompositeResourceLoader;
 import com.google.clearsilver.jsilver.resourceloader.FileSystemResourceLoader;
 import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
+import com.google.doclava.Errors.ErrorMessage;
 import com.google.doclava.javadoc.RootDocImpl;
 import com.sun.javadoc.ClassDoc;
 import com.sun.javadoc.Doc;
@@ -60,6 +63,7 @@
 import java.util.Set;
 import java.util.SortedMap;
 import java.util.TreeMap;
+import java.util.function.Function;
 import java.util.jar.JarFile;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -175,6 +179,7 @@
     private static String proguardFile;
     private static String proofreadFile;
     private static String todoFile;
+    private static String lintBaselineFile;
     private static String stubsDir;
     private static HashSet<String> stubPackages;
     private static HashSet<String> stubImportPackages;
@@ -547,6 +552,21 @@
 
         options.add(
                 new Option() {
+                    private final List<String> names = List.of("-lintbaseline");
+                    @Override public int          getArgumentCount() { return 1; }
+                    @Override public String       getDescription() { return "Allowed lint errors"; }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() { return "<file>"; }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        lintBaselineFile = arguments.get(0);
+                        return true;
+                    }
+                }
+        );
+
+        options.add(
+                new Option() {
                     private final List<String> names = List.of("-error");
                     @Override public int          getArgumentCount() { return 1; }
                     @Override public String       getDescription() { return ""; }
@@ -1564,6 +1584,9 @@
     if (!readManifest()) {
       return false;
     }
+    if (!readLintBaselineFile(lintBaselineFile)) {
+      return false;
+    }
 
     // Set up the data structures
     Converter.makeInfo(root);
@@ -1815,6 +1838,36 @@
         return true;
     }
 
+    private static boolean readLintBaselineFile(String lintBaselineFile) {
+        if (lintBaselineFile == null) {
+          return true;
+        }
+
+        Function<String, SourcePositionInfo> mapper = (line) -> {
+            if (line.trim().length() == 0) {
+              return null;
+            }
+            String[] words = line.split(" ");
+            String[] cols = words[0].split(":");
+            if (cols.length != 2) {
+                System.err.println("ignored baseline entry: " + line);
+                return null;
+            }
+            int row = Integer.parseInt(cols[1]);
+            return new SourcePositionInfo(cols[0], row, 0);
+
+        };
+        try (BufferedReader reader = new BufferedReader(new FileReader(lintBaselineFile))) {
+            List<SourcePositionInfo> baseline =
+                    reader.lines().map(mapper).filter(line -> line != null).collect(toList());
+            Errors.setLintBaseline(baseline);
+            return true;
+        } catch (IOException exception) {
+            exception.printStackTrace(System.err);
+            return false;
+        }
+    }
+
   private static boolean readManifest() {
     manifestPermissions.clear();
     if (manifestFile == null) {
diff --git a/src/com/google/doclava/Errors.java b/src/com/google/doclava/Errors.java
index 05ac006..2a40716 100644
--- a/src/com/google/doclava/Errors.java
+++ b/src/com/google/doclava/Errors.java
@@ -25,6 +25,7 @@
   public static boolean hadError = false;
   private static boolean lintsAreErrors = false;
   private static boolean warningsAreErrors = false;
+  private static List<SourcePositionInfo> baseline;
   private static TreeSet<ErrorMessage> allErrors = new TreeSet<ErrorMessage>();
 
   public static class ErrorMessage implements Comparable<ErrorMessage> {
@@ -108,7 +109,7 @@
 
     int resolvedLevel = error.getLevel();
     if (resolvedLevel == LINT && lintsAreErrors) {
-      resolvedLevel = ERROR;
+      resolvedLevel = isBaselined(where) ? LINT : ERROR;
     }
     if (resolvedLevel == WARNING && warningsAreErrors) {
       resolvedLevel = ERROR;
@@ -174,6 +175,26 @@
     warningsAreErrors = val;
   }
 
+  public static void setLintBaseline(List<SourcePositionInfo> val) {
+    baseline = val;
+  }
+
+  private static boolean isBaselined(SourcePositionInfo errorPosition) {
+    if (baseline == null) {
+      return false;
+    }
+    for (SourcePositionInfo baselinedPosition : baseline) {
+      if (errorPosition.file.endsWith(baselinedPosition.file)) {
+        // The line number information
+        // 1) seems to be wrong, at least for broken link/see tags
+        // 2) will lead to baselines not working when files are edited
+        // So we ignore that information and just allow all lint errors in the file
+        return true;
+      }
+    }
+    return false;
+  }
+
   public static class Error {
     public int code;