1 /* 2 * Copyright (C) 2010 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.doclava; 18 19 import java.util.ArrayList; 20 import java.util.List; 21 import java.util.Set; 22 import java.util.TreeSet; 23 24 public class Errors { 25 public static boolean hadError = false; 26 private static boolean lintsAreErrors = false; 27 private static boolean warningsAreErrors = false; 28 private static List<LintBaselineEntry> baseline; 29 private static TreeSet<ErrorMessage> allErrors = new TreeSet<ErrorMessage>(); 30 31 public static class LintBaselineEntry { 32 final String file; 33 final String message; 34 final int code; 35 LintBaselineEntry(String file, String message, int code)36 LintBaselineEntry(String file, String message, int code) { 37 this.file = file; 38 this.message = message; 39 this.code = code; 40 } 41 } 42 43 public static class ErrorMessage implements Comparable<ErrorMessage> { 44 final int resolvedLevel; 45 final Error error; 46 final SourcePositionInfo pos; 47 final String msg; 48 ErrorMessage(int r, Error e, SourcePositionInfo p, String m)49 ErrorMessage(int r, Error e, SourcePositionInfo p, String m) { 50 resolvedLevel = r; 51 error = e; 52 pos = p; 53 msg = m; 54 } 55 56 @Override compareTo(ErrorMessage other)57 public int compareTo(ErrorMessage other) { 58 int r = pos.compareTo(other.pos); 59 if (r != 0) return r; 60 return msg.compareTo(other.msg); 61 } 62 parse(String line)63 public static LintBaselineEntry parse(String line) { 64 if (line.trim().length() == 0) { 65 return null; 66 } 67 // Format, per toString() below, is: 68 // file:row: lint: multi-word-message [code] 69 // We ignore the row number, so that edits don't invalidate baselines entries. 70 String[] words = line.split(" "); 71 72 String[] fileAndRow = words[0].split(":"); 73 if (fileAndRow.length != 2) { 74 System.err.println("ignored baseline entry (no file & row): " + line); 75 return null; 76 } 77 String file = fileAndRow[0]; 78 79 String lastWord = words[words.length - 1]; 80 if (lastWord.length() <= 3 || lastWord.charAt(0) != '[' 81 || lastWord.charAt(lastWord.length() - 1) != ']') { 82 System.err.println("ignored baseline entry (no error code): " + line); 83 return null; 84 } 85 int code = Integer.parseInt(lastWord.substring(1, lastWord.length() - 1)); 86 87 int messageStart = (words[0] + " lint: ").length(); 88 int messageEnd = line.length() - lastWord.length() - 1; 89 String message = line.substring(messageStart, messageEnd); 90 return new LintBaselineEntry(file, message, code); 91 } 92 93 94 @Override toString()95 public String toString() { 96 final String DEFAULT = "\033[0m"; 97 final String BOLD = "\033[1m"; 98 final String RED = "\033[31m"; 99 final String YELLOW = "\033[33m"; 100 final String CYAN = "\033[36m"; 101 102 StringBuilder res = new StringBuilder(); 103 if (Doclava.android) { 104 res.append(BOLD).append(pos.toString()).append(": "); 105 switch (resolvedLevel) { 106 case LINT: res.append(CYAN); break; 107 case WARNING: res.append(YELLOW); break; 108 case ERROR: res.append(RED); break; 109 } 110 switch (error.getLevel()) { 111 case LINT: res.append("lint: "); break; 112 case WARNING: res.append("warning: "); break; 113 case ERROR: res.append("error: "); break; 114 } 115 res.append(DEFAULT); 116 res.append(msg); 117 res.append(" [").append(error.code).append("]"); 118 } else { 119 // Sigh, some people are parsing the old format. 120 res.append(pos.toString()).append(": "); 121 switch (error.getLevel()) { 122 case LINT: res.append("lint "); break; 123 case WARNING: res.append("warning "); break; 124 case ERROR: res.append("error "); break; 125 default: break; 126 } 127 res.append(error.code).append(": "); 128 res.append(msg); 129 } 130 return res.toString(); 131 } 132 error()133 public Error error() { 134 return error; 135 } 136 } 137 error(Error error, MemberInfo mi, String text)138 public static void error(Error error, MemberInfo mi, String text) { 139 if (error.getLevel() == Errors.LINT) { 140 final String ident = "Doclava" + error.code; 141 for (AnnotationInstanceInfo a : mi.annotations()) { 142 if (a.type().qualifiedNameMatches("android", "annotation.SuppressLint")) { 143 for (AnnotationValueInfo val : a.elementValues()) { 144 if ("value".equals(val.element().name())) { 145 for (AnnotationValueInfo inner : (ArrayList<AnnotationValueInfo>) val.value()) { 146 if (ident.equals(String.valueOf(inner.value()))) { 147 return; 148 } 149 } 150 } 151 } 152 } 153 } 154 } 155 error(error, mi.position(), text); 156 } 157 error(Error error, SourcePositionInfo where, String text)158 public static void error(Error error, SourcePositionInfo where, String text) { 159 if (error.getLevel() == HIDDEN) { 160 return; 161 } 162 163 int resolvedLevel = error.getLevel(); 164 if (resolvedLevel == LINT && lintsAreErrors) { 165 resolvedLevel = isBaselined(error, where, text) ? LINT : ERROR; 166 } 167 if (resolvedLevel == WARNING && warningsAreErrors) { 168 resolvedLevel = ERROR; 169 } 170 171 if (where == null) { 172 where = new SourcePositionInfo("unknown", 0, 0); 173 } 174 175 allErrors.add(new ErrorMessage(resolvedLevel, error, where, text)); 176 177 if (resolvedLevel == ERROR) { 178 hadError = true; 179 } 180 } 181 clearErrors()182 public static void clearErrors() { 183 hadError = false; 184 allErrors.clear(); 185 } 186 printErrors()187 public static void printErrors() { 188 printErrors(allErrors); 189 } 190 printErrors(Set<ErrorMessage> errors)191 public static void printErrors(Set<ErrorMessage> errors) { 192 for (ErrorMessage m : errors) { 193 System.err.println(m.toString()); 194 } 195 System.err.flush(); 196 } 197 getErrors()198 public static Set<ErrorMessage> getErrors() { 199 return allErrors; 200 } 201 202 public static final int INHERIT = -1; 203 public static final int HIDDEN = 0; 204 205 /** 206 * Lint level means that we encountered inconsistent or broken documentation. 207 * These should be resolved, but don't impact API compatibility. 208 */ 209 public static final int LINT = 1; 210 211 /** 212 * Warning level means that we encountered some incompatible or inconsistent 213 * API change. These must be resolved to preserve API compatibility. 214 */ 215 public static final int WARNING = 2; 216 217 /** 218 * Error level means that we encountered severe trouble and were unable to 219 * output the requested documentation. 220 */ 221 public static final int ERROR = 3; 222 setLintsAreErrors(boolean val)223 public static void setLintsAreErrors(boolean val) { 224 lintsAreErrors = val; 225 } 226 setWarningsAreErrors(boolean val)227 public static void setWarningsAreErrors(boolean val) { 228 warningsAreErrors = val; 229 } 230 setLintBaseline(List<LintBaselineEntry> val)231 public static void setLintBaseline(List<LintBaselineEntry> val) { 232 baseline = val; 233 } 234 isBaselined(Error error, SourcePositionInfo where, String text)235 private static boolean isBaselined(Error error, SourcePositionInfo where, String text) { 236 if (baseline == null) { 237 return false; 238 } 239 // Baselines entries have had newlines removed, so remove newlines in 240 // what we're trying to match with the baselines as well. 241 text = text.replace("\n", ""); 242 for (LintBaselineEntry entry : baseline) { 243 if (where.file.endsWith(entry.file) && error.code == entry.code) { 244 if (text.equals(entry.message)) { 245 return true; 246 } 247 if (entry.message.startsWith(text.replaceAll(" in android$", ""))) { 248 // This is a hack: when we generate a compatchanges.html, we generate 249 // a bunch of comments without context, placed naively the "android" package. 250 // TODO: Remove this once all the lint violations in ChangeId constant javadoc 251 // have been cleaned up. 252 return true; 253 } 254 } 255 } 256 return false; 257 } 258 259 public static class Error { 260 public int code; 261 262 /** 263 * @deprecated This field should not be access directly. Instead, use 264 * {@link #getLevel()}. 265 */ 266 @Deprecated 267 public int level; 268 269 /** 270 * When {@code level} is set to {@link #INHERIT}, this is the parent from 271 * which the error will inherit its level. 272 */ 273 private final Error parent; 274 Error(int code, int level)275 public Error(int code, int level) { 276 this.code = code; 277 this.level = level; 278 this.parent = null; 279 sErrors.add(this); 280 } 281 Error(int code, Error parent)282 public Error(int code, Error parent) { 283 this.code = code; 284 this.level = -1; 285 this.parent = parent; 286 sErrors.add(this); 287 } 288 289 /** 290 * Returns the implied level for this error. 291 * <p> 292 * If the level is {@link #INHERIT}, the level will be returned for the 293 * parent. 294 * 295 * @return 296 * @throws IllegalStateException if the level is {@link #INHERIT} and the 297 * parent is {@code null} 298 */ getLevel()299 public int getLevel() { 300 if (level == INHERIT) { 301 if (parent == null) { 302 throw new IllegalStateException("Error with level INHERIT must have non-null parent"); 303 } 304 return parent.getLevel(); 305 } 306 return level; 307 } 308 309 /** 310 * Sets the level. 311 * <p> 312 * Valid arguments are: 313 * <ul> 314 * <li>{@link #HIDDEN} 315 * <li>{@link #WARNING} 316 * <li>{@link #ERROR} 317 * </ul> 318 * 319 * @param level the level to set 320 */ setLevel(int level)321 public void setLevel(int level) { 322 if (level == INHERIT) { 323 throw new IllegalArgumentException("Error level may not be set to INHERIT"); 324 } 325 this.level = level; 326 } 327 toString()328 public String toString() { 329 return "Error #" + this.code; 330 } 331 } 332 333 public static final List<Error> sErrors = new ArrayList<>(); 334 335 // Errors for API verification 336 public static final Error PARSE_ERROR = new Error(1, ERROR); 337 public static final Error ADDED_PACKAGE = new Error(2, WARNING); 338 public static final Error ADDED_CLASS = new Error(3, WARNING); 339 public static final Error ADDED_METHOD = new Error(4, WARNING); 340 public static final Error ADDED_FIELD = new Error(5, WARNING); 341 public static final Error ADDED_INTERFACE = new Error(6, WARNING); 342 public static final Error REMOVED_PACKAGE = new Error(7, WARNING); 343 public static final Error REMOVED_CLASS = new Error(8, WARNING); 344 public static final Error REMOVED_METHOD = new Error(9, WARNING); 345 public static final Error REMOVED_FIELD = new Error(10, WARNING); 346 public static final Error REMOVED_INTERFACE = new Error(11, WARNING); 347 public static final Error CHANGED_STATIC = new Error(12, WARNING); 348 public static final Error ADDED_FINAL = new Error(13, WARNING); 349 public static final Error CHANGED_TRANSIENT = new Error(14, WARNING); 350 public static final Error CHANGED_VOLATILE = new Error(15, WARNING); 351 public static final Error CHANGED_TYPE = new Error(16, WARNING); 352 public static final Error CHANGED_VALUE = new Error(17, WARNING); 353 public static final Error CHANGED_SUPERCLASS = new Error(18, WARNING); 354 public static final Error CHANGED_SCOPE = new Error(19, WARNING); 355 public static final Error CHANGED_ABSTRACT = new Error(20, WARNING); 356 public static final Error CHANGED_THROWS = new Error(21, WARNING); 357 public static final Error CHANGED_NATIVE = new Error(22, HIDDEN); 358 public static final Error CHANGED_CLASS = new Error(23, WARNING); 359 public static final Error CHANGED_DEPRECATED = new Error(24, WARNING); 360 public static final Error CHANGED_SYNCHRONIZED = new Error(25, WARNING); 361 public static final Error ADDED_FINAL_UNINSTANTIABLE = new Error(26, WARNING); 362 public static final Error REMOVED_FINAL = new Error(27, WARNING); 363 public static final Error REMOVED_DEPRECATED_CLASS = new Error(28, REMOVED_CLASS); 364 public static final Error REMOVED_DEPRECATED_METHOD = new Error(29, REMOVED_METHOD); 365 public static final Error REMOVED_DEPRECATED_FIELD = new Error(30, REMOVED_FIELD); 366 public static final Error ADDED_ABSTRACT_METHOD = new Error(31, ADDED_METHOD); 367 368 // Errors in javadoc generation 369 public static final Error UNRESOLVED_LINK = new Error(101, LINT); 370 public static final Error BAD_INCLUDE_TAG = new Error(102, LINT); 371 public static final Error UNKNOWN_TAG = new Error(103, LINT); 372 public static final Error UNKNOWN_PARAM_TAG_NAME = new Error(104, LINT); 373 public static final Error UNDOCUMENTED_PARAMETER = new Error(105, HIDDEN); // LINT 374 public static final Error BAD_ATTR_TAG = new Error(106, LINT); 375 public static final Error BAD_INHERITDOC = new Error(107, HIDDEN); // LINT 376 public static final Error HIDDEN_LINK = new Error(108, LINT); 377 public static final Error HIDDEN_CONSTRUCTOR = new Error(109, WARNING); 378 public static final Error UNAVAILABLE_SYMBOL = new Error(110, WARNING); 379 public static final Error HIDDEN_SUPERCLASS = new Error(111, WARNING); 380 public static final Error DEPRECATED = new Error(112, HIDDEN); 381 public static final Error DEPRECATION_MISMATCH = new Error(113, WARNING); 382 public static final Error MISSING_COMMENT = new Error(114, LINT); 383 public static final Error IO_ERROR = new Error(115, ERROR); 384 public static final Error NO_SINCE_DATA = new Error(116, HIDDEN); 385 public static final Error NO_FEDERATION_DATA = new Error(117, WARNING); 386 public static final Error BROKEN_SINCE_FILE = new Error(118, ERROR); 387 public static final Error INVALID_CONTENT_TYPE = new Error(119, ERROR); 388 public static final Error INVALID_SAMPLE_INDEX = new Error(120, ERROR); 389 public static final Error HIDDEN_TYPE_PARAMETER = new Error(121, WARNING); 390 public static final Error PRIVATE_SUPERCLASS = new Error(122, WARNING); 391 public static final Error NULLABLE = new Error(123, HIDDEN); // LINT 392 public static final Error INT_DEF = new Error(124, HIDDEN); // LINT 393 public static final Error REQUIRES_PERMISSION = new Error(125, LINT); 394 public static final Error BROADCAST_BEHAVIOR = new Error(126, LINT); 395 public static final Error SDK_CONSTANT = new Error(127, LINT); 396 public static final Error TODO = new Error(128, LINT); 397 public static final Error NO_ARTIFACT_DATA = new Error(129, HIDDEN); 398 public static final Error BROKEN_ARTIFACT_FILE = new Error(130, ERROR); 399 public static final Error JAVA_TAG_IN_COMMENT = new Error(131, LINT); 400 public static final Error MULTIPLE_SDK_EXT_INFO = new Error(132, ERROR); 401 setErrorLevel(int code, int level)402 public static boolean setErrorLevel(int code, int level) { 403 for (Error e : sErrors) { 404 if (e.code == code) { 405 e.setLevel(level); 406 return true; 407 } 408 } 409 return false; 410 } 411 } 412