1 /* 2 * Copyright (C) 2014 The Dagger Authors. 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 dagger.internal.codegen.validation; 18 19 import static dagger.internal.codegen.base.ElementFormatter.elementToString; 20 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; 21 import static dagger.internal.codegen.xprocessing.XElements.transitivelyEncloses; 22 import static javax.tools.Diagnostic.Kind.ERROR; 23 import static javax.tools.Diagnostic.Kind.NOTE; 24 import static javax.tools.Diagnostic.Kind.WARNING; 25 26 import androidx.room.compiler.processing.XAnnotation; 27 import androidx.room.compiler.processing.XAnnotationValue; 28 import androidx.room.compiler.processing.XElement; 29 import androidx.room.compiler.processing.XMessager; 30 import com.google.auto.value.AutoValue; 31 import com.google.common.collect.ImmutableSet; 32 import com.google.common.graph.Traverser; 33 import com.google.errorprone.annotations.CanIgnoreReturnValue; 34 import com.google.errorprone.annotations.CheckReturnValue; 35 import java.util.Optional; 36 import javax.tools.Diagnostic; 37 import javax.tools.Diagnostic.Kind; 38 39 /** A collection of issues to report for source code. */ 40 @CheckReturnValue 41 public final class ValidationReport { 42 private static final Traverser<ValidationReport> SUBREPORTS = 43 Traverser.forTree(report -> report.subreports); 44 45 private final XElement subject; 46 private final ImmutableSet<Item> items; 47 private final ImmutableSet<ValidationReport> subreports; 48 private final boolean markedDirty; 49 private boolean hasPrintedErrors; 50 ValidationReport( XElement subject, ImmutableSet<Item> items, ImmutableSet<ValidationReport> subreports, boolean markedDirty)51 private ValidationReport( 52 XElement subject, 53 ImmutableSet<Item> items, 54 ImmutableSet<ValidationReport> subreports, 55 boolean markedDirty) { 56 this.subject = subject; 57 this.items = items; 58 this.subreports = subreports; 59 this.markedDirty = markedDirty; 60 } 61 62 /** Returns the items from this report and all transitive subreports. */ allItems()63 public ImmutableSet<Item> allItems() { 64 return ImmutableSet.copyOf(SUBREPORTS.depthFirstPreOrder(this)) 65 .stream() 66 .flatMap(report -> report.items.stream()) 67 .collect(toImmutableSet()); 68 } 69 70 /** 71 * Returns {@code true} if there are no errors in this report or any subreports and markedDirty is 72 * {@code false}. 73 */ isClean()74 public boolean isClean() { 75 if (markedDirty) { 76 return false; 77 } 78 for (Item item : items) { 79 switch (item.kind()) { 80 case ERROR: 81 return false; 82 default: 83 break; 84 } 85 } 86 for (ValidationReport subreport : subreports) { 87 if (!subreport.isClean()) { 88 return false; 89 } 90 } 91 return true; 92 } 93 94 /** 95 * Prints all messages to {@code messager} (and recurs for subreports). If a message's {@linkplain 96 * Item#element() element} is contained within the report's subject, associates the message with 97 * the message's element. Otherwise, since {@link Diagnostic} reporting is expected to be 98 * associated with elements that are currently being compiled, associates the message with the 99 * subject itself and prepends a reference to the item's element. 100 */ printMessagesTo(XMessager messager)101 public void printMessagesTo(XMessager messager) { 102 if (hasPrintedErrors) { 103 // Avoid printing the errors from this validation report more than once. 104 return; 105 } 106 hasPrintedErrors = true; 107 for (Item item : items) { 108 if (transitivelyEncloses(subject, item.element())) { 109 if (item.annotation().isPresent()) { 110 if (item.annotationValue().isPresent()) { 111 messager.printMessage( 112 item.kind(), 113 item.message(), 114 item.element(), 115 item.annotation().get(), 116 item.annotationValue().get()); 117 } else { 118 messager.printMessage( 119 item.kind(), item.message(), item.element(), item.annotation().get()); 120 } 121 } else { 122 messager.printMessage(item.kind(), item.message(), item.element()); 123 } 124 } else { 125 String message = String.format("[%s] %s", elementToString(item.element()), item.message()); 126 messager.printMessage(item.kind(), message, subject); 127 } 128 } 129 for (ValidationReport subreport : subreports) { 130 subreport.printMessagesTo(messager); 131 } 132 } 133 134 /** Metadata about a {@link ValidationReport} item. */ 135 @AutoValue 136 public abstract static class Item { message()137 public abstract String message(); kind()138 public abstract Kind kind(); element()139 public abstract XElement element(); annotation()140 public abstract Optional<XAnnotation> annotation(); annotationValue()141 abstract Optional<XAnnotationValue> annotationValue(); 142 } 143 about(XElement subject)144 public static Builder about(XElement subject) { 145 return new Builder(subject); 146 } 147 148 /** A {@link ValidationReport} builder. */ 149 public static final class Builder { 150 private final XElement subject; 151 private final ImmutableSet.Builder<Item> items = ImmutableSet.builder(); 152 private final ImmutableSet.Builder<ValidationReport> subreports = ImmutableSet.builder(); 153 private boolean markedDirty; 154 Builder(XElement subject)155 private Builder(XElement subject) { 156 this.subject = subject; 157 } 158 159 @CanIgnoreReturnValue addItems(Iterable<Item> newItems)160 Builder addItems(Iterable<Item> newItems) { 161 items.addAll(newItems); 162 return this; 163 } 164 165 @CanIgnoreReturnValue addError(String message)166 public Builder addError(String message) { 167 return addError(message, subject); 168 } 169 170 @CanIgnoreReturnValue addError(String message, XElement element)171 public Builder addError(String message, XElement element) { 172 return addItem(message, ERROR, element); 173 } 174 175 @CanIgnoreReturnValue addError(String message, XElement element, XAnnotation annotation)176 public Builder addError(String message, XElement element, XAnnotation annotation) { 177 return addItem(message, ERROR, element, annotation); 178 } 179 180 @CanIgnoreReturnValue addError( String message, XElement element, XAnnotation annotation, XAnnotationValue annotationValue)181 public Builder addError( 182 String message, 183 XElement element, 184 XAnnotation annotation, 185 XAnnotationValue annotationValue) { 186 return addItem(message, ERROR, element, annotation, annotationValue); 187 } 188 189 @CanIgnoreReturnValue addWarning(String message)190 Builder addWarning(String message) { 191 return addWarning(message, subject); 192 } 193 194 @CanIgnoreReturnValue addWarning(String message, XElement element)195 Builder addWarning(String message, XElement element) { 196 return addItem(message, WARNING, element); 197 } 198 199 @CanIgnoreReturnValue addWarning(String message, XElement element, XAnnotation annotation)200 Builder addWarning(String message, XElement element, XAnnotation annotation) { 201 return addItem(message, WARNING, element, annotation); 202 } 203 204 @CanIgnoreReturnValue addWarning( String message, XElement element, XAnnotation annotation, XAnnotationValue annotationValue)205 Builder addWarning( 206 String message, 207 XElement element, 208 XAnnotation annotation, 209 XAnnotationValue annotationValue) { 210 return addItem(message, WARNING, element, annotation, annotationValue); 211 } 212 213 @CanIgnoreReturnValue addNote(String message)214 Builder addNote(String message) { 215 return addNote(message, subject); 216 } 217 218 @CanIgnoreReturnValue addNote(String message, XElement element)219 Builder addNote(String message, XElement element) { 220 return addItem(message, NOTE, element); 221 } 222 223 @CanIgnoreReturnValue addNote(String message, XElement element, XAnnotation annotation)224 Builder addNote(String message, XElement element, XAnnotation annotation) { 225 return addItem(message, NOTE, element, annotation); 226 } 227 228 @CanIgnoreReturnValue addNote( String message, XElement element, XAnnotation annotation, XAnnotationValue annotationValue)229 Builder addNote( 230 String message, 231 XElement element, 232 XAnnotation annotation, 233 XAnnotationValue annotationValue) { 234 return addItem(message, NOTE, element, annotation, annotationValue); 235 } 236 237 @CanIgnoreReturnValue addItem(String message, Kind kind, XElement element)238 Builder addItem(String message, Kind kind, XElement element) { 239 return addItem(message, kind, element, Optional.empty(), Optional.empty()); 240 } 241 242 @CanIgnoreReturnValue addItem(String message, Kind kind, XElement element, XAnnotation annotation)243 Builder addItem(String message, Kind kind, XElement element, XAnnotation annotation) { 244 return addItem(message, kind, element, Optional.of(annotation), Optional.empty()); 245 } 246 247 @CanIgnoreReturnValue addItem( String message, Kind kind, XElement element, XAnnotation annotation, XAnnotationValue annotationValue)248 Builder addItem( 249 String message, 250 Kind kind, 251 XElement element, 252 XAnnotation annotation, 253 XAnnotationValue annotationValue) { 254 return addItem(message, kind, element, Optional.of(annotation), Optional.of(annotationValue)); 255 } 256 257 @CanIgnoreReturnValue addItem( String message, Kind kind, XElement element, Optional<XAnnotation> annotation, Optional<XAnnotationValue> annotationValue)258 private Builder addItem( 259 String message, 260 Kind kind, 261 XElement element, 262 Optional<XAnnotation> annotation, 263 Optional<XAnnotationValue> annotationValue) { 264 items.add( 265 new AutoValue_ValidationReport_Item( 266 message, 267 kind, 268 element, 269 annotation, 270 annotationValue)); 271 return this; 272 } 273 274 /** 275 * If called, then {@link #isClean()} will return {@code false} even if there are no error items 276 * in the report. 277 */ markDirty()278 void markDirty() { 279 this.markedDirty = true; 280 } 281 282 @CanIgnoreReturnValue addSubreport(ValidationReport subreport)283 public Builder addSubreport(ValidationReport subreport) { 284 subreports.add(subreport); 285 return this; 286 } 287 build()288 public ValidationReport build() { 289 return new ValidationReport(subject, items.build(), subreports.build(), markedDirty); 290 } 291 } 292 } 293