KspMessager.kt

/*
 * Copyright 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.room.compiler.processing.ksp

import androidx.room.compiler.processing.XAnnotation
import androidx.room.compiler.processing.XAnnotationValue
import androidx.room.compiler.processing.XElement
import androidx.room.compiler.processing.XMessager
import com.google.devtools.ksp.processing.KSPLogger
import com.google.devtools.ksp.symbol.KSNode
import com.google.devtools.ksp.symbol.NonExistLocation
import javax.tools.Diagnostic

internal class KspMessager(
    private val logger: KSPLogger
) : XMessager() {
    override fun onPrintMessage(
        kind: Diagnostic.Kind,
        msg: String,
        element: XElement?,
        annotation: XAnnotation?,
        annotationValue: XAnnotationValue?
    ) {
        if (element == null) {
            internalPrintMessage(kind, msg)
            return
        }

        // In Javac, the Messager requires all preceding parameters to report an error.
        // In KSP, the KspLogger only needs the last so ignore the preceding parameters.
        val nodes = sequence {
            yield((annotationValue as? KspAnnotationValue)?.valueArgument)
            yield((annotation as? KspAnnotation)?.ksAnnotated)
            yield((element as? KspElement)?.declaration)
        }.filterNotNull()
        val ksNode = nodes.firstOrNull {
            // pick first node with a location, if possible
            it.location != NonExistLocation
        } ?: nodes.firstOrNull() // fallback to the first non-null argument

        // TODO: 10/8/21 Consider checking if the KspAnnotationValue is for an item in a value's
        //  list and adding that information to the error message if so (currently, we have to
        //  report an error on the list itself, so the information about the particular item is
        //  lost). https://github.com/google/ksp/issues/656
        if (ksNode == null || ksNode.location == NonExistLocation) {
            internalPrintMessage(kind, "$msg - ${element.fallbackLocationText}", ksNode)
        } else {
            internalPrintMessage(kind, msg, ksNode)
        }
    }

    private fun internalPrintMessage(kind: Diagnostic.Kind, msg: String, ksNode: KSNode? = null) {
        when (kind) {
            Diagnostic.Kind.ERROR -> logger.error(msg, ksNode)
            Diagnostic.Kind.WARNING -> logger.warn(msg, ksNode)
            else -> logger.info(msg, ksNode)
        }
    }
}