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

Allow @Unredacted classes & @Redacted child objects #212

Merged
merged 1 commit into from Apr 26, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -16,6 +16,7 @@
package dev.zacsweers.redacted.annotations

import kotlin.annotation.AnnotationRetention.BINARY
import kotlin.annotation.AnnotationTarget.CLASS
import kotlin.annotation.AnnotationTarget.PROPERTY

/**
Expand All @@ -33,4 +34,4 @@ import kotlin.annotation.AnnotationTarget.PROPERTY
* println(user) // User(name = "Bob", phoneNumber = "██")
* ```
*/
@Retention(BINARY) @Target(PROPERTY) public annotation class Unredacted
@Retention(BINARY) @Target(PROPERTY, CLASS) public annotation class Unredacted
Expand Up @@ -87,6 +87,7 @@ internal class RedactedIrVisitor(

val properties = mutableListOf<Property>()
val classIsRedacted = declarationParent.hasAnnotation(redactedAnnotation)
val classIsUnredacted = declarationParent.hasAnnotation(unredactedAnnotation)
val supertypeIsRedacted by
lazy(NONE) {
declarationParent.getAllSuperclasses().any { it.hasAnnotation(redactedAnnotation) }
Expand All @@ -106,7 +107,7 @@ internal class RedactedIrVisitor(
properties += Property(prop, isRedacted, isUnredacted, parameter)
}

if (classIsRedacted || supertypeIsRedacted || anyRedacted) {
if (classIsRedacted || supertypeIsRedacted || classIsUnredacted || anyRedacted) {
if (declaration.origin == IrDeclarationOrigin.DEFINED) {
declaration.reportError(
"@Redacted is only supported on data or value classes that do *not* have a custom toString() function. Please remove the function or remove the @Redacted annotations."
Expand Down Expand Up @@ -134,7 +135,24 @@ internal class RedactedIrVisitor(
return super.visitFunctionNew(declaration)
}
if (declarationParent.isObject) {
declarationParent.reportError("@Redacted is useless on object classes.")
if (!supertypeIsRedacted) {
declarationParent.reportError("@Redacted is useless on object classes.")
return super.visitFunctionNew(declaration)
} else if (classIsUnredacted) {
declarationParent.reportError("@Unredacted is useless on object classes.")
return super.visitFunctionNew(declaration)
}
}
if (classIsRedacted && classIsUnredacted) {
declarationParent.reportError(
"@Redacted and @Unredacted cannot be applied to a single class."
)
return super.visitFunctionNew(declaration)
}
if (classIsUnredacted && !supertypeIsRedacted) {
declarationParent.reportError(
"@Unredacted cannot be applied to a class unless a supertype is marked @Redacted."
)
return super.visitFunctionNew(declaration)
}
if (anyUnredacted && (!classIsRedacted && !supertypeIsRedacted)) {
Expand All @@ -149,7 +167,12 @@ internal class RedactedIrVisitor(
)
return super.visitFunctionNew(declaration)
}
declaration.convertToGeneratedToString(properties, classIsRedacted, supertypeIsRedacted)
declaration.convertToGeneratedToString(
properties,
classIsRedacted,
classIsUnredacted,
supertypeIsRedacted,
)
}
}

Expand All @@ -166,6 +189,7 @@ internal class RedactedIrVisitor(
private fun IrFunction.convertToGeneratedToString(
properties: List<Property>,
classIsRedacted: Boolean,
classIsUnredacted: Boolean,
supertypeIsRedacted: Boolean,
) {
val parent = parent as IrClass
Expand All @@ -179,6 +203,7 @@ internal class RedactedIrVisitor(
irFunction = this@convertToGeneratedToString,
irProperties = properties,
classIsRedacted = classIsRedacted,
classIsUnredacted = classIsUnredacted,
supertypeIsRedacted = supertypeIsRedacted,
)
}
Expand Down Expand Up @@ -209,12 +234,13 @@ internal class RedactedIrVisitor(
irFunction: IrFunction,
irProperties: List<Property>,
classIsRedacted: Boolean,
classIsUnredacted: Boolean,
supertypeIsRedacted: Boolean,
) {
val irConcat = irConcat()
irConcat.addArgument(irString(irClass.name.asString() + "("))
val hasUnredactedProperties by lazy(NONE) { irProperties.any { it.isUnredacted } }
if (classIsRedacted && !hasUnredactedProperties) {
if (classIsRedacted && !classIsUnredacted && !hasUnredactedProperties) {
irConcat.addArgument(irString(replacementString))
} else {
var first = true
Expand All @@ -225,7 +251,7 @@ internal class RedactedIrVisitor(
val redactProperty =
property.isRedacted ||
(classIsRedacted && !property.isUnredacted) ||
(supertypeIsRedacted && !property.isUnredacted)
(supertypeIsRedacted && !classIsUnredacted && !property.isUnredacted)
if (redactProperty) {
irConcat.addArgument(irString(replacementString))
} else {
Expand Down
Expand Up @@ -34,12 +34,27 @@ class SmokeTest {
assertThat(notSoSecretChild.toString()).isEqualTo("NotSoSecretChild(unredacted=public)")
}

@Test
fun unredactedClassAbstractExample() {
val notAtAllSecretChild = AbstractBase.NotAtAllSecretChild("public")
assertThat(notAtAllSecretChild.toString()).isEqualTo("NotAtAllSecretChild(unredacted=public)")
}

@Test
fun redactedObjectAbstractExample() {
assertThat(AbstractBase.ProplessChild.toString()).isEqualTo("ProplessChild()")
}

@Redacted
abstract class AbstractBase {

data class SecretChild(val redact: String) : AbstractBase()

data class NotSoSecretChild(@Unredacted val unredacted: String) : AbstractBase()

@Unredacted data class NotAtAllSecretChild(val unredacted: String) : AbstractBase()

data object ProplessChild : AbstractBase()
}

@Test
Expand All @@ -54,12 +69,27 @@ class SmokeTest {
assertThat(notSoSecretChild.toString()).isEqualTo("NotSoSecretChild(unredacted=public)")
}

@Test
fun unredactedClassSealedExample() {
val notAtAllSecretChild = SecretParent.NotAtAllSecretChild("public")
assertThat(notAtAllSecretChild.toString()).isEqualTo("NotAtAllSecretChild(unredacted=public)")
}

@Test
fun redactedObjectSealedExample() {
assertThat(SecretParent.ProplessChild.toString()).isEqualTo("ProplessChild()")
}

@Redacted
sealed class SecretParent {

data class SecretChild(val redact: String) : SecretParent()

data class NotSoSecretChild(@Unredacted val unredacted: String) : SecretParent()

@Unredacted data class NotAtAllSecretChild(val unredacted: String) : AbstractBase()

data object ProplessChild : SecretParent()
}

@Test
Expand Down