diff --git a/slack-lint-checks/src/main/java/slack/lint/retrofit/RetrofitUsageDetector.kt b/slack-lint-checks/src/main/java/slack/lint/retrofit/RetrofitUsageDetector.kt index b2c772a4..3f4aa493 100644 --- a/slack-lint-checks/src/main/java/slack/lint/retrofit/RetrofitUsageDetector.kt +++ b/slack-lint-checks/src/main/java/slack/lint/retrofit/RetrofitUsageDetector.kt @@ -61,12 +61,19 @@ class RetrofitUsageDetector : Detector(), SourceCodeScanner { return } + val isMultipart = FQCN_MULTIPART in annotationsByFqcn + if (isMultipart && !isBodyMethod) { + node.report("@Multipart requires @PUT, @POST, or @PATCH.") + return + } + val hasPath = (httpAnnotation.findDeclaredAttributeValue("value")?.evaluate() as? String)?.isNotBlank() ?: false var hasBodyParam = false var hasFieldParams = false + var hasPartParams = false var hasUrlParam = false for (parameter in node.uastParameters) { @@ -101,6 +108,12 @@ class RetrofitUsageDetector : Detector(), SourceCodeScanner { } else { hasUrlParam = true } + } else if (parameter.hasAnnotation(FQCN_PART)) { + if (!isBodyMethod) { + httpAnnotation.report("@Part param requires @PUT, @POST, or @PATCH.") + } else { + hasPartParams = true + } } } @@ -112,7 +125,13 @@ class RetrofitUsageDetector : Detector(), SourceCodeScanner { quickFixData = LintFix.create().removeNode(context, annotation.sourcePsiElement!!), ) } - } else if (isBodyMethod && !hasBodyParam && !hasFieldParams) { + } else if (isMultipart) { + if (hasBodyParam || hasFieldParams) { + httpAnnotation.report("@Multipart methods should only contain @Part parameters.") + } else if (!hasPartParams) { + httpAnnotation.report("@Multipart methods should contain at least one @Part parameter.") + } + } else if (isBodyMethod && !hasBodyParam && !hasFieldParams && !hasPartParams) { httpAnnotation.report("This annotation requires an `@Body` parameter.") } if (!hasPath && !hasUrlParam) { @@ -144,7 +163,9 @@ class RetrofitUsageDetector : Detector(), SourceCodeScanner { private val HTTP_BODY_ANNOTATIONS = setOf("retrofit2.http.PATCH", "retrofit2.http.POST", "retrofit2.http.PUT") private const val FQCN_FORM_ENCODED = "retrofit2.http.FormUrlEncoded" + private const val FQCN_MULTIPART = "retrofit2.http.Multipart" private const val FQCN_FIELD = "retrofit2.http.Field" + private const val FQCN_PART = "retrofit2.http.Part" private const val FQCN_FIELD_MAP = "retrofit2.http.FieldMap" private const val FQCN_BODY = "retrofit2.http.Body" private const val FQCN_URL = "retrofit2.http.Url" diff --git a/slack-lint-checks/src/test/java/slack/lint/retrofit/RetrofitUsageDetectorTest.kt b/slack-lint-checks/src/test/java/slack/lint/retrofit/RetrofitUsageDetectorTest.kt index a8d4c00a..325e8ef9 100644 --- a/slack-lint-checks/src/test/java/slack/lint/retrofit/RetrofitUsageDetectorTest.kt +++ b/slack-lint-checks/src/test/java/slack/lint/retrofit/RetrofitUsageDetectorTest.kt @@ -90,6 +90,8 @@ class RetrofitUsageDetectorTest : BaseSlackLintTest() { import retrofit2.http.Body import retrofit2.http.GET + import retrofit2.http.Multipart + import retrofit2.http.Part import retrofit2.http.POST interface Example { @@ -104,6 +106,22 @@ class RetrofitUsageDetectorTest : BaseSlackLintTest() { @POST("/") fun correct(@Body input: String): String + + @Multipart + @POST("/") + fun multipartCorrect(@Part input: String): String + + @Multipart + @GET("/") + fun multipartBadMethod(@Part input: String): String + + @Multipart + @POST("/") + fun multipartBadParameterType(@Body input: String): String + + @Multipart + @POST("/") + fun multipartMissingPartParameter(): String } """ ) @@ -112,17 +130,26 @@ class RetrofitUsageDetectorTest : BaseSlackLintTest() { .run() .expect( """ - src/test/Example.kt:8: Error: @Body param requires @PUT, @POST, or @PATCH. [RetrofitUsage] - @GET("/") - ~~~~~~~~~ - src/test/Example.kt:11: Error: This annotation requires an @Body parameter. [RetrofitUsage] - @POST("/") - ~~~~~~~~~~ - src/test/Example.kt:15: Error: Duplicate @Body param!. [RetrofitUsage] - fun doubleBody(@Body input: String, @Body input2: String): String - ~~~~~~~~~~~~~~~~~~~~ - 3 errors, 0 warnings - """ + src/test/Example.kt:10: Error: @Body param requires @PUT, @POST, or @PATCH. [RetrofitUsage] + @GET("/") + ~~~~~~~~~ + src/test/Example.kt:13: Error: This annotation requires an @Body parameter. [RetrofitUsage] + @POST("/") + ~~~~~~~~~~ + src/test/Example.kt:17: Error: Duplicate @Body param!. [RetrofitUsage] + fun doubleBody(@Body input: String, @Body input2: String): String + ~~~~~~~~~~~~~~~~~~~~ + src/test/Example.kt:28: Error: @Multipart requires @PUT, @POST, or @PATCH. [RetrofitUsage] + fun multipartBadMethod(@Part input: String): String + ~~~~~~~~~~~~~~~~~~ + src/test/Example.kt:31: Error: @Multipart methods should only contain @Part parameters. [RetrofitUsage] + @POST("/") + ~~~~~~~~~~ + src/test/Example.kt:35: Error: @Multipart methods should contain at least one @Part parameter. [RetrofitUsage] + @POST("/") + ~~~~~~~~~~ + 6 errors, 0 warnings + """ .trimIndent() ) }