diff --git a/build.gradle.kts b/build.gradle.kts index 95774d9..8bda6d6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -27,6 +27,7 @@ dependencies { intellijPlatform { create("IC", "2025.1.4.1") testFramework(org.jetbrains.intellij.platform.gradle.TestFrameworkType.Platform) + testFramework(org.jetbrains.intellij.platform.gradle.TestFrameworkType.Plugin.Java) bundledPlugin("com.intellij.java") diff --git a/gradle.properties b/gradle.properties index cba873f..d4bf375 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,4 +9,4 @@ org.gradle.caching = true org.gradle.jvmargs=-XX\:MaxHeapSize\=4256m -Xmx4256m -Xms2000m -version = 0.0.11 \ No newline at end of file +version = 0.0.12 \ No newline at end of file diff --git a/src/main/kotlin/oap/application/plugin/completion/WsValidateCompletionContributor.kt b/src/main/kotlin/oap/application/plugin/completion/WsValidateCompletionContributor.kt new file mode 100644 index 0000000..541d3df --- /dev/null +++ b/src/main/kotlin/oap/application/plugin/completion/WsValidateCompletionContributor.kt @@ -0,0 +1,42 @@ +package oap.application.plugin.completion + +import com.intellij.codeInsight.completion.CompletionContributor +import com.intellij.codeInsight.completion.CompletionParameters +import com.intellij.codeInsight.completion.CompletionProvider +import com.intellij.codeInsight.completion.CompletionResultSet +import com.intellij.codeInsight.completion.CompletionType +import com.intellij.codeInsight.lookup.LookupElementBuilder +import com.intellij.icons.AllIcons +import com.intellij.patterns.PlatformPatterns +import com.intellij.psi.PsiLiteralExpression +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.util.ProcessingContext +import oap.application.plugin.ref.WsValidateUtil + +class WsValidateCompletionContributor : CompletionContributor() { + init { + extend( + CompletionType.BASIC, + PlatformPatterns.psiElement(), + object : CompletionProvider() { + override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet) { + // Whether the completion position IS the literal, or a child of it (this + // differs between a freshly-parsed in-memory file and a stub-backed real + // project file), is an implementation detail we shouldn't depend on - look + // for a PsiLiteralExpression starting at the position itself (strict=false). + val literal = PsiTreeUtil.getParentOfType(parameters.position, PsiLiteralExpression::class.java, false) ?: return + val annotation = WsValidateUtil.enclosingAnnotation(literal) ?: return + val owner = WsValidateUtil.annotatedOwner(annotation) ?: return + + for (method in WsValidateUtil.candidateValidatorMethods(owner)) { + result.addElement( + LookupElementBuilder.create(method.name) + .withIcon(AllIcons.Nodes.Method) + .withTypeText(WsValidateUtil.VALIDATION_ERRORS_FQN) + ) + } + } + } + ) + } +} diff --git a/src/main/kotlin/oap/application/plugin/ref/ValidWsValidateInspection.kt b/src/main/kotlin/oap/application/plugin/ref/ValidWsValidateInspection.kt new file mode 100644 index 0000000..bc1ed74 --- /dev/null +++ b/src/main/kotlin/oap/application/plugin/ref/ValidWsValidateInspection.kt @@ -0,0 +1,64 @@ +package oap.application.plugin.ref + +import com.intellij.codeInspection.LocalInspectionTool +import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiElementVisitor +import com.intellij.psi.PsiLiteralExpression +import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiParameter + +class ValidWsValidateInspection : LocalInspectionTool() { + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { + return object : PsiElementVisitor() { + override fun visitElement(element: PsiElement) { + if (element !is PsiLiteralExpression || !WsValidateUtil.isWsValidateLiteral(element)) { + super.visitElement(element) + return + } + + val resolved = WsValidateUtil.resolve(element) ?: return + val validator = resolved.validator + + when (val owner = resolved.owner) { + is PsiMethod -> checkMethodLevel(owner, validator, element, holder) + is PsiParameter -> checkParameterLevel(validator, element, holder) + } + + checkReturnType(validator, element, holder) + } + } + } + + private fun checkMethodLevel(annotatedMethod: PsiMethod, validator: PsiMethod, element: PsiLiteralExpression, holder: ProblemsHolder) { + val missing = WsValidateUtil.missingParameterNames(annotatedMethod, validator) + if (missing.isNotEmpty()) { + holder.registerProblem( + element, + "Validator '${validator.name}' parameter(s) ${missing.joinToString(", ")} not supplied by '${annotatedMethod.name}'", + ProblemHighlightType.ERROR + ) + } + } + + private fun checkParameterLevel(validator: PsiMethod, element: PsiLiteralExpression, holder: ProblemsHolder) { + if (validator.parameterList.parametersCount != 1) { + holder.registerProblem( + element, + "Validator '${validator.name}' must take exactly one parameter for a parameter-level @WsValidate", + ProblemHighlightType.ERROR + ) + } + } + + private fun checkReturnType(validator: PsiMethod, element: PsiLiteralExpression, holder: ProblemsHolder) { + if (!WsValidateUtil.returnsValidationErrors(validator)) { + holder.registerProblem( + element, + "Validator '${validator.name}' must return ${WsValidateUtil.VALIDATION_ERRORS_FQN}", + ProblemHighlightType.ERROR + ) + } + } +} diff --git a/src/main/kotlin/oap/application/plugin/ref/WsValidateReferenceContributor.kt b/src/main/kotlin/oap/application/plugin/ref/WsValidateReferenceContributor.kt new file mode 100644 index 0000000..3031944 --- /dev/null +++ b/src/main/kotlin/oap/application/plugin/ref/WsValidateReferenceContributor.kt @@ -0,0 +1,34 @@ +package oap.application.plugin.ref + +import com.intellij.patterns.PlatformPatterns +import com.intellij.psi.ElementManipulators +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiLiteralExpression +import com.intellij.psi.PsiReference +import com.intellij.psi.PsiReferenceBase +import com.intellij.psi.PsiReferenceContributor +import com.intellij.psi.PsiReferenceProvider +import com.intellij.psi.PsiReferenceRegistrar +import com.intellij.util.ProcessingContext + +class WsValidateReferenceContributor : PsiReferenceContributor() { + override fun registerReferenceProviders(registrar: PsiReferenceRegistrar) { + registrar.registerReferenceProvider( + PlatformPatterns.psiElement(PsiLiteralExpression::class.java), + object : PsiReferenceProvider() { + override fun getReferencesByElement(element: PsiElement, context: ProcessingContext): Array { + if (element !is PsiLiteralExpression || !WsValidateUtil.isWsValidateLiteral(element)) { + return PsiReference.EMPTY_ARRAY + } + return arrayOf(WsValidateMethodReference(element)) + } + } + ) + } +} + +class WsValidateMethodReference(literal: PsiLiteralExpression) : PsiReferenceBase(literal, ElementManipulators.getValueTextRange(literal)) { + override fun resolve(): PsiElement? { + return WsValidateUtil.findValidatorMethod(element) + } +} diff --git a/src/main/kotlin/oap/application/plugin/ref/WsValidateUtil.kt b/src/main/kotlin/oap/application/plugin/ref/WsValidateUtil.kt new file mode 100644 index 0000000..e0ca913 --- /dev/null +++ b/src/main/kotlin/oap/application/plugin/ref/WsValidateUtil.kt @@ -0,0 +1,89 @@ +package oap.application.plugin.ref + +import com.intellij.psi.PsiAnnotation +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiLiteralExpression +import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiModifierListOwner +import com.intellij.psi.PsiParameter +import com.intellij.psi.util.PsiTreeUtil + +// Shared lookup logic for the oap.ws.validate.WsValidate annotation contract (see CLAUDE.md, +// "oap-ws Module" section) - a validator method name is always resolved by simple name on the +// SAME class as the annotated element, whether @WsValidate sits on a method or on a parameter. +object WsValidateUtil { + const val ANNOTATION_FQN = "oap.ws.validate.WsValidate" + const val VALIDATION_ERRORS_FQN = "oap.ws.validate.ValidationErrors" + + fun enclosingAnnotation(literal: PsiLiteralExpression): PsiAnnotation? { + val annotation = PsiTreeUtil.getParentOfType(literal, PsiAnnotation::class.java) ?: return null + return if (annotation.qualifiedName == ANNOTATION_FQN) annotation else null + } + + fun annotatedOwner(annotation: PsiAnnotation): PsiModifierListOwner? { + return PsiTreeUtil.getParentOfType(annotation, PsiModifierListOwner::class.java) + } + + fun containingClass(owner: PsiModifierListOwner): PsiClass? { + return when (owner) { + is PsiParameter -> (owner.declarationScope as? PsiMethod)?.containingClass + is PsiMethod -> owner.containingClass + else -> null + } + } + + fun findValidatorMethod(literal: PsiLiteralExpression): PsiMethod? { + return resolve(literal)?.validator + } + + class Resolved(val owner: PsiModifierListOwner, val validator: PsiMethod) + + fun resolve(literal: PsiLiteralExpression): Resolved? { + val name = literal.value as? String ?: return null + val annotation = enclosingAnnotation(literal) ?: return null + val owner = annotatedOwner(annotation) ?: return null + val psiClass = containingClass(owner) ?: return null + val validator = psiClass.findMethodsByName(name, true).firstOrNull() ?: return null + return Resolved(owner, validator) + } + + fun isWsValidateLiteral(element: PsiElement): Boolean { + return element is PsiLiteralExpression && element.value is String && enclosingAnnotation(element) != null + } + + // Names required by `candidate` that `owner`'s own parameters don't supply - empty means the + // candidate's parameter list is fully satisfiable (see ValidWsValidateInspection.checkMethodLevel + // for the runtime contract this mirrors: unmatched names throw IllegalArgumentException at + // request time in oap-ws's own MethodValidatorPeer). + fun missingParameterNames(owner: PsiMethod, candidate: PsiMethod): List { + val ownerParamNames = owner.parameterList.parameters.map { it.name }.toSet() + return candidate.parameterList.parameters.map { it.name }.filter { it !in ownerParamNames } + } + + fun returnsValidationErrors(method: PsiMethod): Boolean { + return method.returnType?.canonicalText == VALIDATION_ERRORS_FQN + } + + fun isShapeCompatible(owner: PsiModifierListOwner, candidate: PsiMethod): Boolean { + return when (owner) { + is PsiMethod -> missingParameterNames(owner, candidate).isEmpty() + is PsiParameter -> candidate.parameterList.parametersCount == 1 + else -> false + } + } + + // Only methods that would NOT be flagged by ValidWsValidateInspection - completion should + // never suggest something the inspection then reports as wrong. + // + // Uses allMethods (declared + inherited), not methods (declared only) - resolve() above finds + // a validator via findMethodsByName(name, checkBases=true), which searches superclasses too + // (WS classes commonly extend a shared base class holding common validators), so candidate + // enumeration must search the same scope or it silently misses every inherited validator. + fun candidateValidatorMethods(owner: PsiModifierListOwner): List { + val psiClass = containingClass(owner) ?: return emptyList() + return psiClass.allMethods + .distinctBy { it.name } + .filter { returnsValidationErrors(it) && isShapeCompatible(owner, it) } + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 80a05c8..6dfabb0 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -47,10 +47,14 @@ + + @@ -97,6 +101,11 @@ displayName="Include validation" groupName="OAP" enabledByDefault="true" level="ERROR"/> + + diff --git a/src/test/java/oap/application/plugin/TestWsValidateService.java b/src/test/java/oap/application/plugin/TestWsValidateService.java new file mode 100644 index 0000000..f35d765 --- /dev/null +++ b/src/test/java/oap/application/plugin/TestWsValidateService.java @@ -0,0 +1,28 @@ +package oap.application.plugin; + +import oap.ws.validate.ValidationErrors; +import oap.ws.validate.WsValidate; + +public class TestWsValidateService { + @WsValidate("isValid") + public String methodLevelValid(String skipDeprecated) { + return skipDeprecated; + } + + public ValidationErrors isValid(String skipDeprecated) { + return ValidationErrors.empty(); + } + + public String parameterLevelValid(@WsValidate("oddParamValidator") int oddParam) { + return String.valueOf(oddParam); + } + + public ValidationErrors oddParamValidator(int oddParam) { + return ValidationErrors.empty(); + } + + @WsValidate("noSuchValidator") + public String methodLevelUnresolved(String requiredParameter) { + return requiredParameter; + } +} diff --git a/src/test/java/oap/ws/validate/ValidationErrors.java b/src/test/java/oap/ws/validate/ValidationErrors.java new file mode 100644 index 0000000..7e54252 --- /dev/null +++ b/src/test/java/oap/ws/validate/ValidationErrors.java @@ -0,0 +1,7 @@ +package oap.ws.validate; + +public class ValidationErrors { + public static ValidationErrors empty() { + return new ValidationErrors(); + } +} diff --git a/src/test/java/oap/ws/validate/WsValidate.java b/src/test/java/oap/ws/validate/WsValidate.java new file mode 100644 index 0000000..ef8bafd --- /dev/null +++ b/src/test/java/oap/ws/validate/WsValidate.java @@ -0,0 +1,12 @@ +package oap.ws.validate; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER, ElementType.METHOD}) +public @interface WsValidate { + String[] value(); +} diff --git a/src/test/kotlin/oap/application/plugin/completion/WsValidateCompletionTest.kt b/src/test/kotlin/oap/application/plugin/completion/WsValidateCompletionTest.kt new file mode 100644 index 0000000..daa3051 --- /dev/null +++ b/src/test/kotlin/oap/application/plugin/completion/WsValidateCompletionTest.kt @@ -0,0 +1,173 @@ +package oap.application.plugin.completion + +import com.intellij.codeInsight.completion.CompletionType +import oap.application.plugin.OapFixtureTestCase +import org.assertj.core.api.Assertions.assertThat +import java.io.File + +class WsValidateCompletionTest : OapFixtureTestCase() { + override fun getTestDataPath(): String { + return File("src/test/java").absolutePath + } + + protected override fun setUp() { + super.setUp() + myFixture.configureByFile("oap/ws/validate/WsValidate.java") + myFixture.configureByFile("oap/ws/validate/ValidationErrors.java") + } + + // Primitive-only signatures - this light test fixture has no JDK attached (see + // WsValidateInspectionTest), so a real JDK type would be unresolvable noise here. + fun testMethodLevelCompletion() { + val file = myFixture.configureByText( + "MethodLevelCompletion.java", """ + import oap.ws.validate.ValidationErrors; + import oap.ws.validate.WsValidate; + + public class MethodLevelCompletion { + @WsValidate("") + public int call(int requiredParameter) { + return requiredParameter; + } + + public ValidationErrors goodValidator(int requiredParameter) { + return ValidationErrors.empty(); + } + + public ValidationErrors badValidatorWrongParam(int notAParam) { + return ValidationErrors.empty(); + } + + public boolean badValidatorWrongReturnType(int requiredParameter) { + return true; + } + } + """.trimIndent() + ) + myFixture.openFileInEditor(file.virtualFile) + myFixture.complete(CompletionType.BASIC) + + val suggestions = myFixture.lookupElementStrings!! + assertThat(suggestions).contains("goodValidator") + assertThat(suggestions).doesNotContain("badValidatorWrongParam", "badValidatorWrongReturnType") + } + + fun testMethodLevelArrayFormCompletion() { + val file = myFixture.configureByText( + "MethodLevelArrayFormCompletion.java", """ + import oap.ws.validate.ValidationErrors; + import oap.ws.validate.WsValidate; + + public class MethodLevelArrayFormCompletion { + @WsValidate({ "" }) + public int call(int requiredParameter) { + return requiredParameter; + } + + public ValidationErrors goodValidator(int requiredParameter) { + return ValidationErrors.empty(); + } + + public ValidationErrors badValidatorWrongParam(int notAParam) { + return ValidationErrors.empty(); + } + } + """.trimIndent() + ) + myFixture.openFileInEditor(file.virtualFile) + myFixture.complete(CompletionType.BASIC) + + val suggestions = myFixture.lookupElementStrings!! + assertThat(suggestions).contains("goodValidator") + assertThat(suggestions).doesNotContain("badValidatorWrongParam") + } + + fun testMethodLevelArrayFormSecondElementCompletion() { + val file = myFixture.configureByText( + "MethodLevelArrayFormSecondElement.java", """ + import oap.ws.validate.ValidationErrors; + import oap.ws.validate.WsValidate; + + public class MethodLevelArrayFormSecondElement { + @WsValidate({ "goodValidator", "" }) + public int call(int requiredParameter) { + return requiredParameter; + } + + public ValidationErrors goodValidator(int requiredParameter) { + return ValidationErrors.empty(); + } + + public ValidationErrors anotherGoodValidator(int requiredParameter) { + return ValidationErrors.empty(); + } + } + """.trimIndent() + ) + myFixture.openFileInEditor(file.virtualFile) + myFixture.complete(CompletionType.BASIC) + + val suggestions = myFixture.lookupElementStrings!! + assertThat(suggestions).contains("goodValidator", "anotherGoodValidator") + } + + // Regression test: real-world WS classes commonly extend a shared base class holding common + // validators. candidateValidatorMethods() must search the whole hierarchy (like resolve()'s + // findMethodsByName(name, checkBases=true) already does for navigation), not just this + // class's own declared methods, or an inherited validator silently never gets suggested. + fun testInheritedValidatorCompletion() { + val file = myFixture.configureByText( + "InheritedValidatorCompletion.java", """ + import oap.ws.validate.ValidationErrors; + import oap.ws.validate.WsValidate; + + class BaseWs { + public ValidationErrors inheritedValidator(int requiredParameter) { + return ValidationErrors.empty(); + } + } + + public class InheritedValidatorCompletion extends BaseWs { + @WsValidate("") + public int call(int requiredParameter) { + return requiredParameter; + } + } + """.trimIndent() + ) + myFixture.openFileInEditor(file.virtualFile) + myFixture.complete(CompletionType.BASIC) + + val suggestions = myFixture.lookupElementStrings!! + assertThat(suggestions).contains("inheritedValidator") + } + + fun testParameterLevelCompletion() { + val file = myFixture.configureByText( + "ParameterLevelCompletion.java", """ + import oap.ws.validate.ValidationErrors; + import oap.ws.validate.WsValidate; + + public class ParameterLevelCompletion { + public int call(@WsValidate("") int oddParam, int other) { + return oddParam; + } + + public ValidationErrors goodValidator(int oddParam) { + return ValidationErrors.empty(); + } + + public ValidationErrors badValidatorTwoParams(int oddParam, int other) { + return ValidationErrors.empty(); + } + } + """.trimIndent() + ) + myFixture.openFileInEditor(file.virtualFile) + myFixture.complete(CompletionType.BASIC) + + val suggestions = myFixture.lookupElementStrings!! + assertThat(suggestions).contains("goodValidator") + assertThat(suggestions).doesNotContain("badValidatorTwoParams") + } +} diff --git a/src/test/kotlin/oap/application/plugin/ref/WsValidateGotoTest.kt b/src/test/kotlin/oap/application/plugin/ref/WsValidateGotoTest.kt new file mode 100644 index 0000000..1aefdf5 --- /dev/null +++ b/src/test/kotlin/oap/application/plugin/ref/WsValidateGotoTest.kt @@ -0,0 +1,62 @@ +package oap.application.plugin.ref + +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiAnnotation +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiLiteralExpression +import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiModifierListOwner +import com.intellij.psi.search.GlobalSearchScope +import oap.application.plugin.OapFixtureTestCase +import org.assertj.core.api.Assertions.assertThat +import java.io.File + +class WsValidateGotoTest : OapFixtureTestCase() { + override fun getTestDataPath(): String { + return File("src/test/java").absolutePath + } + + private fun loadTestClass(): PsiClass { + myFixture.configureByFile("oap/ws/validate/WsValidate.java") + myFixture.configureByFile("oap/ws/validate/ValidationErrors.java") + myFixture.configureByFile("oap/application/plugin/TestWsValidateService.java") + val psiClass = JavaPsiFacade.getInstance(project) + .findClass("oap.application.plugin.TestWsValidateService", GlobalSearchScope.allScope(project)) + return psiClass!! + } + + private fun wsValidateLiteral(annotationOwner: PsiModifierListOwner): PsiLiteralExpression { + val annotation: PsiAnnotation = annotationOwner.getAnnotation(WsValidateUtil.ANNOTATION_FQN)!! + return annotation.parameterList.attributes[0].value as PsiLiteralExpression + } + + fun testGotoMethodLevelValidator() { + val psiClass = loadTestClass() + val method = psiClass.findMethodsByName("methodLevelValid", false)[0] + val literal = wsValidateLiteral(method) + + val resolved = literal.references.firstOrNull()?.resolve() + assertThat(resolved).isInstanceOf(PsiMethod::class.java) + assertThat((resolved as PsiMethod).name).isEqualTo("isValid") + } + + fun testGotoParameterLevelValidator() { + val psiClass = loadTestClass() + val method = psiClass.findMethodsByName("parameterLevelValid", false)[0] + val parameter = method.parameterList.parameters[0] + val literal = wsValidateLiteral(parameter) + + val resolved = literal.references.firstOrNull()?.resolve() + assertThat(resolved).isInstanceOf(PsiMethod::class.java) + assertThat((resolved as PsiMethod).name).isEqualTo("oddParamValidator") + } + + fun testGotoUnresolvedValidator() { + val psiClass = loadTestClass() + val method = psiClass.findMethodsByName("methodLevelUnresolved", false)[0] + val literal = wsValidateLiteral(method) + + val resolved = literal.references.firstOrNull()?.resolve() + assertThat(resolved).isNull() + } +} diff --git a/src/test/kotlin/oap/application/plugin/ref/WsValidateInspectionTest.kt b/src/test/kotlin/oap/application/plugin/ref/WsValidateInspectionTest.kt new file mode 100644 index 0000000..eac120d --- /dev/null +++ b/src/test/kotlin/oap/application/plugin/ref/WsValidateInspectionTest.kt @@ -0,0 +1,124 @@ +package oap.application.plugin.ref + +import com.intellij.psi.PsiFile +import oap.application.plugin.OapFixtureTestCase +import java.io.File + +class WsValidateInspectionTest : OapFixtureTestCase() { + override fun getTestDataPath(): String { + return File("src/test/java").absolutePath + } + + protected override fun setUp() { + super.setUp() + myFixture.enableInspections(ValidWsValidateInspection::class.java) + myFixture.configureByFile("oap/ws/validate/WsValidate.java") + myFixture.configureByFile("oap/ws/validate/ValidationErrors.java") + } + + // Deliberately primitive-only signatures below (no java.lang.String etc.) - this light test + // fixture has no JDK attached, so any real JDK type would show up as a spurious "Cannot + // resolve symbol" error unrelated to what these tests are actually checking. + private fun check(fileName: String, text: String) { + val file: PsiFile = myFixture.configureByText(fileName, text.trimIndent()) + myFixture.openFileInEditor(file.virtualFile) + myFixture.checkHighlighting() + } + + fun testValidMethodLevel() { + check( + "MethodLevelValid.java", """ + import oap.ws.validate.ValidationErrors; + import oap.ws.validate.WsValidate; + + public class MethodLevelValid { + @WsValidate("isValid") + public int call(int skipDeprecated) { + return skipDeprecated; + } + + public ValidationErrors isValid(int skipDeprecated) { + return ValidationErrors.empty(); + } + } + """ + ) + } + + fun testValidParameterLevel() { + check( + "ParameterLevelValid.java", """ + import oap.ws.validate.ValidationErrors; + import oap.ws.validate.WsValidate; + + public class ParameterLevelValid { + public int call(@WsValidate("oddParamValidator") int oddParam) { + return oddParam; + } + + public ValidationErrors oddParamValidator(int oddParam) { + return ValidationErrors.empty(); + } + } + """ + ) + } + + fun testMismatchedParameterName() { + check( + "MismatchedParamName.java", """ + import oap.ws.validate.ValidationErrors; + import oap.ws.validate.WsValidate; + + public class MismatchedParamName { + @WsValidate("validator") + public int call(int requiredParameter) { + return requiredParameter; + } + + public ValidationErrors validator(int missedParam) { + return ValidationErrors.empty(); + } + } + """ + ) + } + + fun testWrongArgCountOnParameterLevel() { + check( + "WrongArgCount.java", """ + import oap.ws.validate.ValidationErrors; + import oap.ws.validate.WsValidate; + + public class WrongArgCount { + public int call(@WsValidate("validator") int oddParam, int other) { + return oddParam; + } + + public ValidationErrors validator(int oddParam, int other) { + return ValidationErrors.empty(); + } + } + """ + ) + } + + fun testWrongReturnType() { + check( + "WrongReturnType.java", """ + import oap.ws.validate.WsValidate; + + public class WrongReturnType { + @WsValidate("validator") + public int call(int requiredParameter) { + return requiredParameter; + } + + public boolean validator(int requiredParameter) { + return true; + } + } + """ + ) + } +}