Skip to content

Commit

Permalink
Support multiple custom annotations (#279)
Browse files Browse the repository at this point in the history
  • Loading branch information
ZacSweers authored Dec 21, 2024
1 parent 5c29f26 commit d2f4d41
Show file tree
Hide file tree
Showing 12 changed files with 244 additions and 114 deletions.
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

0 comments on commit d2f4d41

Please sign in to comment.