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