[K/JS] feat(Generated TS validation): add a new property and gradle task to validate generated by the IR compiler typescript declaration files.
This commit is contained in:
+36
-3
@@ -33,9 +33,7 @@ import java.nio.file.Paths
|
||||
import java.util.zip.ZipFile
|
||||
import kotlin.io.path.*
|
||||
import kotlin.streams.toList
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.test.*
|
||||
|
||||
@JsGradlePluginTests
|
||||
class Kotlin2JsIrGradlePluginIT : AbstractKotlin2JsGradlePluginIT(true) {
|
||||
@@ -252,6 +250,41 @@ class Kotlin2JsIrGradlePluginIT : AbstractKotlin2JsGradlePluginIT(true) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DisplayName("generated typescript declarations validation")
|
||||
@GradleTest
|
||||
fun testGeneratedTypeScriptDeclarationsValidation(gradleVersion: GradleVersion) {
|
||||
project("js-ir-validate-ts", gradleVersion) {
|
||||
buildGradleKts.appendText(
|
||||
"""
|
||||
|fun makeTypeScriptFileInvalid(mode: String) {
|
||||
| val dts = projectDir.resolve("build/compileSync/main/" + mode + "Executable/kotlin/js-ir-validate-ts.d.ts")
|
||||
| dts.appendText("\nlet invalidCode: unique symbol = Symbol()")
|
||||
|}
|
||||
|
|
||||
|tasks.named<org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrLink>("compileDevelopmentExecutableKotlinJs").configure {
|
||||
| doLast { makeTypeScriptFileInvalid("development") }
|
||||
|}
|
||||
|
|
||||
|tasks.named<org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrLink>("compileProductionExecutableKotlinJs").configure {
|
||||
| doLast { makeTypeScriptFileInvalid("production") }
|
||||
|}
|
||||
""".trimMargin()
|
||||
)
|
||||
|
||||
buildAndFail("compileDevelopmentExecutableKotlinJs") {
|
||||
assertTasksFailed(":developmentExecutableValidateGeneratedByCompilerTypeScript")
|
||||
assertFileInProjectExists("build/js/packages/js-ir-validate-ts/kotlin/js-ir-validate-ts.js")
|
||||
assertFileInProjectExists("build/js/packages/js-ir-validate-ts/kotlin/js-ir-validate-ts.d.ts")
|
||||
}
|
||||
|
||||
build("compileProductionExecutableKotlinJs") {
|
||||
assertTasksExecuted(":productionExecutableValidateGeneratedByCompilerTypeScript")
|
||||
assertFileInProjectExists("build/js/packages/js-ir-validate-ts/kotlin/js-ir-validate-ts.js")
|
||||
assertFileInProjectExists("build/js/packages/js-ir-validate-ts/kotlin/js-ir-validate-ts.d.ts")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JsGradlePluginTests
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
plugins {
|
||||
kotlin("js")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
js(IR) {
|
||||
binaries.executable()
|
||||
browser {
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
kotlin.js.ir.development.typescript.validation.strategy=error
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package example
|
||||
|
||||
@JsExport
|
||||
class Child {
|
||||
val name: String = "Child"
|
||||
}
|
||||
|
||||
fun main() {
|
||||
println("Hello there!")
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
js(IR) {
|
||||
browser {
|
||||
commonWebpackConfig {
|
||||
cssSupport {
|
||||
enabled = true
|
||||
}
|
||||
scssSupport {
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
"jsMainImplementation"(npm("decamelize", "4.0.0", generateExternals = true))
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package example
|
||||
|
||||
fun main() {
|
||||
println("Hello there!")
|
||||
}
|
||||
+1
@@ -36,6 +36,7 @@ val npmPackages = listOf(
|
||||
NpmPackage("karma-coverage"),
|
||||
NpmPackage("karma-sourcemap-loader"),
|
||||
NpmPackage("format-util"),
|
||||
NpmPackage("typescript"),
|
||||
)
|
||||
|
||||
data class NpmPackage(
|
||||
|
||||
+9
@@ -25,6 +25,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMultiplatformPlugin
|
||||
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService
|
||||
import org.jetbrains.kotlin.gradle.targets.js.dukat.ExternalsOutputFormat
|
||||
import org.jetbrains.kotlin.gradle.targets.js.dukat.ExternalsOutputFormat.Companion.externalsOutputFormatProperty
|
||||
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinIrJsGeneratedTSValidationStrategy
|
||||
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrOutputGranularity
|
||||
import org.jetbrains.kotlin.gradle.targets.js.webpack.WebpackMajorVersion
|
||||
import org.jetbrains.kotlin.gradle.targets.native.DisabledNativeTargetsReporter
|
||||
@@ -118,6 +119,14 @@ internal class PropertiesProvider private constructor(private val project: Proje
|
||||
get() = property("kotlin.js.ir.output.granularity")?.let { KotlinJsIrOutputGranularity.byArgument(it) }
|
||||
?: KotlinJsIrOutputGranularity.PER_MODULE
|
||||
|
||||
val jsIrGeneratedTypeScriptValidationDevStrategy: KotlinIrJsGeneratedTSValidationStrategy
|
||||
get() = property("kotlin.js.ir.development.typescript.validation.strategy")?.let { KotlinIrJsGeneratedTSValidationStrategy.byArgument(it) }
|
||||
?: KotlinIrJsGeneratedTSValidationStrategy.IGNORE
|
||||
|
||||
val jsIrGeneratedTypeScriptValidationProdStrategy: KotlinIrJsGeneratedTSValidationStrategy
|
||||
get() = property("kotlin.js.ir.production.typescript.validation.strategy")?.let { KotlinIrJsGeneratedTSValidationStrategy.byArgument(it) }
|
||||
?: KotlinIrJsGeneratedTSValidationStrategy.IGNORE
|
||||
|
||||
val incrementalMultiplatform: Boolean?
|
||||
get() = booleanProperty("kotlin.incremental.multiplatform")
|
||||
|
||||
|
||||
+1
@@ -42,6 +42,7 @@ class NpmVersions : Serializable {
|
||||
val karmaCoverage = NpmPackageVersion("karma-coverage", "2.2.0")
|
||||
val karmaSourcemapLoader = NpmPackageVersion("karma-sourcemap-loader", "0.3.8")
|
||||
val formatUtil = NpmPackageVersion("format-util", "1.0.5")
|
||||
val typeScript = NpmPackageVersion("typescript", "4.7.4")
|
||||
|
||||
val kotlinJsTestRunner = KotlinGradleNpmPackage("test-js-runner")
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.gradle.targets.js.ir
|
||||
|
||||
enum class KotlinIrJsGeneratedTSValidationStrategy {
|
||||
ERROR,
|
||||
IGNORE;
|
||||
|
||||
companion object {
|
||||
fun byArgument(argument: String): KotlinIrJsGeneratedTSValidationStrategy? =
|
||||
values().firstOrNull { it.name.equals(argument, ignoreCase = true) }
|
||||
}
|
||||
}
|
||||
+11
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.gradle.targets.js.dsl.Distribution
|
||||
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsBinaryMode
|
||||
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsBinaryContainer.Companion.generateBinaryName
|
||||
import org.jetbrains.kotlin.gradle.targets.js.subtargets.DefaultDistribution
|
||||
import org.jetbrains.kotlin.gradle.targets.js.typescript.TypeScriptValidationTask
|
||||
import org.jetbrains.kotlin.gradle.tasks.withType
|
||||
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
|
||||
|
||||
@@ -50,6 +51,8 @@ sealed class JsIrBinary(
|
||||
|
||||
val linkSyncTaskName: String = linkSyncTaskName()
|
||||
|
||||
val validateGeneratedTsTaskName: String = validateTypeScriptTaskName()
|
||||
|
||||
val linkSyncTask: TaskProvider<Copy>
|
||||
get() = target.project.tasks
|
||||
.withType<Copy>()
|
||||
@@ -63,6 +66,14 @@ sealed class JsIrBinary(
|
||||
COMPILE_SYNC
|
||||
)
|
||||
|
||||
private fun validateTypeScriptTaskName(): String =
|
||||
lowerCamelCaseName(
|
||||
compilation.target.disambiguationClassifier,
|
||||
compilation.name.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME },
|
||||
name,
|
||||
TypeScriptValidationTask.NAME
|
||||
)
|
||||
|
||||
val target: KotlinTarget
|
||||
get() = compilation.target
|
||||
|
||||
|
||||
+34
-8
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.gradle.targets.js.binaryen.BinaryenExec
|
||||
import org.jetbrains.kotlin.gradle.targets.js.dsl.*
|
||||
import org.jetbrains.kotlin.gradle.targets.js.internal.RewriteSourceMapFilterReader
|
||||
import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject
|
||||
import org.jetbrains.kotlin.gradle.targets.js.typescript.TypeScriptValidationTask
|
||||
import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask
|
||||
import org.jetbrains.kotlin.gradle.tasks.registerTask
|
||||
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
|
||||
@@ -42,6 +43,7 @@ constructor(
|
||||
KotlinWasmTargetDsl,
|
||||
KotlinJsSubTargetContainerDsl,
|
||||
KotlinWasmSubTargetContainerDsl {
|
||||
private val propertiesProvider = PropertiesProvider(project)
|
||||
override lateinit var testRuns: NamedDomainObjectContainer<KotlinJsReportAggregatingTestRun>
|
||||
internal set
|
||||
|
||||
@@ -115,6 +117,7 @@ constructor(
|
||||
.withType(JsIrBinary::class.java)
|
||||
.all { binary ->
|
||||
val syncTask = registerCompileSync(binary)
|
||||
val tsValidationTask = registerTypeScriptCheckTask(binary)
|
||||
|
||||
binary.linkTask.configure {
|
||||
it.kotlinOptions.outputFile = project.buildDir
|
||||
@@ -125,7 +128,12 @@ constructor(
|
||||
.canonicalPath
|
||||
|
||||
it.finalizedBy(syncTask)
|
||||
|
||||
if (tsValidationTask != null) {
|
||||
it.finalizedBy(tsValidationTask)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,16 +141,10 @@ constructor(
|
||||
private fun registerCompileSync(binary: JsIrBinary): TaskProvider<Copy> {
|
||||
val compilation = binary.compilation
|
||||
val npmProject = compilation.npmProject
|
||||
return project.registerTask(
|
||||
return project.registerTask<Copy>(
|
||||
binary.linkSyncTaskName
|
||||
) { task ->
|
||||
task.from(
|
||||
project.layout.file(
|
||||
binary.linkTask.flatMap { linkTask ->
|
||||
linkTask.normalizedDestinationDirectory.map { it.asFile }
|
||||
}
|
||||
)
|
||||
)
|
||||
task.from(project.layout.file(binary.linkTask.normalizedDestinationDirectory))
|
||||
|
||||
task.from(project.tasks.named(compilation.processResourcesTaskName))
|
||||
|
||||
@@ -163,6 +165,30 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerTypeScriptCheckTask(binary: JsIrBinary): TaskProvider<TypeScriptValidationTask>? {
|
||||
val linkTask = binary.linkTask
|
||||
val compilation = binary.compilation
|
||||
return if (compilation.name == KotlinCompilation.TEST_COMPILATION_NAME) {
|
||||
null
|
||||
} else {
|
||||
project.registerTask(binary.validateGeneratedTsTaskName, listOf(compilation)) {
|
||||
it.inputDir.set(project.layout.dir(linkTask.normalizedDestinationDirectory))
|
||||
it.validationStrategy.set(
|
||||
when (binary.mode) {
|
||||
KotlinJsBinaryMode.DEVELOPMENT -> propertiesProvider.jsIrGeneratedTypeScriptValidationDevStrategy
|
||||
KotlinJsBinaryMode.PRODUCTION -> propertiesProvider.jsIrGeneratedTypeScriptValidationProdStrategy
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val TaskProvider<KotlinJsIrLink>.normalizedDestinationDirectory
|
||||
get() =
|
||||
flatMap { linkTask ->
|
||||
linkTask.normalizedDestinationDirectory.map { it.asFile }
|
||||
}
|
||||
|
||||
//Binaryen
|
||||
private val applyBinaryenHandlers = mutableListOf<(BinaryenExec.() -> Unit) -> Unit>()
|
||||
|
||||
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.gradle.targets.js.typescript
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
import org.jetbrains.kotlin.gradle.internal.execWithProgress
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
|
||||
import org.jetbrains.kotlin.gradle.targets.js.RequiredKotlinJsDependency
|
||||
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinIrJsGeneratedTSValidationStrategy
|
||||
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin
|
||||
import org.jetbrains.kotlin.gradle.targets.js.npm.RequiresNpmDependencies
|
||||
import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class TypeScriptValidationTask
|
||||
@Inject
|
||||
constructor(
|
||||
@Internal
|
||||
@Transient
|
||||
override val compilation: KotlinJsCompilation
|
||||
) : DefaultTask(), RequiresNpmDependencies {
|
||||
private val npmProject = compilation.npmProject
|
||||
|
||||
@get:Internal
|
||||
@Transient
|
||||
protected val nodeJs = NodeJsRootPlugin.apply(project.rootProject)
|
||||
|
||||
@get:Internal
|
||||
override val nodeModulesRequired: Boolean get() = false
|
||||
|
||||
@get:Internal
|
||||
override val requiredNpmDependencies: Set<RequiredKotlinJsDependency>
|
||||
get() = setOf(nodeJs.versions.typeScript)
|
||||
|
||||
@get:SkipWhenEmpty
|
||||
@get:InputDirectory
|
||||
@get:PathSensitive(PathSensitivity.RELATIVE)
|
||||
abstract val inputDir: DirectoryProperty
|
||||
|
||||
@get:Input
|
||||
abstract val validationStrategy: Property<KotlinIrJsGeneratedTSValidationStrategy>
|
||||
|
||||
private val generatedDts
|
||||
get() = inputDir.asFileTree.matching { it.include("*.d.ts") }.files
|
||||
|
||||
@TaskAction
|
||||
fun run() {
|
||||
val validationStrategy = validationStrategy.get()
|
||||
|
||||
if (validationStrategy == KotlinIrJsGeneratedTSValidationStrategy.IGNORE) return
|
||||
|
||||
val files = generatedDts.map { it.absolutePath }
|
||||
|
||||
if (files.isEmpty()) return
|
||||
|
||||
val result = services.execWithProgress("typescript") {
|
||||
npmProject.useTool(it, "typescript/bin/tsc", listOf(), listOf("--noEmit"))
|
||||
}
|
||||
|
||||
if (result.exitValue == 0) return
|
||||
|
||||
val message = "Oops, Kotlin/JS compiler generated invalid d.ts files."
|
||||
|
||||
when (validationStrategy) {
|
||||
KotlinIrJsGeneratedTSValidationStrategy.ERROR -> error(message)
|
||||
KotlinIrJsGeneratedTSValidationStrategy.IGNORE -> {}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val NAME: String = "validateGeneratedByCompilerTypeScript"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user