Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions api/api.base
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,23 @@ package com.google.devtools.ksp.processing {

public interface KSPLogger {
method public void error(@NonNull String message, @Nullable com.google.devtools.ksp.symbol.KSNode symbol);
method public default void error(@NonNull String message, @Nullable com.google.devtools.ksp.symbol.KSNode symbol, @NonNull com.google.devtools.ksp.processing.KSPSuggestedFix fix);
method public void exception(@NonNull Throwable e);
method public void info(@NonNull String message, @Nullable com.google.devtools.ksp.symbol.KSNode symbol);
method public void logging(@NonNull String message, @Nullable com.google.devtools.ksp.symbol.KSNode symbol);
method public void warn(@NonNull String message, @Nullable com.google.devtools.ksp.symbol.KSNode symbol);
method public default void warn(@NonNull String message, @Nullable com.google.devtools.ksp.symbol.KSNode symbol, @NonNull com.google.devtools.ksp.processing.KSPSuggestedFix fix);
}

public final class KSPSuggestedFix {
ctor public KSPSuggestedFix(@NonNull String replacementText, @Nullable String description);
method @NonNull public String component1();
method @Nullable public String component2();
method @NonNull public com.google.devtools.ksp.processing.KSPSuggestedFix copy(@NonNull String replacementText, @Nullable String description);
method @InaccessibleFromKotlin @Nullable public String getDescription();
method @InaccessibleFromKotlin @NonNull public String getReplacementText();
property @Nullable public String description;
property @NonNull public String replacementText;
}

public interface NativePlatformInfo extends com.google.devtools.ksp.processing.PlatformInfo {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,22 @@ interface KSPLogger {
fun warn(message: String, symbol: KSNode? = null)
fun error(message: String, symbol: KSNode? = null)

fun warn(message: String, symbol: KSNode?, fix: KSPSuggestedFix) {
warn(message, symbol)
}

fun error(message: String, symbol: KSNode?, fix: KSPSuggestedFix) {
error(message, symbol)
}

fun exception(e: Throwable)
}

/**
* Represents a suggested code fix provided by a processor.
* Tools like IntelliJ/Android Studio can use this to provide Quick Fixes.
*/
data class KSPSuggestedFix(
val replacementText: String,
val description: String? = null
)
1 change: 1 addition & 0 deletions common-deps/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ plugins {

dependencies {
compileOnly(project(":api"))
testImplementation(project(":api"))
testImplementation("junit:junit:$junitVersion")

ksp(project(":cmdline-parser-gen"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ class KspGradleLogger(val loglevel: Int) : KSPLogger {
is NonExistLocation, null -> message
}

private fun decorateFix(message: String, symbol: KSNode?, fix: KSPSuggestedFix): String {
val baseMessage = decorateMessage(message, symbol)
val fixDesc = if (fix.description != null) " (${fix.description})" else ""
return "$baseMessage -> Suggested Fix$fixDesc: [${fix.replacementText}]"
}

override fun logging(message: String, symbol: KSNode?) {
if (loglevel <= LOGGING_LEVEL_LOGGING)
messager.println("v: [ksp] ${decorateMessage(message, symbol)}")
Expand All @@ -49,6 +55,16 @@ class KspGradleLogger(val loglevel: Int) : KSPLogger {
messager.println("e: [ksp] ${decorateMessage(message, symbol)}")
}

override fun warn(message: String, symbol: KSNode?, fix: KSPSuggestedFix) {
if (loglevel <= LOGGING_LEVEL_WARN)
messager.println("w: [ksp] ${decorateFix(message, symbol, fix)}")
}

override fun error(message: String, symbol: KSNode?, fix: KSPSuggestedFix) {
if (loglevel <= LOGGING_LEVEL_ERROR)
messager.println("e: [ksp] ${decorateFix(message, symbol, fix)}")
}

override fun exception(e: Throwable) {
if (loglevel <= LOGGING_LEVEL_ERROR)
messager.println("e: [ksp] $e")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright 2026 Google LLC
* Copyright 2010-2026 JetBrains s.r.o. and Kotlin Programming Language contributors.
*
* 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 com.google.devtools.ksp.processing

import org.junit.Assert.assertEquals
import org.junit.Test
import java.io.ByteArrayOutputStream
import java.io.PrintStream

class KspGradleLoggerTest {

@Test
fun testWarnWithFix() {
val out = ByteArrayOutputStream()
val originalOut = System.out
System.setOut(PrintStream(out))
try {
val logger = KspGradleLogger(KspGradleLogger.LOGGING_LEVEL_WARN)
val fix = KSPSuggestedFix("NewAnnotation", "Replace with NewAnnotation")
logger.warn("Deprecated annotation", null, fix)

val output = out.toString().trim()
assertEquals(
"w: [ksp] Deprecated annotation -> Suggested Fix (Replace with NewAnnotation): [NewAnnotation]",
output
)
} finally {
System.setOut(originalOut)
}
}

@Test
fun testErrorWithFix() {
val out = ByteArrayOutputStream()
val originalOut = System.out
System.setOut(PrintStream(out))
try {
val logger = KspGradleLogger(KspGradleLogger.LOGGING_LEVEL_ERROR)
val fix = KSPSuggestedFix("replacement")
logger.error("Something wrong", null, fix)

val output = out.toString().trim()
assertEquals(
"e: [ksp] Something wrong -> Suggested Fix: [replacement]",
output
)
} finally {
System.setOut(originalOut)
}
}

@Test
fun testWarnWithoutFix() {
val out = ByteArrayOutputStream()
val originalOut = System.out
System.setOut(PrintStream(out))
try {
val logger = KspGradleLogger(KspGradleLogger.LOGGING_LEVEL_WARN)
logger.warn("Simple warning", null)

val output = out.toString().trim()
assertEquals("w: [ksp] Simple warning", output)
} finally {
System.setOut(originalOut)
}
}

@Test
fun testErrorWithoutFix() {
val out = ByteArrayOutputStream()
val originalOut = System.out
System.setOut(PrintStream(out))
try {
val logger = KspGradleLogger(KspGradleLogger.LOGGING_LEVEL_ERROR)
logger.error("Simple error", null)

val output = out.toString().trim()
assertEquals("e: [ksp] Simple error", output)
} finally {
System.setOut(originalOut)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package com.google.devtools.ksp.impl

import com.google.devtools.ksp.processing.KSPLogger
import com.google.devtools.ksp.processing.KSPSuggestedFix
import com.google.devtools.ksp.symbol.KSNode

class CommandLineKSPLogger : KSPLogger {
Expand All @@ -39,6 +40,16 @@ class CommandLineKSPLogger : KSPLogger {
messager.println(message)
}

override fun warn(message: String, symbol: KSNode?, fix: KSPSuggestedFix) {
val fixDesc = if (fix.description != null) " (${fix.description})" else ""
messager.println("Warning: $message -> Suggested Fix$fixDesc: [${fix.replacementText}]")
}

override fun error(message: String, symbol: KSNode?, fix: KSPSuggestedFix) {
val fixDesc = if (fix.description != null) " (${fix.description})" else ""
messager.println("Error: $message -> Suggested Fix$fixDesc: [${fix.replacementText}]")
}

override fun exception(e: Throwable) {
messager.println(e.message)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -467,11 +467,22 @@ class KotlinSymbolProcessing(
logger.error(message, symbol)
}

override fun error(message: String, symbol: KSNode?, fix: KSPSuggestedFix) {
hasError = true
logger.error(message, symbol, fix)
}

override fun warn(message: String, symbol: KSNode?) {
if (kspConfig.allWarningsAsErrors)
hasError = true
logger.warn(message, symbol)
}

override fun warn(message: String, symbol: KSNode?, fix: KSPSuggestedFix) {
if (kspConfig.allWarningsAsErrors)
hasError = true
logger.warn(message, symbol, fix)
}
}

val projectDisposable: Disposable = Disposer.newDisposable("StandaloneAnalysisAPISession.project")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright 2026 Google LLC
* Copyright 2010-2026 JetBrains s.r.o. and Kotlin Programming Language contributors.
*
* 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 com.google.devtools.ksp.impl

import com.google.devtools.ksp.processing.KSPSuggestedFix
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.parallel.Execution
import org.junit.jupiter.api.parallel.ExecutionMode
import java.io.ByteArrayOutputStream
import java.io.PrintStream

@Execution(ExecutionMode.SAME_THREAD)
class CommandLineKSPLoggerTest {

@Test
fun testWarnWithFix() {
val err = ByteArrayOutputStream()
val originalErr = System.err
System.setErr(PrintStream(err))
try {
val logger = CommandLineKSPLogger()
val fix = KSPSuggestedFix("NewAnnotation", "Replace with NewAnnotation")
logger.warn("Deprecated annotation", null, fix)

val output = err.toString().trim()
assertEquals(
"Warning: Deprecated annotation -> Suggested Fix (Replace with NewAnnotation): [NewAnnotation]",
output
)
} finally {
System.setErr(originalErr)
}
}

@Test
fun testErrorWithFix() {
val err = ByteArrayOutputStream()
val originalErr = System.err
System.setErr(PrintStream(err))
try {
val logger = CommandLineKSPLogger()
val fix = KSPSuggestedFix("replacement")
logger.error("Something wrong", null, fix)

val output = err.toString().trim()
assertEquals(
"Error: Something wrong -> Suggested Fix: [replacement]",
output
)
} finally {
System.setErr(originalErr)
}
}

@Test
fun testWarnWithoutFix() {
val err = ByteArrayOutputStream()
val originalErr = System.err
System.setErr(PrintStream(err))
try {
val logger = CommandLineKSPLogger()
logger.warn("Simple warning", null)

val output = err.toString().trim()
assertEquals("Simple warning", output)
} finally {
System.setErr(originalErr)
}
}

@Test
fun testErrorWithoutFix() {
val err = ByteArrayOutputStream()
val originalErr = System.err
System.setErr(PrintStream(err))
try {
val logger = CommandLineKSPLogger()
logger.error("Simple error", null)

val output = err.toString().trim()
assertEquals("Simple error", output)
} finally {
System.setErr(originalErr)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -865,6 +865,12 @@ abstract class KSPUnitTestSuite(
runTest("$AA_PATH/exitCode.kt")
}

@TestMetadata("suggestedFix.kt")
@Test
fun testSuggestedFix() {
runTest("$AA_PATH/suggestedFix.kt")
}

@TestMetadata("packageProviderForGenerated.kt")
@Test
fun testPackageProviderForGenerated() {
Expand Down
26 changes: 26 additions & 0 deletions kotlin-analysis-api/testData/suggestedFix.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2026 Google LLC
* Copyright 2010-2026 JetBrains s.r.o. and Kotlin Programming Language contributors.
*
* 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.
*/

// TEST PROCESSOR: SuggestedFixProcessor
// EXPECTED:
// KSP FAILED WITH EXIT CODE: PROCESSING_ERROR
// warn:NewClass:Replace with NewClass
// error:AlternativeClass:null
// END

// FILE: DeprecatedClass.kt
class DeprecatedClass
Loading