tech.picnic.errorprone.bugpatterns.RequestMappingAnnotation Maven / Gradle / Ivy
Show all versions of error-prone-contrib Show documentation
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.ALL;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
import static com.google.errorprone.matchers.Matchers.annotations;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.isSameType;
import static com.google.errorprone.matchers.Matchers.isType;
import static com.google.errorprone.matchers.Matchers.methodHasParameters;
import static com.google.errorprone.matchers.Matchers.not;
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import java.io.InputStream;
import java.time.ZoneId;
import java.util.Locale;
import java.util.TimeZone;
/**
* A {@link BugChecker} that flags {@code @RequestMapping} methods that have one or more parameters
* that appear to lack a relevant annotation.
*
* Matched mappings are {@code @{Delete,Get,Patch,Post,Put,Request}Mapping}.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary = "Make sure all `@RequestMapping` method parameters are annotated",
link = BUG_PATTERNS_BASE_URL + "RequestMappingAnnotation",
linkType = CUSTOM,
severity = WARNING,
tags = LIKELY_ERROR)
public final class RequestMappingAnnotation extends BugChecker implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
private static final String ANN_PACKAGE_PREFIX = "org.springframework.web.bind.annotation.";
// XXX: Generalize this logic to fully support Spring meta-annotations, then update the class
// documentation.
private static final Matcher HAS_MAPPING_ANNOTATION =
annotations(
AT_LEAST_ONE,
anyOf(
isType(ANN_PACKAGE_PREFIX + "DeleteMapping"),
isType(ANN_PACKAGE_PREFIX + "GetMapping"),
isType(ANN_PACKAGE_PREFIX + "PatchMapping"),
isType(ANN_PACKAGE_PREFIX + "PostMapping"),
isType(ANN_PACKAGE_PREFIX + "PutMapping"),
isType(ANN_PACKAGE_PREFIX + "RequestMapping")));
// XXX: Add other parameters as necessary. Also consider whether it makes sense to have WebMVC-
// and WebFlux-specific logic. See
// https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-arguments
// and
// https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-ann-arguments.
private static final Matcher LACKS_PARAMETER_ANNOTATION =
not(
methodHasParameters(
ALL,
anyOf(
annotations(
AT_LEAST_ONE,
anyOf(
isType(ANN_PACKAGE_PREFIX + "PathVariable"),
isType(ANN_PACKAGE_PREFIX + "RequestAttribute"),
isType(ANN_PACKAGE_PREFIX + "RequestBody"),
isType(ANN_PACKAGE_PREFIX + "RequestHeader"),
isType(ANN_PACKAGE_PREFIX + "RequestParam"),
isType(ANN_PACKAGE_PREFIX + "RequestPart"),
isType(
"org.springframework.security.core.annotation.CurrentSecurityContext"))),
isSameType(InputStream.class.getCanonicalName()),
isSameType(Locale.class.getCanonicalName()),
isSameType(TimeZone.class.getCanonicalName()),
isSameType(ZoneId.class.getCanonicalName()),
isSameType("jakarta.servlet.http.HttpServletRequest"),
isSameType("jakarta.servlet.http.HttpServletResponse"),
isSameType("javax.servlet.http.HttpServletRequest"),
isSameType("javax.servlet.http.HttpServletResponse"),
isSameType("org.springframework.http.HttpMethod"),
isSameType("org.springframework.ui.Model"),
isSameType("org.springframework.validation.BindingResult"),
isSameType("org.springframework.web.context.request.NativeWebRequest"),
isSameType("org.springframework.web.context.request.WebRequest"),
isSameType("org.springframework.web.server.ServerWebExchange"),
isSameType("org.springframework.web.util.UriBuilder"),
isSameType("org.springframework.web.util.UriComponentsBuilder"))));
/** Instantiates a new {@link RequestMappingAnnotation} instance. */
public RequestMappingAnnotation() {}
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
// XXX: Auto-add `@RequestParam` where applicable.
// XXX: What about the `PurchasingProposerRequestParams` in POM? Implies `@RequestBody`?
// (Documentation doesn't mention this, IIUC.)
return HAS_MAPPING_ANNOTATION.matches(tree, state)
&& LACKS_PARAMETER_ANNOTATION.matches(tree, state)
? buildDescription(tree)
.setMessage(
"""
Not all parameters of this request mapping method are annotated; this may be a \
mistake. If the unannotated parameters represent query string parameters, annotate \
them with `@RequestParam`.""")
.build()
: Description.NO_MATCH;
}
}