001/* 002 * Copyright (C) 2022 - 2023, the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package io.github.ascopes.jct.assertions; 017 018import static io.github.ascopes.jct.utils.IterableUtils.combineOneOrMore; 019import static io.github.ascopes.jct.utils.IterableUtils.requireNonNullValues; 020import static java.util.Objects.requireNonNull; 021import static java.util.function.Predicate.not; 022 023import io.github.ascopes.jct.diagnostics.TraceDiagnostic; 024import io.github.ascopes.jct.repr.TraceDiagnosticListRepresentation; 025import io.github.ascopes.jct.utils.StringUtils; 026import java.util.ArrayList; 027import java.util.LinkedHashSet; 028import java.util.List; 029import java.util.Locale; 030import java.util.function.Predicate; 031import java.util.stream.Collectors; 032import java.util.stream.StreamSupport; 033import javax.tools.Diagnostic.Kind; 034import javax.tools.JavaFileObject; 035import org.apiguardian.api.API; 036import org.apiguardian.api.API.Status; 037import org.assertj.core.api.AbstractListAssert; 038 039/** 040 * Assertions for a list of diagnostics. 041 * 042 * @author Ashley Scopes 043 * @since 0.0.1 044 */ 045@API(since = "0.0.1", status = Status.STABLE) 046public final class TraceDiagnosticListAssert 047 extends 048 AbstractListAssert<TraceDiagnosticListAssert, List<? extends TraceDiagnostic<? extends JavaFileObject>>, TraceDiagnostic<? extends JavaFileObject>, TraceDiagnosticAssert> { 049 050 /** 051 * Initialize this assertion. 052 * 053 * @param traceDiagnostics the diagnostics to perform assertions on. 054 */ 055 public TraceDiagnosticListAssert( 056 List<? extends TraceDiagnostic<? extends JavaFileObject>> traceDiagnostics 057 ) { 058 super(traceDiagnostics, TraceDiagnosticListAssert.class); 059 info.useRepresentation(TraceDiagnosticListRepresentation.getInstance()); 060 } 061 062 /** 063 * Get a {@link TraceDiagnosticListAssert} across all diagnostics that have the {@link Kind#ERROR} 064 * kind. 065 * 066 * @return the assertion object for {@link Kind#ERROR} diagnostics. 067 * @throws AssertionError if the list is null. 068 */ 069 public TraceDiagnosticListAssert errors() { 070 return filteringByKinds(DiagnosticKindAssert.ERROR_DIAGNOSTIC_KINDS); 071 } 072 073 /** 074 * Get a {@link TraceDiagnosticListAssert} across all diagnostics that have the 075 * {@link Kind#WARNING} or {@link Kind#MANDATORY_WARNING} kind. 076 * 077 * @return the assertion object for {@link Kind#WARNING} and {@link Kind#MANDATORY_WARNING} 078 * diagnostics. 079 * @throws AssertionError if the list is null. 080 */ 081 public TraceDiagnosticListAssert warnings() { 082 return filteringByKinds(DiagnosticKindAssert.WARNING_DIAGNOSTIC_KINDS); 083 } 084 085 /** 086 * Get a {@link TraceDiagnosticListAssert} across all diagnostics that have the 087 * {@link Kind#WARNING} kind. 088 * 089 * @return the assertion object for {@link Kind#WARNING} diagnostics. 090 * @throws AssertionError if the list is null. 091 */ 092 public TraceDiagnosticListAssert customWarnings() { 093 return filteringByKinds(Kind.WARNING); 094 } 095 096 /** 097 * Get a {@link TraceDiagnosticListAssert} across all diagnostics that have the 098 * {@link Kind#MANDATORY_WARNING} kind. 099 * 100 * @return the assertion object for {@link Kind#MANDATORY_WARNING} diagnostics. 101 * @throws AssertionError if the list is null. 102 */ 103 public TraceDiagnosticListAssert mandatoryWarnings() { 104 return filteringByKinds(Kind.MANDATORY_WARNING); 105 } 106 107 /** 108 * Get a {@link TraceDiagnosticListAssert} across all diagnostics that have the {@link Kind#NOTE} 109 * kind. 110 * 111 * @return the assertion object for {@link Kind#NOTE} diagnostics. 112 * @throws AssertionError if the list is null. 113 */ 114 public TraceDiagnosticListAssert notes() { 115 return filteringByKinds(Kind.NOTE); 116 } 117 118 /** 119 * Get a {@link TraceDiagnosticListAssert} across all diagnostics that have the {@link Kind#OTHER} 120 * kind. 121 * 122 * @return the assertion object for {@link Kind#OTHER} diagnostics. 123 * @throws AssertionError if the list is null. 124 */ 125 public TraceDiagnosticListAssert others() { 126 return filteringByKinds(Kind.OTHER); 127 } 128 129 /** 130 * Get a {@link TraceDiagnosticListAssert} that contains diagnostics corresponding to any of the 131 * given {@link Kind kinds}. 132 * 133 * @param kind the first kind to match. 134 * @param moreKinds additional kinds to match. 135 * @return the assertion object for the filtered diagnostics. 136 * @throws AssertionError if this list is null. 137 * @throws NullPointerException if any of the kinds are null. 138 */ 139 public TraceDiagnosticListAssert filteringByKinds(Kind kind, Kind... moreKinds) { 140 requireNonNull(kind, "kind must not be null"); 141 requireNonNullValues(moreKinds, "moreKinds"); 142 return filteringByKinds(combineOneOrMore(kind, moreKinds)); 143 } 144 145 146 /** 147 * Get a {@link TraceDiagnosticListAssert} that contains diagnostics corresponding to any of the 148 * given {@link Kind kinds}. 149 * 150 * @param kinds the kinds to match. 151 * @return the assertion object for the filtered diagnostics. 152 * @throws AssertionError if this list is null. 153 * @throws NullPointerException if any of the kinds are null. 154 */ 155 public TraceDiagnosticListAssert filteringByKinds(Iterable<Kind> kinds) { 156 requireNonNullValues(kinds, "kinds"); 157 return filteringBy(kind(kinds)); 158 } 159 160 /** 161 * Get a {@link TraceDiagnosticListAssert} that contains diagnostics corresponding to none of the 162 * given {@link Kind kinds}. 163 * 164 * @param kind the first kind to ensure are not matched. 165 * @param moreKinds additional kinds to ensure are not matched. 166 * @return the assertion object for the filtered diagnostics. 167 * @throws AssertionError if this list is null. 168 * @throws NullPointerException if any of the kinds are null. 169 */ 170 public TraceDiagnosticListAssert excludingKinds(Kind kind, Kind... moreKinds) { 171 requireNonNull(kind, "kind must not be null"); 172 requireNonNullValues(moreKinds, "moreKinds"); 173 return excludingKinds(combineOneOrMore(kind, moreKinds)); 174 } 175 176 /** 177 * Get a {@link TraceDiagnosticListAssert} that contains diagnostics corresponding to none of the 178 * given {@link Kind kinds}. 179 * 180 * @param kinds the kinds to filter out. 181 * @return the assertion object for the filtered diagnostics. 182 * @throws AssertionError if this list is null. 183 * @throws NullPointerException if any of the kinds are null. 184 */ 185 public TraceDiagnosticListAssert excludingKinds(Iterable<Kind> kinds) { 186 requireNonNullValues(kinds, "kinds"); 187 return filteringBy(not(kind(kinds))); 188 } 189 190 /** 191 * Assert that this list has no {@link Kind#ERROR} diagnostics. 192 * 193 * @return this assertion object for further call chaining. 194 * @throws AssertionError if this list is null. 195 */ 196 public TraceDiagnosticListAssert hasNoErrors() { 197 return hasNoDiagnosticsOfKinds(DiagnosticKindAssert.ERROR_DIAGNOSTIC_KINDS); 198 } 199 200 /** 201 * Assert that this list has no {@link Kind#ERROR}, {@link Kind#WARNING}, or 202 * {@link Kind#MANDATORY_WARNING} diagnostics. 203 * 204 * @return this assertion object for further call chaining. 205 * @throws AssertionError if this list is null. 206 */ 207 public TraceDiagnosticListAssert hasNoErrorsOrWarnings() { 208 return hasNoDiagnosticsOfKinds(DiagnosticKindAssert.WARNING_AND_ERROR_DIAGNOSTIC_KINDS); 209 } 210 211 /** 212 * Assert that this list has no {@link Kind#WARNING} or {@link Kind#MANDATORY_WARNING} 213 * diagnostics. 214 * 215 * @return this assertion object for further call chaining. 216 * @throws AssertionError if this list is null. 217 */ 218 public TraceDiagnosticListAssert hasNoWarnings() { 219 return hasNoDiagnosticsOfKinds(DiagnosticKindAssert.WARNING_DIAGNOSTIC_KINDS); 220 } 221 222 /** 223 * Assert that this list has no {@link Kind#WARNING} diagnostics. 224 * 225 * @return this assertion object for further call chaining. 226 * @throws AssertionError if this list is null. 227 */ 228 public TraceDiagnosticListAssert hasNoCustomWarnings() { 229 return hasNoDiagnosticsOfKinds(Kind.WARNING); 230 } 231 232 /** 233 * Assert that this list has no {@link Kind#MANDATORY_WARNING} diagnostics. 234 * 235 * @return this assertion object for further call chaining. 236 * @throws AssertionError if this list is null. 237 */ 238 public TraceDiagnosticListAssert hasNoMandatoryWarnings() { 239 return hasNoDiagnosticsOfKinds(Kind.MANDATORY_WARNING); 240 } 241 242 /** 243 * Assert that this list has no {@link Kind#NOTE} diagnostics. 244 * 245 * @return this assertion object for further call chaining. 246 * @throws AssertionError if this list is null. 247 */ 248 public TraceDiagnosticListAssert hasNoNotes() { 249 return hasNoDiagnosticsOfKinds(Kind.NOTE); 250 } 251 252 /** 253 * Assert that this list has no {@link Kind#OTHER} diagnostics. 254 * 255 * @return this assertion object for further call chaining. 256 * @throws AssertionError if this list is null. 257 */ 258 public TraceDiagnosticListAssert hasNoOtherDiagnostics() { 259 return hasNoDiagnosticsOfKinds(Kind.OTHER); 260 } 261 262 /** 263 * Assert that this list has no diagnostics matching any of the given kinds. 264 * 265 * @param kind the first kind to check for. 266 * @param moreKinds any additional kinds to check for. 267 * @return this assertion object for further call chaining. 268 * @throws AssertionError if the diagnostic list is null. 269 * @throws NullPointerException if the kind or more kinds are null. 270 */ 271 public TraceDiagnosticListAssert hasNoDiagnosticsOfKinds(Kind kind, Kind... moreKinds) { 272 requireNonNull(kind, "kind must not be null"); 273 requireNonNullValues(moreKinds, "moreKinds"); 274 return hasNoDiagnosticsOfKinds(combineOneOrMore(kind, moreKinds)); 275 } 276 277 /** 278 * Assert that this list has no diagnostics matching any of the given kinds. 279 * 280 * @param kinds the kinds to check for. 281 * @return this assertion object for further call chaining. 282 * @throws AssertionError if the diagnostic list is null. 283 * @throws NullPointerException if any of the kinds are null. 284 */ 285 public TraceDiagnosticListAssert hasNoDiagnosticsOfKinds(Iterable<Kind> kinds) { 286 requireNonNullValues(kinds, "kinds"); 287 288 var actualDiagnostics = actual 289 .stream() 290 .filter(kind(kinds)) 291 .collect(Collectors.toList()); 292 293 if (!actualDiagnostics.isEmpty()) { 294 var allKindsString = StreamSupport.stream(kinds.spliterator(), false) 295 .map(next -> next.name().toLowerCase(Locale.ROOT).replace('_', ' ')) 296 .sorted() 297 .collect(Collectors.collectingAndThen( 298 Collectors.toUnmodifiableList(), 299 names -> StringUtils.toWordedList(names, ", ", ", or ") 300 )); 301 302 failWithActualExpectedAndMessage( 303 actualDiagnostics.size(), 304 0, 305 "Expected no %s diagnostics.\n\nDiagnostics:\n%s", 306 allKindsString, 307 TraceDiagnosticListRepresentation.getInstance().toStringOf(actualDiagnostics) 308 ); 309 } 310 311 return myself; 312 } 313 314 /** 315 * Filter diagnostics by a given predicate and return an assertion object that applies to all 316 * diagnostics that match that predicate. 317 * 318 * @param predicate the predicate to match. 319 * @return the assertion object for the diagnostics that match. 320 * @throws NullPointerException if the predicate is null. 321 * @throws AssertionError if the diagnostic list is null. 322 */ 323 public TraceDiagnosticListAssert filteringBy( 324 Predicate<TraceDiagnostic<? extends JavaFileObject>> predicate 325 ) { 326 requireNonNull(predicate, "predicate must not be null"); 327 328 isNotNull(); 329 330 return actual 331 .stream() 332 .filter(predicate) 333 .collect(Collectors.collectingAndThen( 334 Collectors.toUnmodifiableList(), 335 TraceDiagnosticListAssert::new 336 )); 337 } 338 339 @Override 340 protected TraceDiagnosticAssert toAssert( 341 TraceDiagnostic<? extends JavaFileObject> value, 342 String description 343 ) { 344 return new TraceDiagnosticAssert(value).describedAs(description); 345 } 346 347 @Override 348 protected TraceDiagnosticListAssert newAbstractIterableAssert( 349 Iterable<? extends TraceDiagnostic<? extends JavaFileObject>> iterable 350 ) { 351 var list = new ArrayList<TraceDiagnostic<? extends JavaFileObject>>(); 352 iterable.forEach(list::add); 353 return new TraceDiagnosticListAssert(list); 354 } 355 356 private Predicate<TraceDiagnostic<? extends JavaFileObject>> kind(Iterable<Kind> kinds) { 357 var kindsSet = new LinkedHashSet<Kind>(); 358 kinds.forEach(kindsSet::add); 359 360 return diagnostic -> kindsSet.contains(diagnostic.getKind()); 361 } 362}