Remove conditional-preprocessor compiler module
This commit is contained in:
@@ -178,7 +178,6 @@ extra["nativePlatformVariants"] =
|
||||
extra["compilerModules"] = arrayOf(
|
||||
":compiler:util",
|
||||
":compiler:container",
|
||||
":compiler:conditional-preprocessor",
|
||||
":compiler:resolution",
|
||||
":compiler:serialization",
|
||||
":compiler:psi",
|
||||
|
||||
@@ -177,7 +177,6 @@ extra["nativePlatformVariants"] =
|
||||
extra["compilerModules"] = arrayOf(
|
||||
":compiler:util",
|
||||
":compiler:container",
|
||||
":compiler:conditional-preprocessor",
|
||||
":compiler:resolution",
|
||||
":compiler:serialization",
|
||||
":compiler:psi",
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
id("jps-compatible")
|
||||
}
|
||||
|
||||
jvmTarget = "1.6"
|
||||
|
||||
dependencies {
|
||||
val compile by configurations
|
||||
compile(project(":compiler:cli"))
|
||||
compile(project(":compiler:daemon-common"))
|
||||
compile(project(":compiler:incremental-compilation-impl"))
|
||||
compile(project(":kotlin-build-common"))
|
||||
compile(commonDep("org.fusesource.jansi", "jansi"))
|
||||
compile(commonDep("org.jline", "jline"))
|
||||
compileOnly(intellijCoreDep()) { includeJars("intellij-core") }
|
||||
compileOnly(intellijDep()) { includeIntellijCoreJarDependencies(project) }
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
"main" { projectDefault() }
|
||||
"test" {}
|
||||
}
|
||||
-65
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.preprocessor
|
||||
|
||||
import org.jetbrains.kotlin.psi.KtAnnotated
|
||||
import org.jetbrains.kotlin.psi.KtAnnotationEntry
|
||||
import org.jetbrains.kotlin.psi.KtUserType
|
||||
|
||||
interface Conditional {
|
||||
|
||||
interface PlatformVersion : Conditional
|
||||
|
||||
abstract class Parser(val name: String, val parse: (arguments: PositionalAndNamedArguments) -> Conditional)
|
||||
|
||||
data class JvmVersion(val minimum: Int, val maximum: Int): PlatformVersion {
|
||||
val versionRange: IntRange = minimum..maximum
|
||||
companion object : Parser("JvmVersion", parse = { arguments ->
|
||||
val minimum = arguments[0, "minimum"]?.parseIntegerValue()
|
||||
val maximum = arguments[1, "maximum"]?.parseIntegerValue()
|
||||
|
||||
JvmVersion(minimum ?: 6, maximum ?: 100)
|
||||
})
|
||||
}
|
||||
|
||||
data class JsVersion(val version: Int = 5): PlatformVersion {
|
||||
companion object : Parser("JsVersion", parse = { JsVersion() })
|
||||
}
|
||||
|
||||
data class TargetName(val name: String): Conditional {
|
||||
companion object : Parser("RenameOnTargetPlatform", parse = { arguments ->
|
||||
val name = arguments[0, "name"]!!.parseStringValue()
|
||||
TargetName(name)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
val ANNOTATIONS: Map<String, Parser> = listOf<Parser>(JvmVersion, JsVersion, TargetName).associateBy { it.name }
|
||||
}
|
||||
}
|
||||
|
||||
fun KtAnnotated.parseConditionalAnnotations(): List<Conditional> =
|
||||
annotationEntries.mapNotNull {
|
||||
val parser = Conditional.ANNOTATIONS.get(it.typeReferenceName)
|
||||
parser?.parse?.invoke(it.valueArguments.splitToPositionalAndNamed())
|
||||
}
|
||||
|
||||
|
||||
val KtAnnotationEntry.typeReferenceName: String? get() =
|
||||
(typeReference?.typeElement as? KtUserType)?.referencedName
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.preprocessor
|
||||
|
||||
interface Evaluator : (List<Conditional>) -> Boolean
|
||||
|
||||
interface PlatformEvaluator : Evaluator {
|
||||
override fun invoke(conditions: List<Conditional>): Boolean = evaluate(conditions.filterIsInstance<Conditional.PlatformVersion>())
|
||||
|
||||
fun evaluate(conditions: List<Conditional.PlatformVersion>): Boolean
|
||||
= conditions.isEmpty() || conditions.any { match(it) }
|
||||
|
||||
fun match(platformCondition: Conditional.PlatformVersion): Boolean
|
||||
}
|
||||
|
||||
data class JvmPlatformEvaluator(val version: Int): PlatformEvaluator {
|
||||
override fun match(platformCondition: Conditional.PlatformVersion)
|
||||
= platformCondition is Conditional.JvmVersion && version in platformCondition.versionRange
|
||||
override fun toString() = "platform: JVM$version"
|
||||
}
|
||||
|
||||
data class JsPlatformEvaluator(val ecmaScriptVersion: Int = 5): PlatformEvaluator {
|
||||
override fun match(platformCondition: Conditional.PlatformVersion)
|
||||
= platformCondition is Conditional.JsVersion
|
||||
override fun toString() = "platform: JS"
|
||||
}
|
||||
-72
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.preprocessor
|
||||
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
|
||||
data class Modification(val range: TextRange, val apply: (String) -> String)
|
||||
|
||||
class CollectModificationsVisitor(evaluators: List<Evaluator>) : KtTreeVisitorVoid() {
|
||||
|
||||
val elementModifications: Map<Evaluator, MutableList<Modification>> =
|
||||
evaluators.associateBy(keySelector = { it }, valueTransform = { arrayListOf<Modification>() })
|
||||
|
||||
override fun visitDeclaration(declaration: KtDeclaration) {
|
||||
super.visitDeclaration(declaration)
|
||||
|
||||
val annotations = declaration.parseConditionalAnnotations()
|
||||
val name = (declaration as? KtNamedDeclaration)?.nameAsSafeName ?: declaration.name
|
||||
|
||||
val declResults = arrayListOf<Pair<Evaluator, Boolean>>()
|
||||
for ((evaluator, modifications) in elementModifications) {
|
||||
val conditionalResult = evaluator(annotations)
|
||||
declResults.add(evaluator to conditionalResult)
|
||||
|
||||
if (!conditionalResult)
|
||||
modifications.add(Modification(declaration.textRange) { rangeText ->
|
||||
buildString {
|
||||
append("/* Not available on $evaluator */")
|
||||
repeat(StringUtil.getLineBreakCount(rangeText)) { append("\n") }
|
||||
}
|
||||
})
|
||||
else {
|
||||
val targetName = annotations.filterIsInstance<Conditional.TargetName>().singleOrNull()
|
||||
if (targetName != null) {
|
||||
val placeholderName = (declaration as KtNamedDeclaration).nameAsName!!.asString()
|
||||
val realName = targetName.name
|
||||
modifications.add(Modification(declaration.textRange) { it.replace(placeholderName, realName) })
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//println("declaration: ${declaration.javaClass.simpleName} $name${if (annotations.isNotEmpty()) ", annotations: ${annotations.joinToString { it.toString() }}, evaluation result: $declResults" else ""}")
|
||||
}
|
||||
}
|
||||
|
||||
fun List<Modification>.applyTo(sourceText: String): String {
|
||||
return buildString {
|
||||
var prevIndex = 0
|
||||
for ((range, transform) in this@applyTo) {
|
||||
append(sourceText, prevIndex, range.startOffset)
|
||||
append(transform(range.substring(sourceText)))
|
||||
prevIndex = range.endOffset
|
||||
}
|
||||
append(sourceText, prevIndex, sourceText.length)
|
||||
}
|
||||
}
|
||||
-163
@@ -1,163 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.preprocessor
|
||||
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
|
||||
|
||||
|
||||
data class Profile(val name: String, val evaluator: Evaluator, val targetRoot: File)
|
||||
|
||||
fun createJvmProfile(targetRoot: File, version: Int): Profile = Profile("JVM$version", JvmPlatformEvaluator(version), File(targetRoot, "jvm$version"))
|
||||
fun createJsProfile(targetRoot: File): Profile = Profile("JS", JsPlatformEvaluator(), File(targetRoot, "js"))
|
||||
|
||||
val profileEvaluators: Map<String, () -> Evaluator> =
|
||||
listOf(6, 7, 8)
|
||||
.associateBy({ version -> "JVM$version" }, { version -> { JvmPlatformEvaluator(version) } })
|
||||
.plus<String, () -> PlatformEvaluator>(("JS" to { JsPlatformEvaluator() }))
|
||||
|
||||
fun createProfile(name: String, targetRoot: File): Profile {
|
||||
val (profileName, evaluator) = profileEvaluators.entries.firstOrNull { it.key.equals(name, ignoreCase = true) } ?: throw IllegalArgumentException("Profile with name '$name' is not supported")
|
||||
return Profile(profileName, evaluator(), targetRoot)
|
||||
}
|
||||
|
||||
|
||||
class Preprocessor(val logger: Logger = SystemOutLogger) {
|
||||
|
||||
val fileType = KotlinFileType.INSTANCE
|
||||
val jetPsiFactory: KtPsiFactory
|
||||
|
||||
init {
|
||||
val configuration = CompilerConfiguration()
|
||||
val environment = KotlinCoreEnvironment.createForProduction(Disposable { }, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
|
||||
|
||||
val project = environment.project
|
||||
jetPsiFactory = KtPsiFactory(project)
|
||||
}
|
||||
|
||||
sealed class FileProcessingResult {
|
||||
object Skip : FileProcessingResult()
|
||||
object Copy : FileProcessingResult()
|
||||
|
||||
class Modify(val sourceText: String, val modifications: List<Modification>) : FileProcessingResult() {
|
||||
fun getModifiedText(): String = modifications.applyTo(sourceText)
|
||||
|
||||
override fun toString(): String = "Modify(${modifications.size})"
|
||||
}
|
||||
|
||||
override fun toString() = this::class.java.simpleName
|
||||
}
|
||||
|
||||
fun processSources(sourceRoot: File, profile: Profile) {
|
||||
processDirectorySingleEvaluator(sourceRoot, profile.targetRoot, profile.evaluator)
|
||||
}
|
||||
|
||||
private fun processFileSingleEvaluator(sourceFile: File, evaluator: Evaluator): FileProcessingResult {
|
||||
if (sourceFile.extension != fileType.defaultExtension)
|
||||
return FileProcessingResult.Copy
|
||||
|
||||
val sourceText = sourceFile.readText().convertLineSeparators()
|
||||
val psiFile = jetPsiFactory.createFile(sourceFile.name, sourceText)
|
||||
|
||||
val fileAnnotations = psiFile.parseConditionalAnnotations()
|
||||
if (!evaluator(fileAnnotations))
|
||||
return FileProcessingResult.Skip
|
||||
|
||||
|
||||
val visitor = CollectModificationsVisitor(listOf(evaluator))
|
||||
psiFile.accept(visitor)
|
||||
|
||||
val list = visitor.elementModifications.values.single()
|
||||
return if (list.isNotEmpty())
|
||||
FileProcessingResult.Modify(sourceText, list)
|
||||
else
|
||||
FileProcessingResult.Copy
|
||||
}
|
||||
|
||||
private fun processDirectorySingleEvaluator(sourceRoot: File, targetRoot: File, evaluator: Evaluator) {
|
||||
val (sourceFiles, sourceDirectories) = sourceRoot.listFiles().partition { !it.isDirectory }
|
||||
|
||||
val processedFiles = hashSetOf<File>()
|
||||
for (sourceFile in sourceFiles)
|
||||
{
|
||||
val result = processFileSingleEvaluator(sourceFile, evaluator)
|
||||
logger.debug("$result: $sourceFile")
|
||||
if (result is FileProcessingResult.Skip) {
|
||||
continue
|
||||
}
|
||||
|
||||
val targetFile = sourceFile.makeRelativeTo(sourceRoot, targetRoot)
|
||||
processedFiles += targetFile
|
||||
|
||||
if (targetFile.exists() && targetFile.isDirectory)
|
||||
targetFile.deleteRecursively()
|
||||
|
||||
// if no modifications — copy
|
||||
if (result is FileProcessingResult.Copy) {
|
||||
FileUtil.copy(sourceFile, targetFile)
|
||||
} else if (result is FileProcessingResult.Modify) {
|
||||
val resultText = result.getModifiedText()
|
||||
if (targetFile.exists() && targetFile.isTextEqualTo(resultText))
|
||||
continue
|
||||
logger.info("Rewriting modified $targetFile")
|
||||
targetFile.parentFile!!.mkdirsOrFail()
|
||||
targetFile.writeText(resultText)
|
||||
}
|
||||
}
|
||||
|
||||
for (sourceDir in sourceDirectories) {
|
||||
val targetDir = sourceDir.makeRelativeTo(sourceRoot, targetRoot)
|
||||
if (targetDir.exists() && !targetDir.isDirectory) {
|
||||
targetDir.delete()
|
||||
}
|
||||
targetDir.mkdirsOrFail()
|
||||
processDirectorySingleEvaluator(sourceDir, targetDir, evaluator)
|
||||
processedFiles += targetDir
|
||||
}
|
||||
|
||||
for (targetFile in targetRoot.listFiles()) {
|
||||
if (!processedFiles.remove(processedFiles.find { FileUtil.filesEqual(it, targetFile) })) {
|
||||
logger.info("Deleting skipped $targetFile")
|
||||
targetFile.deleteRecursively()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun String.convertLineSeparators(): String = StringUtil.convertLineSeparators(this)
|
||||
|
||||
fun File.isTextEqualTo(content: String): Boolean = readText().lines() == content.lines()
|
||||
|
||||
fun File.makeRelativeTo(from: File, to: File) = File(to, toRelativeString(from))
|
||||
|
||||
fun File.mkdirsOrFail() {
|
||||
if (!mkdirs() && !exists()) {
|
||||
throw IOException("Failed to create directory $this.")
|
||||
}
|
||||
}
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
@file:JvmName("PreprocessorCLI")
|
||||
package org.jetbrains.kotlin.preprocessor
|
||||
|
||||
import java.io.File
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
if (args.size != 3) {
|
||||
println("Usage: <path to sources> <output path> <profile>")
|
||||
System.exit(1)
|
||||
}
|
||||
|
||||
val sourcePath = File(args[0])
|
||||
val targetPath = File(args[1])
|
||||
|
||||
val profile = createProfile(args[2], targetPath)
|
||||
|
||||
println("Preprocessing sources in $sourcePath to $targetPath with profile ${profile.name}")
|
||||
Preprocessor().processSources(sourcePath, profile)
|
||||
}
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.preprocessor
|
||||
|
||||
import org.jetbrains.kotlin.psi.KtStringTemplateEntry
|
||||
import org.jetbrains.kotlin.psi.ValueArgument
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType
|
||||
|
||||
data class PositionalAndNamedArguments(val positional: List<ValueArgument>, val named: List<ValueArgument>)
|
||||
{
|
||||
operator fun get(position: Int, name: String): ValueArgument? =
|
||||
positional.getOrNull(position) ?: named.find { it.getArgumentName()!!.asName.asString() == name }
|
||||
}
|
||||
|
||||
fun List<ValueArgument>.splitToPositionalAndNamed(): PositionalAndNamedArguments {
|
||||
val (positional, named) = partition { !it.isNamed() }
|
||||
return PositionalAndNamedArguments(positional, named)
|
||||
}
|
||||
|
||||
fun ValueArgument.parseIntegerValue(): Int = getArgumentExpression()!!.text.toInt()
|
||||
fun ValueArgument.parseStringValue(): String = getArgumentExpression()!!.getChildrenOfType<KtStringTemplateEntry>().joinToString("") { it.text }
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.preprocessor
|
||||
|
||||
interface Logger {
|
||||
fun debug(msg: CharSequence)
|
||||
fun info(msg: CharSequence)
|
||||
fun warn(msg: CharSequence)
|
||||
fun error(msg: CharSequence)
|
||||
}
|
||||
|
||||
object SystemOutLogger : Logger {
|
||||
private fun out(level: String, msg: CharSequence) = println("[$level] $msg")
|
||||
|
||||
var isDebugEnabled: Boolean = true
|
||||
override fun debug(msg: CharSequence) {
|
||||
if (isDebugEnabled) out("DEBUG", msg)
|
||||
}
|
||||
override fun info(msg: CharSequence) = out("INFO", msg)
|
||||
override fun warn(msg: CharSequence) = out("WARN", msg)
|
||||
override fun error(msg: CharSequence) = out("ERROR", msg)
|
||||
}
|
||||
|
||||
fun Logger.withPrefix(prefix: String): Logger = PrefixedLogger(prefix, this)
|
||||
|
||||
class PrefixedLogger(val prefix: String, val logger: Logger) : Logger {
|
||||
private fun prefix(msg: CharSequence): CharSequence = StringBuilder().apply {
|
||||
append(prefix)
|
||||
append(": ")
|
||||
append(msg)
|
||||
}
|
||||
|
||||
override fun debug(msg: CharSequence) = logger.debug(prefix(msg))
|
||||
override fun info(msg: CharSequence) = logger.info(prefix(msg))
|
||||
override fun warn(msg: CharSequence) = logger.warn(prefix(msg))
|
||||
override fun error(msg: CharSequence) = logger.error(prefix(msg))
|
||||
}
|
||||
@@ -20,7 +20,6 @@ include ":kotlin-build-common",
|
||||
":kotlin-preloader",
|
||||
":kotlin-runner",
|
||||
":compiler:container",
|
||||
":compiler:conditional-preprocessor",
|
||||
":compiler:resolution",
|
||||
":compiler:serialization",
|
||||
":compiler:psi",
|
||||
|
||||
Reference in New Issue
Block a user