+1
-1
@@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
kotlin("jvm") version "1.3.70" apply false
|
||||
kotlin("jvm") version "1.4.0" apply false
|
||||
id("org.jetbrains.dokka") version "0.10.0" apply false
|
||||
id("com.gradle.plugin-publish") version "0.11.0" apply false
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
plugins {
|
||||
id("java-gradle-plugin")
|
||||
kotlin("jvm")
|
||||
kotlin("kapt")
|
||||
|
||||
id("com.gradle.plugin-publish")
|
||||
}
|
||||
@@ -11,9 +10,6 @@ plugins {
|
||||
dependencies {
|
||||
implementation(kotlin("stdlib"))
|
||||
implementation(kotlin("gradle-plugin-api"))
|
||||
|
||||
kapt("com.google.auto.service:auto-service:1.0-rc6")
|
||||
compileOnly("com.google.auto.service:auto-service-annotations:1.0-rc6")
|
||||
}
|
||||
|
||||
pluginBundle {
|
||||
|
||||
+29
-3
@@ -16,11 +16,37 @@
|
||||
|
||||
package com.bnorm.power
|
||||
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerPluginSupportPlugin
|
||||
import org.jetbrains.kotlin.gradle.plugin.SubpluginArtifact
|
||||
import org.jetbrains.kotlin.gradle.plugin.SubpluginOption
|
||||
|
||||
class PowerAssertGradlePlugin : Plugin<Project> {
|
||||
override fun apply(project: Project): Unit = with(project) {
|
||||
class PowerAssertGradlePlugin : KotlinCompilerPluginSupportPlugin {
|
||||
override fun apply(target: Project): Unit = with(target) {
|
||||
extensions.create("kotlinPowerAssert", PowerAssertGradleExtension::class.java)
|
||||
}
|
||||
|
||||
override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean = true
|
||||
|
||||
override fun getCompilerPluginId(): String = "com.bnorm.kotlin-power-assert"
|
||||
|
||||
override fun getPluginArtifact(): SubpluginArtifact = SubpluginArtifact(
|
||||
groupId = "com.bnorm.power",
|
||||
artifactId = "kotlin-power-assert",
|
||||
version = "0.4.0-SNAPSHOT"
|
||||
)
|
||||
|
||||
override fun applyToCompilation(
|
||||
kotlinCompilation: KotlinCompilation<*>
|
||||
): Provider<List<SubpluginOption>> {
|
||||
val project = kotlinCompilation.target.project
|
||||
val extension = project.extensions.getByType(PowerAssertGradleExtension::class.java)
|
||||
return project.provider {
|
||||
extension.functions.map {
|
||||
SubpluginOption(key = "function", value = it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Brian Norman
|
||||
*
|
||||
* 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.bnorm.power
|
||||
|
||||
import com.google.auto.service.AutoService
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.tasks.compile.AbstractCompile
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinGradleSubplugin
|
||||
import org.jetbrains.kotlin.gradle.plugin.SubpluginArtifact
|
||||
import org.jetbrains.kotlin.gradle.plugin.SubpluginOption
|
||||
|
||||
@AutoService(KotlinGradleSubplugin::class)
|
||||
class PowerAssertGradleSubplugin : KotlinGradleSubplugin<AbstractCompile> {
|
||||
|
||||
override fun getCompilerPluginId(): String = "com.bnorm.kotlin-power-assert"
|
||||
|
||||
override fun isApplicable(project: Project, task: AbstractCompile): Boolean =
|
||||
project.plugins.hasPlugin(PowerAssertGradlePlugin::class.java)
|
||||
|
||||
|
||||
override fun getPluginArtifact(): SubpluginArtifact = SubpluginArtifact(
|
||||
groupId = "com.bnorm.power",
|
||||
artifactId = "kotlin-power-assert",
|
||||
version = "0.4.0-SNAPSHOT"
|
||||
)
|
||||
|
||||
override fun apply(
|
||||
project: Project,
|
||||
kotlinCompile: AbstractCompile,
|
||||
javaCompile: AbstractCompile?,
|
||||
variantData: Any?,
|
||||
androidProjectHandler: Any?,
|
||||
kotlinCompilation: KotlinCompilation<KotlinCommonOptions>?
|
||||
): List<SubpluginOption> {
|
||||
val extension = project.extensions.findByType(PowerAssertGradleExtension::class.java)
|
||||
?: PowerAssertGradleExtension()
|
||||
|
||||
return extension.functions.map {
|
||||
SubpluginOption(key = "function", value = it)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,13 +11,13 @@ plugins {
|
||||
|
||||
dependencies {
|
||||
implementation(kotlin("stdlib-jdk8"))
|
||||
compileOnly("org.jetbrains.kotlin:kotlin-compiler-embeddable:1.3.70")
|
||||
compileOnly("org.jetbrains.kotlin:kotlin-compiler-embeddable")
|
||||
|
||||
kapt("com.google.auto.service:auto-service:1.0-rc6")
|
||||
compileOnly("com.google.auto.service:auto-service-annotations:1.0-rc6")
|
||||
|
||||
testImplementation(kotlin("test-junit"))
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-compiler-embeddable:1.3.70")
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-compiler-embeddable")
|
||||
testImplementation("com.github.tschuchortdev:kotlin-compile-testing:1.2.6")
|
||||
}
|
||||
|
||||
|
||||
@@ -16,13 +16,14 @@
|
||||
|
||||
package com.bnorm.power
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.deepCopyWithVariables
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.declarations.IrDeclarationParent
|
||||
import org.jetbrains.kotlin.ir.expressions.IrCall
|
||||
import org.jetbrains.kotlin.ir.expressions.IrConst
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
|
||||
import org.jetbrains.kotlin.ir.expressions.IrWhen
|
||||
import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
|
||||
|
||||
sealed class Node {
|
||||
@@ -31,7 +32,7 @@ sealed class Node {
|
||||
val children: List<Node> get() = mutableChildren
|
||||
|
||||
protected fun dump(builder: StringBuilder, indent: Int) {
|
||||
builder.append(" ".repeat(indent)).append(this).appendln()
|
||||
builder.append(" ".repeat(indent)).append(this).appendLine()
|
||||
for (child in children) {
|
||||
child.dump(builder, indent + 1)
|
||||
}
|
||||
@@ -67,10 +68,10 @@ class ExpressionNode(
|
||||
_expressions.add(expression)
|
||||
}
|
||||
|
||||
fun getExpressionsCopy(): List<IrExpression> {
|
||||
fun getExpressionsCopy(initialParent: IrDeclarationParent?): List<IrExpression> {
|
||||
// Return a copy of all the expression by creating a deep copy of the head
|
||||
// expression and running back through the assertion tree builder
|
||||
val headCopy = _expressions.first().deepCopyWithVariables()
|
||||
val headCopy = _expressions.first().deepCopyWithSymbols(initialParent)
|
||||
return (buildAssertTree(headCopy).children.single() as ExpressionNode)._expressions
|
||||
}
|
||||
|
||||
@@ -101,7 +102,7 @@ fun buildAssertTree(expression: IrExpression): RootNode {
|
||||
}
|
||||
|
||||
override fun visitCall(expression: IrCall, data: Node) {
|
||||
if (expression.symbol.descriptor.name.asString() == "EQEQ" && expression.origin == IrStatementOrigin.EXCLEQ) {
|
||||
if (expression.symbol.owner.name.asString() == "EQEQ" && expression.origin == IrStatementOrigin.EXCLEQ) {
|
||||
// Skip the EQEQ part of a EXCLEQ call
|
||||
expression.acceptChildren(this, data)
|
||||
} else {
|
||||
|
||||
@@ -63,6 +63,7 @@ fun IrBuilderWithScope.buildMessage(
|
||||
|
||||
val columnOffset: Int = when (original) {
|
||||
is IrMemberAccessExpression -> {
|
||||
// TODO IrFunction doesn't have 'isInfix' as a property
|
||||
val descriptor = original.symbol.descriptor
|
||||
when {
|
||||
descriptor is FunctionDescriptor && descriptor.isInfix -> source.indexOf(descriptor.name.asString())
|
||||
|
||||
@@ -23,11 +23,7 @@ import org.jetbrains.kotlin.backend.common.ir.asSimpleLambda
|
||||
import org.jetbrains.kotlin.backend.common.ir.inline
|
||||
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
|
||||
import org.jetbrains.kotlin.backend.common.lower.at
|
||||
import org.jetbrains.kotlin.backend.common.serialization.findPackage
|
||||
import org.jetbrains.kotlin.builtins.isBuiltinFunctionalType
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
|
||||
@@ -37,6 +33,7 @@ import org.jetbrains.kotlin.ir.builders.irCallOp
|
||||
import org.jetbrains.kotlin.ir.builders.irFalse
|
||||
import org.jetbrains.kotlin.ir.builders.irReturn
|
||||
import org.jetbrains.kotlin.ir.builders.irString
|
||||
import org.jetbrains.kotlin.ir.builders.parent
|
||||
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.ir.declarations.path
|
||||
@@ -47,18 +44,21 @@ import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
|
||||
import org.jetbrains.kotlin.ir.expressions.IrStringConcatenation
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionExpressionImpl
|
||||
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
|
||||
import org.jetbrains.kotlin.ir.types.IrSimpleType
|
||||
import org.jetbrains.kotlin.ir.types.IrType
|
||||
import org.jetbrains.kotlin.ir.types.IrTypeArgument
|
||||
import org.jetbrains.kotlin.ir.types.IrTypeProjection
|
||||
import org.jetbrains.kotlin.ir.types.getClass
|
||||
import org.jetbrains.kotlin.ir.types.isBoolean
|
||||
import org.jetbrains.kotlin.ir.types.isSubtypeOf
|
||||
import org.jetbrains.kotlin.ir.util.functions
|
||||
import org.jetbrains.kotlin.ir.util.referenceFunction
|
||||
import org.jetbrains.kotlin.ir.util.isFunctionOrKFunction
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.typeUtil.isBoolean
|
||||
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
|
||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||
import java.io.File
|
||||
|
||||
@@ -90,16 +90,18 @@ class PowerAssertCallTransformer(
|
||||
}
|
||||
|
||||
override fun visitCall(expression: IrCall): IrExpression {
|
||||
val function = expression.symbol.descriptor
|
||||
if (functions.none { function.fqNameSafe == it })
|
||||
// TODO expression.symbol.owner.fqNameSafe includes the wrapping ClassKt while descriptor doesn't
|
||||
val fqName = expression.symbol.descriptor.fqNameSafe
|
||||
if (functions.none { fqName == it })
|
||||
return super.visitCall(expression)
|
||||
|
||||
val delegate = findDelegate(function) ?: run {
|
||||
val delegate = findDelegate(fqName) ?: run {
|
||||
// Find a valid delegate function or do not translate
|
||||
// TODO log a warning
|
||||
return super.visitCall(expression)
|
||||
}
|
||||
|
||||
val function = expression.symbol.owner
|
||||
val assertionArgument = expression.getValueArgument(0)!!
|
||||
val messageArgument = if (function.valueParameters.size == 2) expression.getValueArgument(1) else null
|
||||
|
||||
@@ -114,7 +116,7 @@ class PowerAssertCallTransformer(
|
||||
val title = when {
|
||||
messageArgument is IrConst<*> -> messageArgument
|
||||
messageArgument is IrStringConcatenation -> messageArgument
|
||||
lambda != null -> lambda.inline()
|
||||
lambda != null -> lambda.inline(parent)
|
||||
messageArgument != null -> {
|
||||
val invoke = messageArgument.type.getClass()!!.functions.single { it.name == OperatorNameConventions.INVOKE }
|
||||
irCallOp(invoke.symbol, invoke.returnType, messageArgument)
|
||||
@@ -142,13 +144,11 @@ class PowerAssertCallTransformer(
|
||||
fun buildCall(builder: IrBuilderWithScope, message: IrExpression): IrExpression
|
||||
}
|
||||
|
||||
private fun findDelegate(function: FunctionDescriptor): FunctionDelegate? {
|
||||
fun KotlinType.toIrType() = context.typeTranslator.translateType(this)
|
||||
|
||||
return context.findOverloads(function)
|
||||
private fun findDelegate(fqName: FqName): FunctionDelegate? {
|
||||
return context.findOverloads(fqName)
|
||||
.mapNotNull { overload ->
|
||||
// TODO allow other signatures than (Boolean, String) and (Boolean, () -> String))
|
||||
val parameters = overload.descriptor.valueParameters
|
||||
val parameters = overload.owner.valueParameters
|
||||
if (parameters.size != 2) return@mapNotNull null
|
||||
if (!parameters[0].type.isBoolean()) return@mapNotNull null
|
||||
|
||||
@@ -156,7 +156,7 @@ class PowerAssertCallTransformer(
|
||||
isStringSupertype(parameters[1].type) -> {
|
||||
object : FunctionDelegate {
|
||||
override fun buildCall(builder: IrBuilderWithScope, message: IrExpression): IrExpression = with(builder) {
|
||||
irCall(overload, type = overload.descriptor.returnType!!.toIrType()).apply {
|
||||
irCall(overload, type = overload.owner.returnType).apply {
|
||||
putValueArgument(0, irFalse())
|
||||
putValueArgument(1, message)
|
||||
}
|
||||
@@ -166,6 +166,7 @@ class PowerAssertCallTransformer(
|
||||
isStringFunction(parameters[1].type) -> {
|
||||
object : FunctionDelegate {
|
||||
override fun buildCall(builder: IrBuilderWithScope, message: IrExpression): IrExpression = with(builder) {
|
||||
val scope = this
|
||||
val lambda = buildFun {
|
||||
name = Name.special("<anonymous>")
|
||||
returnType = context.irBuiltIns.stringType
|
||||
@@ -176,30 +177,35 @@ class PowerAssertCallTransformer(
|
||||
body = bodyBuilder.irBlockBody {
|
||||
+irReturn(message)
|
||||
}
|
||||
parent = scope.parent
|
||||
}
|
||||
val expression = IrFunctionExpressionImpl(-1, -1, context.irBuiltIns.stringType, lambda, IrStatementOrigin.LAMBDA)
|
||||
irCall(overload, type = overload.descriptor.returnType!!.toIrType()).apply {
|
||||
irCall(overload, type = overload.owner.returnType).apply {
|
||||
putValueArgument(0, irFalse())
|
||||
putValueArgument(1, expression)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
else -> {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
private fun isStringFunction(type: KotlinType): Boolean =
|
||||
type.isBuiltinFunctionalType && type.arguments.size == 1 && isStringSupertype(type.arguments.first().type)
|
||||
private fun isStringFunction(type: IrType): Boolean =
|
||||
type.isFunctionOrKFunction() && type is IrSimpleType && (type.arguments.size == 1 && isStringSupertype(type.arguments.first()))
|
||||
|
||||
private fun isStringSupertype(type: KotlinType): Boolean =
|
||||
context.builtIns.stringType.isSubtypeOf(type)
|
||||
private fun isStringSupertype(argument: IrTypeArgument): Boolean =
|
||||
argument is IrTypeProjection && isStringSupertype(argument.type)
|
||||
|
||||
private fun isStringSupertype(type: IrType): Boolean =
|
||||
context.irBuiltIns.stringType.isSubtypeOf(type, context.irBuiltIns)
|
||||
}
|
||||
|
||||
// TODO is this the best way to find overload functions?
|
||||
private fun IrPluginContext.findOverloads(function: FunctionDescriptor): List<IrFunctionSymbol> =
|
||||
function.findPackage().getMemberScope()
|
||||
.getContributedFunctions(function.name, NoLookupLocation.FROM_BACKEND)
|
||||
.map { symbolTable.referenceFunction(it) }
|
||||
private fun IrPluginContext.findOverloads(fqName: FqName): List<IrFunctionSymbol> {
|
||||
return referenceFunctions(fqName).toList()
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ class PowerAssertCommandLineProcessor : CommandLineProcessor {
|
||||
optionName = "function",
|
||||
valueDescription = "function full-qualified name",
|
||||
description = "fully qualified path of function to intercept",
|
||||
required = false, // TODO required for Kotlin/JS
|
||||
allowMultipleOccurrences = true
|
||||
)
|
||||
)
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.ir.builders.IrStatementsBuilder
|
||||
import org.jetbrains.kotlin.ir.builders.irBlock
|
||||
import org.jetbrains.kotlin.ir.builders.irGet
|
||||
import org.jetbrains.kotlin.ir.builders.irTemporary
|
||||
import org.jetbrains.kotlin.ir.builders.parent
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.IrGetValue
|
||||
import org.jetbrains.kotlin.ir.expressions.IrWhen
|
||||
@@ -75,13 +76,13 @@ abstract class PowerAssertGenerator {
|
||||
node: ExpressionNode,
|
||||
thenPart: IrStatementsBuilder<*>.(subStack: MutableList<IrStackVariable>) -> IrExpression
|
||||
): IrWhen {
|
||||
val expressions = node.getExpressionsCopy()
|
||||
val expressions = node.getExpressionsCopy(this.parent)
|
||||
val stackTransformer = StackBuilder(this, stack, expressions)
|
||||
val transformed = expressions.first().transform(stackTransformer, null)
|
||||
return irIfThen(irNot(transformed), thenPart(stack.toMutableList()))
|
||||
}
|
||||
|
||||
inner class StackBuilder(
|
||||
class StackBuilder(
|
||||
private val builder: IrStatementsBuilder<*>,
|
||||
private val stack: MutableList<IrStackVariable>,
|
||||
private val transform: List<IrExpression>
|
||||
|
||||
+2
-21
@@ -5,7 +5,7 @@ buildscript {
|
||||
}
|
||||
|
||||
plugins {
|
||||
kotlin("multiplatform") version "1.3.70"
|
||||
kotlin("multiplatform") version "1.4.0"
|
||||
}
|
||||
apply(plugin = "com.bnorm.power.kotlin-power-assert")
|
||||
|
||||
@@ -23,15 +23,9 @@ kotlin {
|
||||
}
|
||||
}
|
||||
}
|
||||
js {
|
||||
js(IR) {
|
||||
browser()
|
||||
nodejs()
|
||||
|
||||
compilations.all {
|
||||
kotlinOptions {
|
||||
kotlinOptions.freeCompilerArgs += listOf("-Xir-produce-klib-dir", "-Xir-produce-js")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val osName = System.getProperty("os.name")
|
||||
@@ -43,9 +37,6 @@ kotlin {
|
||||
|
||||
sourceSets {
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
implementation(kotlin("stdlib"))
|
||||
}
|
||||
}
|
||||
val commonTest by getting {
|
||||
dependencies {
|
||||
@@ -53,21 +44,11 @@ kotlin {
|
||||
implementation(kotlin("test-annotations-common"))
|
||||
}
|
||||
}
|
||||
val jvmMain by getting {
|
||||
dependencies {
|
||||
implementation(kotlin("stdlib-jdk8"))
|
||||
}
|
||||
}
|
||||
val jvmTest by getting {
|
||||
dependencies {
|
||||
implementation(kotlin("test-junit"))
|
||||
}
|
||||
}
|
||||
val jsMain by getting {
|
||||
dependencies {
|
||||
implementation(kotlin("stdlib-js"))
|
||||
}
|
||||
}
|
||||
val jsTest by getting {
|
||||
dependencies {
|
||||
implementation(kotlin("test-js"))
|
||||
|
||||
Reference in New Issue
Block a user