diff --git a/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/detect/JVMLoggerDetector.kt b/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/detect/JVMLoggerDetector.kt index cd4cc7e51..87080bd35 100644 --- a/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/detect/JVMLoggerDetector.kt +++ b/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/detect/JVMLoggerDetector.kt @@ -22,8 +22,12 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.util.Computable import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiElement +import com.intellij.psi.PsiRecursiveElementVisitor import com.intellij.refactoring.suggested.endOffset import com.intellij.refactoring.suggested.startOffset +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression +import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod import org.jetbrains.uast.UCallExpression import org.jetbrains.uast.ULiteralExpression import org.jetbrains.uast.UMethod @@ -58,11 +62,15 @@ class JVMLoggerDetector(val project: Project) : LoggerDetector { } override fun determineLoggerStatements(guideMark: MethodGuideMark): List { - val uMethod = ApplicationManager.getApplication().runReadAction(Computable { - guideMark.getPsiMethod().toUElementOfType() - }) - if (uMethod != null) { - determineLoggerStatements(uMethod, guideMark.sourceFileMarker) + if (guideMark.language.id == "Groovy") { + determineLoggerStatements(guideMark.getPsiMethod() as GrMethod, guideMark.sourceFileMarker) + } else { + val uMethod = ApplicationManager.getApplication().runReadAction(Computable { + guideMark.getPsiMethod().toUElementOfType() + }) + if (uMethod != null) { + determineLoggerStatements(uMethod, guideMark.sourceFileMarker) + } } return guideMark.getChildren().mapNotNull { it.getUserData(DETECTED_LOGGER) } } @@ -111,6 +119,54 @@ class JVMLoggerDetector(val project: Project) : LoggerDetector { return loggerStatements } + /** + * Unsure why, but Groovy UAST visitors don't work here. Have to use Groovy PSI. + */ + fun determineLoggerStatements(grMethod: GrMethod, fileMarker: SourceFileMarker): List { + val loggerStatements = mutableListOf() + ApplicationManager.getApplication().runReadAction { + grMethod.acceptChildren(object : PsiRecursiveElementVisitor() { + override fun visitElement(element: PsiElement) { + if (element is GrMethodCallExpression) { + val loggerClass = element.resolveMethod()?.containingClass?.qualifiedName + if (loggerClass != null && LOGGER_CLASSES.contains(loggerClass)) { + val methodName = element.resolveMethod()?.name + if (methodName != null && LOGGER_METHODS.contains(methodName)) { + val logTemplate = element.argumentList.expressionArguments.firstOrNull()?.run { + (this as? GrLiteral)?.value as? String + } + + if (logTemplate != null) { + log.debug("Found log statement: $logTemplate") + val detectedLogger = DetectedLogger( + logTemplate, methodName, getLineNumber(element) + 1 + ) + loggerStatements.add(detectedLogger) + + //create expression guide mark for the log statement + val guideMark = fileMarker.createExpressionSourceMark( + element, SourceMark.Type.GUIDE + ) + if (!fileMarker.containsSourceMark(guideMark)) { + guideMark.putUserData(DETECTED_LOGGER, detectedLogger) + guideMark.apply(true) + } else { + fileMarker.getSourceMark(guideMark.artifactQualifiedName, SourceMark.Type.GUIDE) + ?.putUserData(DETECTED_LOGGER, detectedLogger) + } + } else { + log.warn("No log template argument available for expression: $element") + } + } + } + } + super.visitElement(element) + } + }) + } + return loggerStatements + } + private fun getLineNumber(element: PsiElement, start: Boolean = true): Int { val document = element.containingFile.viewProvider.document ?: PsiDocumentManager.getInstance(element.project).getDocument(element.containingFile) diff --git a/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/service/JVMArtifactCreationService.kt b/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/service/JVMArtifactCreationService.kt index 7015da9c6..f7df6aa52 100644 --- a/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/service/JVMArtifactCreationService.kt +++ b/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/service/JVMArtifactCreationService.kt @@ -19,6 +19,9 @@ package spp.jetbrains.marker.jvm.service import com.intellij.psi.PsiElement import com.intellij.psi.PsiNameIdentifierOwner import com.intellij.psi.PsiStatement +import com.intellij.psi.impl.source.tree.LeafPsiElement +import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatement +import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.packaging.GrPackageDefinition import spp.jetbrains.marker.IArtifactCreationService import spp.jetbrains.marker.SourceMarkerUtils import spp.jetbrains.marker.jvm.service.utils.JVMMarkerUtils @@ -102,6 +105,12 @@ class JVMArtifactCreationService : IArtifactCreationService { autoApply: Boolean ): Optional { val element = SourceMarkerUtils.getElementAtLine(fileMarker.psiFile, lineNumber) + if (element is LeafPsiElement && element.parent is GrImportStatement) { + return Optional.empty() + } else if (element is LeafPsiElement && element.parent is GrPackageDefinition) { + return Optional.empty() + } + return if (element is PsiStatement) { Optional.ofNullable(JVMMarkerUtils.getOrCreateExpressionInlayMark(fileMarker, element, autoApply)) } else if (element is PsiElement) { diff --git a/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/service/utils/JVMMarkerUtils.kt b/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/service/utils/JVMMarkerUtils.kt index da6e66e88..e04aa3139 100755 --- a/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/service/utils/JVMMarkerUtils.kt +++ b/marker/jvm-marker/src/main/kotlin/spp/jetbrains/marker/jvm/service/utils/JVMMarkerUtils.kt @@ -133,7 +133,7 @@ object JVMMarkerUtils { } return if (inlayMark == null) { - if (element.text != "}") { + if (element.language.id != "Groovy" && element.text != "}") { val uExpression = element.toUElement() if (uExpression !is UExpression && uExpression !is UDeclaration) return null } @@ -404,7 +404,7 @@ object JVMMarkerUtils { } } if (parentIdentifier == null) { - error("Could not determine parent of element: $element") //todo: extension function, see SourceMarkerConfig, make test + error("Could not determine parent of element: $element") //todo: extension function, see SourceMarkerConfig, make test, groovy import statements } element.textRange.startOffset.let { diff --git a/marker/jvm-marker/src/test/kotlin/spp/jetbrains/marker/jvm/detect/JVMLoggerDetectorTest.kt b/marker/jvm-marker/src/test/kotlin/spp/jetbrains/marker/jvm/detect/JVMLoggerDetectorTest.kt index 7e97ed631..d9c1ca7b3 100644 --- a/marker/jvm-marker/src/test/kotlin/spp/jetbrains/marker/jvm/detect/JVMLoggerDetectorTest.kt +++ b/marker/jvm-marker/src/test/kotlin/spp/jetbrains/marker/jvm/detect/JVMLoggerDetectorTest.kt @@ -33,6 +33,8 @@ import io.vertx.core.Vertx import org.intellij.lang.annotations.Language import org.jetbrains.kotlin.idea.core.util.toPsiFile import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.plugins.groovy.lang.psi.GroovyFile +import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod import org.jetbrains.uast.UFile import org.jetbrains.uast.toUElement import spp.jetbrains.UserData @@ -192,4 +194,40 @@ class JVMLoggerDetectorTest : LightJavaCodeInsightFixtureTestCase() { assertContainsOrdered(result, "trace {}", "debug {}", "info {}", "warn {}", "error {}") } } + + fun testGroovyLogbackLogger() { + @Language("Groovy") val code = """ + import ch.qos.logback.classic.Logger + class TestLogback { + var log = new Logger() + void loggers() { + log.trace("trace {}", "trace") + log.debug("debug {}", "debug") + log.info("info {}", "info") + log.warn("warn {}", "warn") + log.error("error {}", "error") + } + } + """.trimIndent() + + ApplicationManager.getApplication().runReadAction { + val sourceFile = myFixture.createFile("TestLogback.groovy", code).toPsiFile(project) + assertNotNull(sourceFile) + + val uFile = sourceFile.toUElement() as UFile + assertEquals(1, uFile.classes.size) + assertEquals(1, uFile.classes[0].methods.size) + + JVMMarker.setup() + SourceFileMarker.SUPPORTED_FILE_TYPES.add(GroovyFile::class.java) + val fileMarker = SourceMarker.getSourceFileMarker(sourceFile!!) + assertNotNull(fileMarker) + + val result = JVMLoggerDetector(project.apply { UserData.vertx(this, Vertx.vertx()) }) + .determineLoggerStatements(uFile.classes[0].methods[0].sourcePsi as GrMethod, fileMarker!!) + .map { it.logPattern } + assertEquals(5, result.size) + assertContainsOrdered(result, "trace {}", "debug {}", "info {}", "warn {}", "error {}") + } + } } diff --git a/marker/src/main/kotlin/spp/jetbrains/marker/SourceMarkerUtils.kt b/marker/src/main/kotlin/spp/jetbrains/marker/SourceMarkerUtils.kt index 2429d80a0..65ea7a31d 100644 --- a/marker/src/main/kotlin/spp/jetbrains/marker/SourceMarkerUtils.kt +++ b/marker/src/main/kotlin/spp/jetbrains/marker/SourceMarkerUtils.kt @@ -18,6 +18,9 @@ package spp.jetbrains.marker import com.intellij.openapi.editor.Document import com.intellij.psi.* +import com.intellij.psi.impl.source.tree.LeafPsiElement +import com.intellij.psi.javadoc.PsiDocComment +import com.intellij.psi.javadoc.PsiDocToken import com.intellij.psi.util.parentOfType import spp.jetbrains.marker.source.mark.api.SourceMark @@ -35,7 +38,7 @@ object SourceMarkerUtils { * @since 0.1.0 */ @JvmStatic - fun getElementAtLine(file: PsiFile, line: Int): PsiElement? { + fun getElementAtLine(file: PsiFile, line: Int, ignoreComments: Boolean = true): PsiElement? { val document: Document = PsiDocumentManager.getInstance(file.project).getDocument(file)!! if (document.lineCount == line - 1) { return null @@ -67,11 +70,22 @@ object SourceMarkerUtils { if (element != null && getLineNumber(element) != line) { return null + } else if (element != null && ignoreComments && isComment(element)) { + return null } return element } + private fun isComment(element: PsiElement): Boolean { + val comment = element is PsiDocToken || element is PsiComment || element is PsiDocComment + if (comment) return true + + return if (element is LeafPsiElement) { + isComment(element.parent) + } else false + } + /** * todo: description. *