Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multiple custom annotations #279

Merged
merged 7 commits into from
Dec 21, 2024
Merged
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ Changelog

- Fix FIR diagnostics rendering in the IDE. Note this only works in the K2 Kotlin IDE plugin + setting the IntelliJ `kotlin.k2.only.bundled.compiler.plugins.enabled` registry key to `false`.
- When custom annotations are defined, report those names in FIR error messages.
- Support multiple custom annotations.
- For Gradle configuration, the singular `*Annotation` properties are deprecated in favor of plural`*Annotations` `SetProperty` types.
- For CLI consumers, the `redactedAnnotation` and `unredactedAnnotation` properties are now `redactedAnnotations` and `unredactedAnnotations`.
- Build against Gradle `8.12`.
- Only report errors in FIR now. Removes the `validateIr` plugin option.
- No longer support K1.
Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,17 @@ You can configure custom behavior with properties on the `redacted` extension.

```kotlin
redacted {
// Define a custom annotation. The -annotations artifact won't be automatically added to
// Define custom annotations. The -annotations artifact won't be automatically added to
// dependencies if you define your own!
// Note that this must be in the format of a string where packages are delimited by '/' and
// Note that these must be in the format of a string where packages are delimited by '/' and
// classes by '.', e.g. "kotlin/Map.Entry"
redactedAnnotation = "dev/zacsweers/redacted/annotations/Redacted" // Default
redactedAnnotations.add("dev/zacsweers/redacted/annotations/Redacted") // Default

// Define a custom unredacted annotation.
unredactedAnnotation = "dev/zacsweers/redacted/annotations/Unredacted" // Default
// Define custom unredacted annotations.
unredactedAnnotations.add("dev/zacsweers/redacted/annotations/Unredacted") // Default

// Define whether or not this is enabled. Useful if you want to gate this behind a dynamic
// build configuration.
// Define whether this plugin is enabled on this compilation. Useful if you want to
// gate this behind a dynamic build configuration.
enabled = true // Default

// Define a custom replacement string for redactions.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,47 @@ package dev.zacsweers.redacted.gradle
import javax.inject.Inject
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.api.provider.SetProperty

internal const val DEFAULT_ANNOTATION = "dev/zacsweers/redacted/annotations/Redacted"
internal val DEFAULT_ANNOTATION_SET = setOf(DEFAULT_ANNOTATION)
internal const val DEFAULT_UNREDACTED_ANNOTATION = "dev/zacsweers/redacted/annotations/Unredacted"
internal val DEFAULT_UNREDACTED_ANNOTATION_SET = setOf(DEFAULT_UNREDACTED_ANNOTATION)

public abstract class RedactedPluginExtension @Inject constructor(objects: ObjectFactory) {
@Deprecated("Use redactedAnnotations instead", ReplaceWith("redactedAnnotations"))
public val redactedAnnotation: Property<String> =
objects.property(String::class.java).convention(DEFAULT_ANNOTATION)

/**
* Define a custom redacted marker annotation. The -annotations artifact won't be automatically
* Define custom redacted marker annotations. The -annotations artifact won't be automatically
* added to dependencies if you define your own!
*
* Note that this must be in the format of a string where packages are delimited by '/' and
* Note that these must be in the format of a string where packages are delimited by '/' and
* classes by '.', e.g. "kotlin/Map.Entry"
*/
public val redactedAnnotation: Property<String> =
objects.property(String::class.java).convention(DEFAULT_ANNOTATION)
public val redactedAnnotations: SetProperty<String> =
objects.setProperty(String::class.java).convention(setOf(DEFAULT_ANNOTATION))

@Deprecated("Use unredactedAnnotations instead", ReplaceWith("unredactedAnnotations"))
public val unredactedAnnotation: Property<String> =
objects.property(String::class.java).convention(DEFAULT_UNREDACTED_ANNOTATION)

/**
* Define custom unredacted marker annotations. The -annotations artifact won't be automatically
* added to dependencies if you define your own!
*
* Note that these must be in the format of a string where packages are delimited by '/' and
* classes by '.', e.g. "kotlin/Map.Entry"
*/
public val unredactedAnnotations: SetProperty<String> =
objects.setProperty(String::class.java).convention(setOf(DEFAULT_UNREDACTED_ANNOTATION))

/** Flag to enable/disable the plugin on this specific compilation. */
public val enabled: Property<Boolean> =
objects.property(Boolean::class.javaObjectType).convention(true)

/** Defines a custom replacement string. The default is "██". */
public val replacementString: Property<String> =
objects.property(String::class.java).convention("██")
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,28 @@ public class RedactedGradleSubplugin : KotlinCompilerPluginSupportPlugin {
): Provider<List<SubpluginOption>> {
val project = kotlinCompilation.target.project
val extension = project.extensions.getByType(RedactedPluginExtension::class.java)
val annotation = extension.redactedAnnotation
val unredactedAnnotation = extension.unredactedAnnotation
@Suppress("DEPRECATION")
val annotations =
extension.redactedAnnotations.zip(extension.redactedAnnotation) {
annotations,
singleAnnotation ->
annotations + singleAnnotation
}
@Suppress("DEPRECATION")
val unredactedAnnotations =
extension.unredactedAnnotations.zip(extension.unredactedAnnotation) {
annotations,
singleAnnotation ->
annotations + singleAnnotation
}

// Default annotation is used, so add it as a dependency
// Note only multiplatform, jvm/android, and js are supported. Anyone else is on their own.
if (annotation.get() == DEFAULT_ANNOTATION) {
val useDefaults =
annotations.getOrElse(DEFAULT_ANNOTATION_SET) == DEFAULT_ANNOTATION_SET ||
unredactedAnnotations.getOrElse(DEFAULT_UNREDACTED_ANNOTATION_SET) ==
DEFAULT_UNREDACTED_ANNOTATION_SET
if (useDefaults) {
project.dependencies.add(
kotlinCompilation.implementationConfigurationName,
"dev.zacsweers.redacted:redacted-compiler-plugin-annotations:$VERSION",
Expand All @@ -62,8 +78,11 @@ public class RedactedGradleSubplugin : KotlinCompilerPluginSupportPlugin {
listOf(
SubpluginOption(key = "enabled", value = enabled.toString()),
SubpluginOption(key = "replacementString", value = extension.replacementString.get()),
SubpluginOption(key = "redactedAnnotation", value = annotation.get()),
SubpluginOption(key = "unredactedAnnotation", value = unredactedAnnotation.get()),
SubpluginOption(key = "redactedAnnotations", value = annotations.get().joinToString(",")),
SubpluginOption(
key = "unredactedAnnotations",
value = unredactedAnnotations.get().joinToString(","),
),
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ internal val KEY_ENABLED =
CompilerConfigurationKey<Boolean>("Enable/disable Redacted's plugin on the given compilation")
internal val KEY_REPLACEMENT_STRING =
CompilerConfigurationKey<String>("The replacement string to use in redactions")
internal val KEY_REDACTED_ANNOTATION =
internal val KEY_REDACTED_ANNOTATIONS =
CompilerConfigurationKey<String>(
"The redacted marker annotation (i.e. com/example/Redacted) to look for when redacting"
"The redacted marker annotations (i.e. com/example/Redacted) to look for when redacting"
)
internal val KEY_UNREDACTED_ANNOTATION =
CompilerConfigurationKey<String>(
"The unredacted marker annotation (i.e. com/example/Unredacted) to look for when redacting"
"The unredacted marker annotations (i.e. com/example/Unredacted) to look for when redacting"
)

@OptIn(ExperimentalCompilerApi::class)
Expand All @@ -59,18 +59,18 @@ public class RedactedCommandLineProcessor : CommandLineProcessor {
allowMultipleOccurrences = false,
)

val OPTION_REDACTED_ANNOTATION =
val OPTION_REDACTED_ANNOTATIONS =
CliOption(
optionName = "redactedAnnotation",
optionName = "redactedAnnotations",
valueDescription = "String",
description = KEY_REDACTED_ANNOTATION.toString(),
description = KEY_REDACTED_ANNOTATIONS.toString(),
required = true,
allowMultipleOccurrences = false,
)

val OPTION_UNREDACTED_ANNOTATION =
val OPTION_UNREDACTED_ANNOTATIONS =
CliOption(
optionName = "unredactedAnnotation",
optionName = "unredactedAnnotations",
valueDescription = "String",
description = KEY_UNREDACTED_ANNOTATION.toString(),
required = true,
Expand All @@ -84,8 +84,8 @@ public class RedactedCommandLineProcessor : CommandLineProcessor {
listOf(
OPTION_ENABLED,
OPTION_REPLACEMENT_STRING,
OPTION_REDACTED_ANNOTATION,
OPTION_UNREDACTED_ANNOTATION,
OPTION_REDACTED_ANNOTATIONS,
OPTION_UNREDACTED_ANNOTATIONS,
)

override fun processOption(
Expand All @@ -96,8 +96,8 @@ public class RedactedCommandLineProcessor : CommandLineProcessor {
when (option.optionName) {
"enabled" -> configuration.put(KEY_ENABLED, value.toBoolean())
"replacementString" -> configuration.put(KEY_REPLACEMENT_STRING, value)
"redactedAnnotation" -> configuration.put(KEY_REDACTED_ANNOTATION, value)
"unredactedAnnotation" -> configuration.put(KEY_UNREDACTED_ANNOTATION, value)
"redactedAnnotations" -> configuration.put(KEY_REDACTED_ANNOTATIONS, value)
"unredactedAnnotations" -> configuration.put(KEY_UNREDACTED_ANNOTATION, value)
else -> error("Unknown plugin option: ${option.optionName}")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,21 @@ import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.ClassId

internal class RedactedIrGenerationExtension(
private val messageCollector: MessageCollector,
private val replacementString: String,
private val redactedAnnotationName: FqName,
private val unRedactedAnnotationName: FqName,
private val redactedAnnotations: Set<ClassId>,
private val unRedactedAnnotations: Set<ClassId>,
) : IrGenerationExtension {

override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
val redactedTransformer =
RedactedIrVisitor(
pluginContext,
redactedAnnotationName,
unRedactedAnnotationName,
redactedAnnotations,
unRedactedAnnotations,
replacementString,
messageCollector,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,15 @@ import org.jetbrains.kotlin.ir.util.isPrimitiveArray
import org.jetbrains.kotlin.ir.util.parentClassOrNull
import org.jetbrains.kotlin.ir.util.primaryConstructor
import org.jetbrains.kotlin.ir.util.properties
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.util.OperatorNameConventions

internal const val LOG_PREFIX = "*** REDACTED (IR):"

internal class RedactedIrVisitor(
private val pluginContext: IrPluginContext,
private val redactedAnnotation: FqName,
private val unRedactedAnnotation: FqName,
private val redactedAnnotations: Set<ClassId>,
private val unRedactedAnnotations: Set<ClassId>,
private val replacementString: String,
private val messageCollector: MessageCollector,
) : IrElementTransformerVoidWithContext() {
Expand All @@ -78,10 +78,10 @@ internal class RedactedIrVisitor(
primaryConstructor.valueParameters.associateBy { it.name.asString() }

val properties = mutableListOf<Property>()
val classIsRedacted = declarationParent.hasAnnotation(redactedAnnotation)
val classIsUnredacted = declarationParent.hasAnnotation(unRedactedAnnotation)
val classIsRedacted = redactedAnnotations.any(declarationParent::hasAnnotation)
val classIsUnredacted = unRedactedAnnotations.any(declarationParent::hasAnnotation)
val supertypeIsRedacted by unsafeLazy {
declarationParent.getAllSuperclasses().any { it.hasAnnotation(redactedAnnotation) }
declarationParent.getAllSuperclasses().any { redactedAnnotations.any(it::hasAnnotation) }
}
var anyRedacted = false
var anyUnredacted = false
Expand Down Expand Up @@ -150,10 +150,10 @@ internal class RedactedIrVisitor(
}

private val IrProperty.isRedacted: Boolean
get() = hasAnnotation(redactedAnnotation)
get() = redactedAnnotations.any(::hasAnnotation)

private val IrProperty.isUnredacted: Boolean
get() = hasAnnotation(unRedactedAnnotation)
get() = unRedactedAnnotations.any(::hasAnnotation)

/**
* The actual body of the toString method. Copied from
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,25 +40,31 @@ public class RedactedComponentRegistrar : CompilerPluginRegistrar() {
val messageCollector =
configuration.get(CommonConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE)
val replacementString = checkNotNull(configuration[KEY_REPLACEMENT_STRING])
val redactedAnnotation = checkNotNull(configuration[KEY_REDACTED_ANNOTATION])
val unRedactedAnnotation = checkNotNull(configuration[KEY_UNREDACTED_ANNOTATION])
val redactedAnnotations =
checkNotNull(configuration[KEY_REDACTED_ANNOTATIONS]).splitToSequence(",").mapTo(
LinkedHashSet()
) {
ClassId.fromString(it)
}
val unRedactedAnnotations =
checkNotNull(configuration[KEY_UNREDACTED_ANNOTATION]).splitToSequence(",").mapTo(
LinkedHashSet()
) {
ClassId.fromString(it)
}
val usesK2 = configuration.languageVersionSettings.languageVersion.usesK2
val redactedAnnotationClassId = ClassId.fromString(redactedAnnotation)
val fqRedactedAnnotation = redactedAnnotationClassId.asSingleFqName()
val unRedactedAnnotationClassId = ClassId.fromString(unRedactedAnnotation)
val fqUnRedactedAnnotation = unRedactedAnnotationClassId.asSingleFqName()

if (usesK2) {
FirExtensionRegistrarAdapter.registerExtension(
FirRedactedExtensionRegistrar(redactedAnnotationClassId, unRedactedAnnotationClassId)
FirRedactedExtensionRegistrar(redactedAnnotations, unRedactedAnnotations)
)
}
IrGenerationExtension.registerExtension(
RedactedIrGenerationExtension(
messageCollector,
replacementString,
fqRedactedAnnotation,
fqUnRedactedAnnotation,
redactedAnnotations,
unRedactedAnnotations,
)
)
}
Expand Down
Loading
Loading