Android Extensions refactoring

This commit is contained in:
Yan Zhulanow
2015-08-18 16:31:26 +03:00
parent 2de4de4a1e
commit ad10340bd7
29 changed files with 254 additions and 241 deletions
@@ -1 +1 @@
org.jetbrains.kotlin.android.AndroidCommandLineProcessor org.jetbrains.kotlin.android.synthetic.AndroidCommandLineProcessor
@@ -1 +1 @@
org.jetbrains.kotlin.android.AndroidComponentRegistrar org.jetbrains.kotlin.android.synthetic.AndroidComponentRegistrar
@@ -14,14 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.android package org.jetbrains.kotlin.android.synthetic
import com.intellij.mock.MockProject import com.intellij.mock.MockProject
import com.intellij.openapi.components.ServiceManager import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.extensions.Extensions
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.impl.PsiTreeChangePreprocessor
import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.android.synthetic.codegen.AndroidExpressionCodegenExtension
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
import org.jetbrains.kotlin.compiler.plugin.CliOption import org.jetbrains.kotlin.compiler.plugin.CliOption
import org.jetbrains.kotlin.compiler.plugin.CliOptionProcessingException import org.jetbrains.kotlin.compiler.plugin.CliOptionProcessingException
@@ -30,7 +29,10 @@ import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.CompilerConfigurationKey import org.jetbrains.kotlin.config.CompilerConfigurationKey
import org.jetbrains.kotlin.extensions.ExternalDeclarationsProvider import org.jetbrains.kotlin.extensions.ExternalDeclarationsProvider
import org.jetbrains.kotlin.lang.resolve.android.* import org.jetbrains.kotlin.android.synthetic.res.AndroidLayoutXmlFileManager
import org.jetbrains.kotlin.android.synthetic.res.SyntheticFileGenerator
import org.jetbrains.kotlin.android.synthetic.res.CliAndroidLayoutXmlFileManager
import org.jetbrains.kotlin.android.synthetic.res.CliSyntheticFileGenerator
import org.jetbrains.kotlin.psi.JetFile import org.jetbrains.kotlin.psi.JetFile
public object AndroidConfigurationKeys { public object AndroidConfigurationKeys {
@@ -68,8 +70,8 @@ public class AndroidCommandLineProcessor : CommandLineProcessor {
public class CliAndroidDeclarationsProvider(private val project: Project) : ExternalDeclarationsProvider { public class CliAndroidDeclarationsProvider(private val project: Project) : ExternalDeclarationsProvider {
override fun getExternalDeclarations(moduleInfo: ModuleInfo?): Collection<JetFile> { override fun getExternalDeclarations(moduleInfo: ModuleInfo?): Collection<JetFile> {
val parser = ServiceManager.getService(project, javaClass<AndroidUIXmlProcessor>()) val parser = ServiceManager.getService(project, javaClass<SyntheticFileGenerator>())
return parser.parseToPsi() ?: listOf() return parser.getSyntheticFiles() ?: listOf()
} }
} }
@@ -81,11 +83,10 @@ public class AndroidComponentRegistrar : ComponentRegistrar {
val supportV4 = configuration.get(AndroidConfigurationKeys.SUPPORT_V4) ?: "false" val supportV4 = configuration.get(AndroidConfigurationKeys.SUPPORT_V4) ?: "false"
if (androidResPath != null && androidManifest != null) { if (androidResPath != null && androidManifest != null) {
val xmlProcessor = CliAndroidUIXmlProcessor(project, androidManifest, androidResPath) val xmlProcessor = CliSyntheticFileGenerator(project, androidManifest, androidResPath, supportV4 == "true")
if (supportV4 == "true") xmlProcessor.supportV4 = true
project.registerService(javaClass<AndroidUIXmlProcessor>(), xmlProcessor) project.registerService(javaClass<SyntheticFileGenerator>(), xmlProcessor)
project.registerService(javaClass<AndroidResourceManager>(), CliAndroidResourceManager(project, androidManifest, androidResPath)) project.registerService(javaClass<AndroidLayoutXmlFileManager>(), CliAndroidLayoutXmlFileManager(project, androidManifest, androidResPath))
ExternalDeclarationsProvider.registerExtension(project, CliAndroidDeclarationsProvider(project)) ExternalDeclarationsProvider.registerExtension(project, CliAndroidDeclarationsProvider(project))
ExpressionCodegenExtension.registerExtension(project, AndroidExpressionCodegenExtension()) ExpressionCodegenExtension.registerExtension(project, AndroidExpressionCodegenExtension())
@@ -14,9 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.lang.resolve.android package org.jetbrains.kotlin.android.synthetic
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
import org.jetbrains.kotlin.android.synthetic.res.AndroidFragment
import org.jetbrains.kotlin.android.synthetic.res.AndroidResource
import org.jetbrains.kotlin.android.synthetic.res.AndroidWidget
import org.jetbrains.kotlin.lexer.JetKeywordToken import org.jetbrains.kotlin.lexer.JetKeywordToken
import org.jetbrains.kotlin.lexer.JetTokens import org.jetbrains.kotlin.lexer.JetTokens
@@ -14,17 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.lang.resolve.android package org.jetbrains.kotlin.android.synthetic
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiElement
import com.intellij.openapi.project.Project
import com.intellij.openapi.components.ServiceManager
import com.intellij.psi.PsiField
import com.intellij.psi.PsiClass
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.util.Computable import com.intellij.openapi.util.Computable
import com.intellij.openapi.module.ModuleServiceManager import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
fun isAndroidSyntheticFile(f: PsiFile?): Boolean { fun isAndroidSyntheticFile(f: PsiFile?): Boolean {
return f?.getUserData(AndroidConst.ANDROID_USER_PACKAGE) != null return f?.getUserData(AndroidConst.ANDROID_USER_PACKAGE) != null
@@ -32,6 +27,6 @@ fun isAndroidSyntheticFile(f: PsiFile?): Boolean {
public fun isAndroidSyntheticElement(element: PsiElement?): Boolean { public fun isAndroidSyntheticElement(element: PsiElement?): Boolean {
return isAndroidSyntheticFile(ApplicationManager.getApplication().runReadAction(Computable { return isAndroidSyntheticFile(ApplicationManager.getApplication().runReadAction(Computable {
element?.getContainingFile() element?.containingFile
})) }))
} }
@@ -14,10 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.lang.resolve.android package org.jetbrains.kotlin.android.synthetic
import org.xml.sax.helpers.DefaultHandler
import org.xml.sax.Attributes import org.xml.sax.Attributes
import org.xml.sax.helpers.DefaultHandler
import java.util.HashMap import java.util.HashMap
class AndroidXmlHandler(private val elementCallback: (String, String) -> Unit) : DefaultHandler() { class AndroidXmlHandler(private val elementCallback: (String, String) -> Unit) : DefaultHandler() {
@@ -14,11 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.lang.resolve.android package org.jetbrains.kotlin.android.synthetic
import java.util.ArrayList import java.util.ArrayList
open class Context(val buffer: StringBuffer = StringBuffer(), private var indentDepth: Int = 0) { open class Context(val buffer: StringBuffer = StringBuffer(), private var indentDepth: Int = 0) {
open class InvalidIndent(num: Int) : RuntimeException("Indentation level < 0: $num") open class InvalidIndent(num: Int) : RuntimeException("Indentation level < 0: $num")
@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.lang.resolve.android package org.jetbrains.kotlin.android.synthetic
interface KotlinWriter { interface KotlinWriter {
fun toStringBuffer(): StringBuffer fun toStringBuffer(): StringBuffer
@@ -14,29 +14,24 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.android package org.jetbrains.kotlin.android.synthetic.codegen
import org.jetbrains.kotlin.codegen.ClassBuilder import org.jetbrains.kotlin.codegen.ClassBuilder
import org.jetbrains.kotlin.codegen.ClassBuilderMode
import org.jetbrains.kotlin.codegen.FunctionCodegen import org.jetbrains.kotlin.codegen.FunctionCodegen
import org.jetbrains.kotlin.codegen.StackValue import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
import org.jetbrains.kotlin.codegen.state.GenerationState import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.codegen.state.JetTypeMapper import org.jetbrains.kotlin.codegen.state.JetTypeMapper
import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.lang.resolve.android.AndroidConst import org.jetbrains.kotlin.android.synthetic.AndroidConst
import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaClassDescriptor import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaClassDescriptor
import org.jetbrains.kotlin.psi.JetClassOrObject import org.jetbrains.kotlin.psi.JetClassOrObject
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyClassDescriptor import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyClassDescriptor
import org.jetbrains.kotlin.resolve.scopes.receivers.ClassReceiver
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
import org.jetbrains.kotlin.types.Flexibility
import org.jetbrains.kotlin.types.lowerIfFlexible import org.jetbrains.kotlin.types.lowerIfFlexible
import org.jetbrains.org.objectweb.asm.Label import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.Opcodes.ACC_PRIVATE import org.jetbrains.org.objectweb.asm.Opcodes.ACC_PRIVATE
@@ -14,21 +14,20 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.lang.resolve.android package org.jetbrains.kotlin.android.synthetic.res
import com.intellij.psi.PsiFile import com.intellij.openapi.module.Module
import com.intellij.psi.PsiElement import com.intellij.openapi.module.ModuleServiceManager
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager import com.intellij.psi.PsiManager
import com.intellij.openapi.util.*
import com.intellij.openapi.vfs.impl.*
import com.intellij.openapi.vfs.*
import com.intellij.openapi.components.*
import com.intellij.openapi.extensions.*
import com.intellij.openapi.module.*
import org.jetbrains.kotlin.psi.JetProperty import org.jetbrains.kotlin.psi.JetProperty
import java.util.*
public abstract class AndroidResourceManager(val project: Project) { public abstract class AndroidLayoutXmlFileManager(val project: Project) {
public abstract val androidModuleInfo: AndroidModuleInfo? public abstract val androidModuleInfo: AndroidModuleInfo?
@@ -42,9 +41,9 @@ public abstract class AndroidResourceManager(val project: Project) {
fun VirtualFile.getAllChildren(): List<VirtualFile> { fun VirtualFile.getAllChildren(): List<VirtualFile> {
val allChildren = arrayListOf<VirtualFile>() val allChildren = arrayListOf<VirtualFile>()
val currentChildren = getChildren() ?: emptyArray() val currentChildren = children ?: emptyArray()
for (child in currentChildren) { for (child in currentChildren) {
if (child.isDirectory()) { if (child.isDirectory) {
allChildren.addAll(child.getAllChildren()) allChildren.addAll(child.getAllChildren())
} }
else { else {
@@ -57,20 +56,27 @@ public abstract class AndroidResourceManager(val project: Project) {
val resDirectories = info.resDirectories.map { fileManager.findFileByUrl("file://$it") } val resDirectories = info.resDirectories.map { fileManager.findFileByUrl("file://$it") }
val allChildren = resDirectories.flatMap { it?.getAllChildren() ?: listOf() } val allChildren = resDirectories.flatMap { it?.getAllChildren() ?: listOf() }
return allChildren val allLayoutFiles = allChildren.filter { it.parent.name.startsWith("layout") && it.name.toLowerCase().endsWith(".xml") }
.filter { it.getParent().getName().startsWith("layout") && it.getName().toLowerCase().endsWith(".xml") } val allLayoutPsiFiles = allLayoutFiles.fold(ArrayList<PsiFile>(allLayoutFiles.size())) { list, file ->
.map { psiManager.findFile(it) } val psiFile = psiManager.findFile(file)
.filterNotNull() if (psiFile != null && psiFile.parent != null) {
.groupBy { it.getName().substringBeforeLast('.') } list += psiFile
.mapValues { it.getValue().sortBy { it.getParent()!!.getName().length() } } }
list
}
val layoutNameToXmls = allLayoutPsiFiles
.groupBy { it.name.substringBeforeLast('.') }
.mapValues { it.getValue().sortBy { it.parent!!.name.length() } }
return layoutNameToXmls
} }
companion object { companion object {
public fun getInstance(module: Module): AndroidResourceManager { public fun getInstance(module: Module): AndroidLayoutXmlFileManager {
val service = ModuleServiceManager.getService(module, javaClass<AndroidResourceManager>()) val service = ModuleServiceManager.getService(module, javaClass<AndroidLayoutXmlFileManager>())
return service ?: module.getComponent(javaClass<AndroidResourceManager>())!! return service ?: module.getComponent(javaClass<AndroidLayoutXmlFileManager>())!!
} }
} }
} }
@@ -14,9 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.lang.resolve.android package org.jetbrains.kotlin.android.synthetic.res
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.android.synthetic.toMap
import org.xml.sax.Attributes import org.xml.sax.Attributes
import org.xml.sax.helpers.DefaultHandler import org.xml.sax.helpers.DefaultHandler
import java.io.File import java.io.File
@@ -24,11 +25,11 @@ import java.io.FileInputStream
import javax.xml.parsers.SAXParser import javax.xml.parsers.SAXParser
import javax.xml.parsers.SAXParserFactory import javax.xml.parsers.SAXParserFactory
public class CliAndroidResourceManager( public class CliAndroidLayoutXmlFileManager(
project: Project, project: Project,
private val manifestPath: String, private val manifestPath: String,
private val resDirectories: List<String> private val resDirectories: List<String>
) : AndroidResourceManager(project) { ) : AndroidLayoutXmlFileManager(project) {
override val androidModuleInfo by lazy { override val androidModuleInfo by lazy {
AndroidModuleInfo(getApplicationPackage(manifestPath), resDirectories) AndroidModuleInfo(getApplicationPackage(manifestPath), resDirectories)
@@ -60,7 +61,7 @@ public class CliAndroidResourceManager(
return applicationPackage return applicationPackage
} }
catch (e: Exception) { catch (e: Exception) {
throw AndroidUIXmlProcessor.NoAndroidManifestFound() throw SyntheticFileGenerator.NoAndroidManifestFound()
} }
} }
@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.lang.resolve.android package org.jetbrains.kotlin.android.synthetic.res
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.ModificationTracker import com.intellij.openapi.util.ModificationTracker
@@ -22,33 +22,41 @@ import com.intellij.psi.PsiFile
import com.intellij.psi.util.CachedValue import com.intellij.psi.util.CachedValue
import com.intellij.psi.util.CachedValueProvider.Result import com.intellij.psi.util.CachedValueProvider.Result
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import org.jetbrains.kotlin.android.synthetic.AndroidXmlHandler
import org.jetbrains.kotlin.android.synthetic.parseAndroidResource
import kotlin.properties.Delegates
public class CliAndroidUIXmlProcessor( public class CliSyntheticFileGenerator(
project: Project, project: Project,
private val manifestPath: String, private val manifestPath: String,
private val resDirectories: List<String> private val resDirectories: List<String>,
) : AndroidUIXmlProcessor(project) { private val supportV4: Boolean
) : SyntheticFileGenerator(project) {
override val resourceManager: CliAndroidResourceManager by lazy { override fun supportV4(): Boolean {
CliAndroidResourceManager(project, manifestPath, resDirectories) return supportV4
}
override val layoutXmlFileManager: CliAndroidLayoutXmlFileManager by Delegates.lazy {
CliAndroidLayoutXmlFileManager(project, manifestPath, resDirectories)
} }
override val cachedSources: CachedValue<List<AndroidSyntheticFile>> by lazy { override val cachedSources: CachedValue<List<AndroidSyntheticFile>> by lazy {
cachedValue { cachedValue {
Result.create(parse(), ModificationTracker.NEVER_CHANGED) Result.create(generateSyntheticFiles(), ModificationTracker.NEVER_CHANGED)
} }
} }
override fun parseLayout(files: List<PsiFile>): List<AndroidResource> { override fun extractLayoutResources(files: List<PsiFile>): List<AndroidResource> {
val resources = arrayListOf<AndroidResource>() val resources = arrayListOf<AndroidResource>()
val handler = AndroidXmlHandler { id, widgetType -> resources.add(parseAndroidResource(id, widgetType)) } val handler = AndroidXmlHandler { id, widgetType -> resources.add(parseAndroidResource(id, widgetType)) }
try { try {
for (file in files) { for (file in files) {
val inputStream = ByteArrayInputStream(file.virtualFile.contentsToByteArray()) val inputStream = ByteArrayInputStream(file.getVirtualFile().contentsToByteArray())
resourceManager.saxParser.parse(inputStream, handler) layoutXmlFileManager.saxParser.parse(inputStream, handler)
} }
return removeDuplicates(resources) return filterDuplicates(resources)
} }
catch (e: Throwable) { catch (e: Throwable) {
LOG.error(e) LOG.error(e)
@@ -14,39 +14,38 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.lang.resolve.android package org.jetbrains.kotlin.android.synthetic.res
import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager import com.intellij.psi.PsiManager
import com.intellij.psi.util.CachedValue import com.intellij.psi.util.CachedValue
import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValueProvider.Result import com.intellij.psi.util.CachedValueProvider.Result
import com.intellij.psi.util.CachedValuesManager import com.intellij.psi.util.CachedValuesManager
import com.intellij.testFramework.LightVirtualFile import com.intellij.testFramework.LightVirtualFile
import org.jetbrains.kotlin.android.synthetic.AndroidConst
import org.jetbrains.kotlin.android.synthetic.KotlinStringWriter
import org.jetbrains.kotlin.android.synthetic.escapeAndroidIdentifier
import org.jetbrains.kotlin.psi.JetFile import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.types.Flexibility import org.jetbrains.kotlin.types.Flexibility
public class AndroidSyntheticFile(val name: String, val contents: String) public class AndroidSyntheticFile(val name: String, val contents: String)
public abstract class AndroidUIXmlProcessor(protected val project: Project) { public abstract class SyntheticFileGenerator(protected val project: Project) {
public class NoAndroidManifestFound : Exception("No android manifest file found in project root") public class NoAndroidManifestFound : Exception("No android manifest file found in project root")
protected val LOG: Logger = Logger.getInstance(javaClass) protected val LOG: Logger = Logger.getInstance(javaClass)
public abstract val resourceManager: AndroidResourceManager public abstract val layoutXmlFileManager: AndroidLayoutXmlFileManager
protected abstract val cachedSources: CachedValue<List<AndroidSyntheticFile>> protected abstract val cachedSources: CachedValue<List<AndroidSyntheticFile>>
//MAKE CONSTANT (or abstract)
var supportV4 = false
private val cachedJetFiles: CachedValue<List<JetFile>> by lazy { private val cachedJetFiles: CachedValue<List<JetFile>> by lazy {
cachedValue { cachedValue {
val psiManager = PsiManager.getInstance(project) val psiManager = PsiManager.getInstance(project)
val applicationPackage = resourceManager.androidModuleInfo?.applicationPackage val applicationPackage = layoutXmlFileManager.androidModuleInfo?.applicationPackage
val jetFiles = cachedSources.value.mapIndexed { index, syntheticFile -> val jetFiles = cachedSources.value.mapIndexed { index, syntheticFile ->
val fileName = AndroidConst.SYNTHETIC_FILENAME_PREFIX + syntheticFile.name + ".kt" val fileName = AndroidConst.SYNTHETIC_FILENAME_PREFIX + syntheticFile.name + ".kt"
@@ -62,15 +61,18 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) {
} }
} }
public fun parse(generateCommonFiles: Boolean = true): List<AndroidSyntheticFile> { protected abstract fun supportV4(): Boolean
public fun generateSyntheticFiles(generateCommonFiles: Boolean = true): List<AndroidSyntheticFile> {
val commonFiles = if (generateCommonFiles) { val commonFiles = if (generateCommonFiles) {
val clearCacheFile = renderSyntheticFile("clearCache") { val renderSyntheticFile = renderSyntheticFile("clearCache") {
writePackage(AndroidConst.SYNTHETIC_PACKAGE) writePackage(AndroidConst.SYNTHETIC_PACKAGE)
writeAndroidImports() writeAndroidImports()
writeClearCacheFunction(AndroidConst.ACTIVITY_FQNAME) writeClearCacheFunction(AndroidConst.ACTIVITY_FQNAME)
writeClearCacheFunction(AndroidConst.FRAGMENT_FQNAME) writeClearCacheFunction(AndroidConst.FRAGMENT_FQNAME)
if (supportV4) writeClearCacheFunction(AndroidConst.SUPPORT_FRAGMENT_FQNAME) if (supportV4()) writeClearCacheFunction(AndroidConst.SUPPORT_FRAGMENT_FQNAME)
} }
val clearCacheFile = renderSyntheticFile
listOf(clearCacheFile, listOf(clearCacheFile,
FLEXIBLE_TYPE_FILE, FLEXIBLE_TYPE_FILE,
@@ -79,9 +81,9 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) {
FAKE_SUPPORT_V4_WIDGET_FILE) FAKE_SUPPORT_V4_WIDGET_FILE)
} else listOf() } else listOf()
return resourceManager.getLayoutXmlFiles().flatMap { entry -> return layoutXmlFileManager.getLayoutXmlFiles().flatMap { entry ->
val files = entry.getValue() val files = entry.getValue()
val resources = parseLayout(files) val resources = extractLayoutResources(files)
val layoutName = files[0].name.substringBefore('.') val layoutName = files[0].name.substringBefore('.')
@@ -92,15 +94,15 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) {
}.filterNotNull() + commonFiles }.filterNotNull() + commonFiles
} }
public fun parseToPsi(): List<JetFile>? = cachedJetFiles.value public fun getSyntheticFiles(): List<JetFile>? = cachedJetFiles.value
protected abstract fun parseLayout(files: List<PsiFile>): List<AndroidResource> protected abstract fun extractLayoutResources(files: List<PsiFile>): List<AndroidResource>
private fun renderMainLayoutFile(layoutName: String, resources: List<AndroidResource>): AndroidSyntheticFile { private fun renderMainLayoutFile(layoutName: String, resources: List<AndroidResource>): AndroidSyntheticFile {
return renderLayoutFile(layoutName + AndroidConst.LAYOUT_POSTFIX, return renderLayoutFile(layoutName + AndroidConst.LAYOUT_POSTFIX,
escapeAndroidIdentifier(layoutName), resources) { escapeAndroidIdentifier(layoutName), resources) {
val properties = it.mainProperties.toArrayList() val properties = it.mainProperties.toArrayList()
if (supportV4) properties.addAll(it.mainPropertiesForSupportV4) if (supportV4()) properties.addAll(it.mainPropertiesForSupportV4)
properties properties
} }
} }
@@ -153,7 +155,7 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) {
writeText("public fun $receiver.${AndroidConst.CLEAR_FUNCTION_NAME}() {}\n") writeText("public fun $receiver.${AndroidConst.CLEAR_FUNCTION_NAME}() {}\n")
} }
protected fun <T> cachedValue(result: () -> CachedValueProvider.Result<T>): CachedValue<T> { protected fun <T> cachedValue(result: () -> Result<T>): CachedValue<T> {
return CachedValuesManager.getManager(project).createCachedValue(result, false) return CachedValuesManager.getManager(project).createCachedValue(result, false)
} }
@@ -161,7 +163,7 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) {
return fqName.startsWith(AndroidConst.SUPPORT_V4_PACKAGE) return fqName.startsWith(AndroidConst.SUPPORT_V4_PACKAGE)
} }
protected fun removeDuplicates(resources: List<AndroidResource>): List<AndroidResource> { protected fun filterDuplicates(resources: List<AndroidResource>): List<AndroidResource> {
val resourceMap = linkedMapOf<String, AndroidResource>() val resourceMap = linkedMapOf<String, AndroidResource>()
val resourcesToExclude = hashSetOf<String>() val resourcesToExclude = hashSetOf<String>()
@@ -14,7 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.lang.resolve.android package org.jetbrains.kotlin.android.synthetic.res
import org.jetbrains.kotlin.android.synthetic.AndroidConst
public data class AndroidModuleInfo(val applicationPackage: String, resDirectories: List<String>) { public data class AndroidModuleInfo(val applicationPackage: String, resDirectories: List<String>) {
val resDirectories = resDirectories.sort() val resDirectories = resDirectories.sort()
@@ -40,7 +42,7 @@ public class AndroidWidget(id: String, override val className: String) : Android
val MAIN_PROPERTIES_SUPPORT_V4 = listOf(AndroidConst.SUPPORT_FRAGMENT_FQNAME to "getView().findViewById(0)") val MAIN_PROPERTIES_SUPPORT_V4 = listOf(AndroidConst.SUPPORT_FRAGMENT_FQNAME to "getView().findViewById(0)")
val VIEW_PROPERTIES = listOf("android.view.View" to "findViewById(0)") val VIEW_PROPERTIES = listOf(AndroidConst.VIEW_FQNAME to "findViewById(0)")
} }
override val mainProperties = MAIN_PROPERTIES override val mainProperties = MAIN_PROPERTIES
@@ -23,9 +23,9 @@ import org.jetbrains.kotlin.test.JetTestUtils
import org.jetbrains.kotlin.test.ConfigurationKind import org.jetbrains.kotlin.test.ConfigurationKind
import org.jetbrains.kotlin.test.TestJdkKind import org.jetbrains.kotlin.test.TestJdkKind
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.lang.resolve.android.AndroidConst import org.jetbrains.kotlin.android.synthetic.AndroidConst
import org.jetbrains.kotlin.lang.resolve.android.CliAndroidUIXmlProcessor import org.jetbrains.kotlin.android.synthetic.res.CliSyntheticFileGenerator
import org.jetbrains.kotlin.lang.resolve.android.AndroidUIXmlProcessor import org.jetbrains.kotlin.android.synthetic.res.SyntheticFileGenerator
import kotlin.test.* import kotlin.test.*
public abstract class AbstractAndroidXml2KConversionTest : UsefulTestCase() { public abstract class AbstractAndroidXml2KConversionTest : UsefulTestCase() {
@@ -35,10 +35,10 @@ public abstract class AbstractAndroidXml2KConversionTest : UsefulTestCase() {
val jetCoreEnvironment = getEnvironment() val jetCoreEnvironment = getEnvironment()
val layoutPaths = getResPaths(path) val layoutPaths = getResPaths(path)
val parser = CliAndroidUIXmlProcessor(jetCoreEnvironment.project, path + "AndroidManifest.xml", layoutPaths) val parser = CliSyntheticFileGenerator(jetCoreEnvironment.project, path + "AndroidManifest.xml", layoutPaths)
parser.supportV4 = testDirectory.name.startsWith("support") parser.supportV4 = testDirectory.name.startsWith("support")
val actual = parser.parse(false).toMap { it.name } val actual = parser.generateSyntheticFiles(false).toMap { it.name }
val expectedLayoutFiles = testDirectory.listFiles { val expectedLayoutFiles = testDirectory.listFiles {
it.isFile() && it.name.endsWith(".kt") it.isFile() && it.name.endsWith(".kt")
@@ -58,7 +58,7 @@ public abstract class AbstractAndroidXml2KConversionTest : UsefulTestCase() {
doTest(path) doTest(path)
fail("NoAndroidManifestFound not thrown") fail("NoAndroidManifestFound not thrown")
} }
catch (e: AndroidUIXmlProcessor.NoAndroidManifestFound) { catch (e: SyntheticFileGenerator.NoAndroidManifestFound) {
} }
} }
@@ -20,16 +20,16 @@ import com.intellij.openapi.extensions.Extensions
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.impl.PsiTreeChangePreprocessor import com.intellij.psi.impl.PsiTreeChangePreprocessor
import org.jetbrains.kotlin.extensions.ExternalDeclarationsProvider import org.jetbrains.kotlin.extensions.ExternalDeclarationsProvider
import org.jetbrains.kotlin.android.AndroidConfigurationKeys import org.jetbrains.kotlin.android.synthetic.AndroidConfigurationKeys
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
import org.jetbrains.kotlin.android.AndroidExpressionCodegenExtension import org.jetbrains.kotlin.android.synthetic.codegen.AndroidExpressionCodegenExtension
import com.intellij.testFramework.UsefulTestCase import com.intellij.testFramework.UsefulTestCase
import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.psi.JetFile import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.lang.resolve.android.CliAndroidUIXmlProcessor import org.jetbrains.kotlin.android.synthetic.res.CliSyntheticFileGenerator
import java.io.File import java.io.File
private class AndroidTestExternalDeclarationsProvider( private class AndroidTestExternalDeclarationsProvider(
@@ -39,9 +39,9 @@ private class AndroidTestExternalDeclarationsProvider(
val supportV4: Boolean val supportV4: Boolean
) : ExternalDeclarationsProvider { ) : ExternalDeclarationsProvider {
override fun getExternalDeclarations(moduleInfo: ModuleInfo?): Collection<JetFile> { override fun getExternalDeclarations(moduleInfo: ModuleInfo?): Collection<JetFile> {
val parser = CliAndroidUIXmlProcessor(project, manifestPath, resPaths) val parser = CliSyntheticFileGenerator(project, manifestPath, resPaths)
parser.supportV4 = supportV4 parser.supportV4 = supportV4
return parser.parseToPsi() ?: listOf() return parser.getSyntheticFiles() ?: listOf()
} }
} }
@@ -12,22 +12,22 @@
<depends optional="false">org.jetbrains.android</depends> <depends optional="false">org.jetbrains.android</depends>
<extensions defaultExtensionNs="com.intellij"> <extensions defaultExtensionNs="com.intellij">
<moduleService serviceInterface="org.jetbrains.kotlin.lang.resolve.android.AndroidUIXmlProcessor" <moduleService serviceInterface="org.jetbrains.kotlin.android.synthetic.res.SyntheticFileGenerator"
serviceImplementation="org.jetbrains.kotlin.plugin.android.IDEAndroidUIXmlProcessor"/> serviceImplementation="org.jetbrains.kotlin.android.synthetic.idea.res.IDESyntheticFileGenerator"/>
<moduleService serviceInterface="org.jetbrains.kotlin.lang.resolve.android.AndroidResourceManager" <moduleService serviceInterface="org.jetbrains.kotlin.android.synthetic.res.AndroidLayoutXmlFileManager"
serviceImplementation="org.jetbrains.kotlin.plugin.android.IDEAndroidResourceManager"/> serviceImplementation="org.jetbrains.kotlin.android.synthetic.idea.res.IDEAndroidLayoutXmlFileManager"/>
<compileServer.plugin classpath="jps/kotlin-android-extensions-jps.jar;android-compiler-plugin.jar"/> <compileServer.plugin classpath="jps/kotlin-android-extensions-jps.jar;android-compiler-plugin.jar"/>
<gotoDeclarationHandler implementation="org.jetbrains.kotlin.plugin.android.AndroidGotoDeclarationHandler"/> <gotoDeclarationHandler implementation="org.jetbrains.kotlin.android.synthetic.idea.AndroidGotoDeclarationHandler"/>
<psi.treeChangePreprocessor implementation="org.jetbrains.kotlin.plugin.android.AndroidPsiTreeChangePreprocessor"/> <psi.treeChangePreprocessor implementation="org.jetbrains.kotlin.android.synthetic.idea.AndroidPsiTreeChangePreprocessor"/>
<renamePsiElementProcessor id="KotlinAndroidSyntheticProperty" <renamePsiElementProcessor id="KotlinAndroidSyntheticProperty"
implementation="org.jetbrains.kotlin.plugin.android.AndroidRenameProcessor" implementation="org.jetbrains.kotlin.android.synthetic.idea.AndroidRenameProcessor"
order="first"/> order="first"/>
</extensions> </extensions>
<extensions defaultExtensionNs="org.jetbrains.kotlin"> <extensions defaultExtensionNs="org.jetbrains.kotlin">
<externalDeclarationsProvider implementation="org.jetbrains.kotlin.plugin.android.IDEAndroidExternalDeclarationsProvider"/> <externalDeclarationsProvider implementation="org.jetbrains.kotlin.android.synthetic.idea.IDEAndroidExternalDeclarationsProvider"/>
<expressionCodegenExtension implementation="org.jetbrains.kotlin.android.AndroidExpressionCodegenExtension"/> <expressionCodegenExtension implementation="org.jetbrains.kotlin.android.synthetic.codegen.AndroidExpressionCodegenExtension"/>
<findUsagesHandlerDecorator implementation="org.jetbrains.kotlin.plugin.android.AndroidFindUsageHandlerDecorator"/> <findUsagesHandlerDecorator implementation="org.jetbrains.kotlin.android.synthetic.idea.AndroidFindUsageHandlerDecorator"/>
<simpleNameReferenceExtension implementation="org.jetbrains.kotlin.plugin.android.AndroidSimpleNameReferenceExtension"/> <simpleNameReferenceExtension implementation="org.jetbrains.kotlin.android.synthetic.idea.AndroidSimpleNameReferenceExtension"/>
</extensions> </extensions>
</idea-plugin> </idea-plugin>
@@ -14,28 +14,27 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.plugin.android package org.jetbrains.kotlin.android.synthetic.idea
import com.intellij.find.findUsages.FindUsagesHandler import com.intellij.find.findUsages.FindUsagesHandler
import com.intellij.find.findUsages.FindUsagesOptions
import com.intellij.find.findUsages.JavaVariableFindUsagesOptions
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.module.ModuleServiceManager
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.xml.XmlAttribute
import com.intellij.usageView.UsageInfo import com.intellij.usageView.UsageInfo
import com.intellij.util.Processor import com.intellij.util.Processor
import com.intellij.find.findUsages.FindUsagesOptions
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.components.ServiceManager
import com.intellij.psi.xml.XmlAttribute
import org.jetbrains.android.util.AndroidResourceUtil import org.jetbrains.android.util.AndroidResourceUtil
import java.util.ArrayList import org.jetbrains.kotlin.android.synthetic.isAndroidSyntheticElement
import com.intellij.find.findUsages.JavaVariableFindUsagesOptions import org.jetbrains.kotlin.android.synthetic.res.SyntheticFileGenerator
import com.intellij.openapi.module.ModuleServiceManager
import org.jetbrains.kotlin.idea.caches.resolve.ModuleSourceInfo import org.jetbrains.kotlin.idea.caches.resolve.ModuleSourceInfo
import org.jetbrains.kotlin.idea.caches.resolve.getModuleInfo import org.jetbrains.kotlin.idea.caches.resolve.getModuleInfo
import org.jetbrains.kotlin.plugin.findUsages.handlers.KotlinFindUsagesHandlerDecorator import org.jetbrains.kotlin.plugin.findUsages.handlers.KotlinFindUsagesHandlerDecorator
import org.jetbrains.kotlin.psi.JetNamedDeclaration import org.jetbrains.kotlin.psi.JetNamedDeclaration
import org.jetbrains.kotlin.psi.JetProperty import org.jetbrains.kotlin.psi.JetProperty
import org.jetbrains.kotlin.idea.util.application.runReadAction import java.util.ArrayList
import org.jetbrains.kotlin.lang.resolve.android.isAndroidSyntheticElement
import org.jetbrains.kotlin.lang.resolve.android.AndroidUIXmlProcessor
class AndroidFindUsageHandlerDecorator : KotlinFindUsagesHandlerDecorator { class AndroidFindUsageHandlerDecorator : KotlinFindUsagesHandlerDecorator {
override fun decorateHandler(element: PsiElement, forHighlightUsages: Boolean, delegate: FindUsagesHandler): FindUsagesHandler { override fun decorateHandler(element: PsiElement, forHighlightUsages: Boolean, delegate: FindUsagesHandler): FindUsagesHandler {
@@ -56,9 +55,9 @@ class AndroidFindMemberUsagesHandler(
val property = declaration as JetProperty val property = declaration as JetProperty
val moduleInfo = declaration.getModuleInfo() as? ModuleSourceInfo ?: return super.getPrimaryElements() val moduleInfo = declaration.getModuleInfo() as? ModuleSourceInfo ?: return super.getPrimaryElements()
val parser = ModuleServiceManager.getService(moduleInfo.module, javaClass<AndroidUIXmlProcessor>()) val parser = ModuleServiceManager.getService(moduleInfo.module, javaClass<SyntheticFileGenerator>())
val psiElements = parser?.resourceManager?.propertyToXmlAttributes(property) val psiElements = parser?.layoutXmlFileManager?.propertyToXmlAttributes(property)
val valueElements = psiElements?.map { (it as? XmlAttribute)?.getValueElement() as? PsiElement }?.filterNotNull() val valueElements = psiElements?.map { (it as? XmlAttribute)?.getValueElement() as? PsiElement }?.filterNotNull()
if (valueElements != null && valueElements.isNotEmpty()) return valueElements.toTypedArray() if (valueElements != null && valueElements.isNotEmpty()) return valueElements.toTypedArray()
@@ -74,9 +73,9 @@ class AndroidFindMemberUsagesHandler(
val property = declaration as JetProperty val property = declaration as JetProperty
val moduleInfo = declaration.getModuleInfo() as? ModuleSourceInfo ?: return super.getPrimaryElements() val moduleInfo = declaration.getModuleInfo() as? ModuleSourceInfo ?: return super.getPrimaryElements()
val parser = ModuleServiceManager.getService(moduleInfo.module, javaClass<AndroidUIXmlProcessor>()) val parser = ModuleServiceManager.getService(moduleInfo.module, javaClass<SyntheticFileGenerator>())
val psiElements = parser?.resourceManager?.propertyToXmlAttributes(property) ?: listOf() val psiElements = parser?.layoutXmlFileManager?.propertyToXmlAttributes(property) ?: listOf()
val res = ArrayList<PsiElement>() val res = ArrayList<PsiElement>()
for (psiElement in psiElements) { for (psiElement in psiElements) {
@@ -97,7 +96,7 @@ class AndroidFindMemberUsagesHandler(
override fun processElementUsages(element: PsiElement, processor: Processor<UsageInfo>, options: FindUsagesOptions): Boolean { override fun processElementUsages(element: PsiElement, processor: Processor<UsageInfo>, options: FindUsagesOptions): Boolean {
assert(isAndroidSyntheticElement(declaration)) assert(isAndroidSyntheticElement(declaration))
val findUsagesOptions = JavaVariableFindUsagesOptions(runReadAction { element.getProject() }) val findUsagesOptions = JavaVariableFindUsagesOptions(runReadAction { element.project })
findUsagesOptions.isSearchForTextOccurrences = false findUsagesOptions.isSearchForTextOccurrences = false
findUsagesOptions.isSkipImportStatements = true findUsagesOptions.isSkipImportStatements = true
findUsagesOptions.isUsages = true findUsagesOptions.isUsages = true
@@ -105,4 +104,9 @@ class AndroidFindMemberUsagesHandler(
findUsagesOptions.isWriteAccess = true findUsagesOptions.isWriteAccess = true
return super.processElementUsages(element, processor, findUsagesOptions) return super.processElementUsages(element, processor, findUsagesOptions)
} }
// Android extensions plugin has it's own runtime -> different function classes
private fun runReadAction<T>(action: () -> T): T {
return ApplicationManager.getApplication().runReadAction<T>(action)
}
} }
@@ -14,21 +14,21 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.plugin.android package org.jetbrains.kotlin.android.synthetic.idea
import org.jetbrains.kotlin.idea.caches.resolve.getModuleInfo
import com.intellij.codeInsight.navigation.actions.GotoDeclarationHandler import com.intellij.codeInsight.navigation.actions.GotoDeclarationHandler
import com.intellij.psi.PsiElement
import com.intellij.openapi.editor.Editor
import com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.psi.JetSimpleNameExpression
import org.jetbrains.kotlin.psi.JetProperty
import org.jetbrains.kotlin.idea.caches.resolve.ModuleSourceInfo
import com.intellij.psi.xml.XmlAttribute
import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.actionSystem.DataContext
import org.jetbrains.kotlin.idea.references.JetSimpleNameReference import com.intellij.openapi.editor.Editor
import com.intellij.openapi.module.ModuleServiceManager import com.intellij.openapi.module.ModuleServiceManager
import org.jetbrains.kotlin.lang.resolve.android.AndroidUIXmlProcessor import com.intellij.psi.PsiElement
import com.intellij.psi.impl.source.tree.LeafPsiElement
import com.intellij.psi.xml.XmlAttribute
import org.jetbrains.kotlin.android.synthetic.res.SyntheticFileGenerator
import org.jetbrains.kotlin.idea.caches.resolve.ModuleSourceInfo
import org.jetbrains.kotlin.idea.caches.resolve.getModuleInfo
import org.jetbrains.kotlin.idea.references.JetSimpleNameReference
import org.jetbrains.kotlin.psi.JetProperty
import org.jetbrains.kotlin.psi.JetSimpleNameExpression
public class AndroidGotoDeclarationHandler : GotoDeclarationHandler { public class AndroidGotoDeclarationHandler : GotoDeclarationHandler {
@@ -40,8 +40,8 @@ public class AndroidGotoDeclarationHandler : GotoDeclarationHandler {
val moduleInfo = sourceElement.getModuleInfo() val moduleInfo = sourceElement.getModuleInfo()
if (moduleInfo !is ModuleSourceInfo) return null if (moduleInfo !is ModuleSourceInfo) return null
val parser = ModuleServiceManager.getService(moduleInfo.module, javaClass<AndroidUIXmlProcessor>())!! val parser = ModuleServiceManager.getService(moduleInfo.module, javaClass<SyntheticFileGenerator>())!!
val psiElements = parser.resourceManager.propertyToXmlAttributes(property) val psiElements = parser.layoutXmlFileManager.propertyToXmlAttributes(property)
val valueElements = psiElements.map { (it as? XmlAttribute)?.getValueElement() as? PsiElement }.filterNotNull() val valueElements = psiElements.map { (it as? XmlAttribute)?.getValueElement() as? PsiElement }.filterNotNull()
if (valueElements.isNotEmpty()) return valueElements.toTypedArray() if (valueElements.isNotEmpty()) return valueElements.toTypedArray()
} }
@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.plugin.android package org.jetbrains.kotlin.android.synthetic.idea
import com.intellij.ide.highlighter.XmlFileType import com.intellij.ide.highlighter.XmlFileType
import com.intellij.openapi.roots.ProjectRootManager import com.intellij.openapi.roots.ProjectRootManager
@@ -25,7 +25,7 @@ import com.intellij.psi.PsiFile
import com.intellij.psi.impl.PsiTreeChangeEventImpl import com.intellij.psi.impl.PsiTreeChangeEventImpl
import com.intellij.psi.impl.PsiTreeChangePreprocessor import com.intellij.psi.impl.PsiTreeChangePreprocessor
import com.intellij.psi.xml.XmlFile import com.intellij.psi.xml.XmlFile
import org.jetbrains.kotlin.lang.resolve.android.AndroidResourceManager import org.jetbrains.kotlin.android.synthetic.res.AndroidLayoutXmlFileManager
public class AndroidPsiTreeChangePreprocessor : PsiTreeChangePreprocessor, SimpleModificationTracker() { public class AndroidPsiTreeChangePreprocessor : PsiTreeChangePreprocessor, SimpleModificationTracker() {
@@ -47,7 +47,7 @@ public class AndroidPsiTreeChangePreprocessor : PsiTreeChangePreprocessor, Simpl
val projectFileIndex = ProjectRootManager.getInstance(project).getFileIndex() val projectFileIndex = ProjectRootManager.getInstance(project).getFileIndex()
val module = projectFileIndex.getModuleForFile(file.getVirtualFile()) val module = projectFileIndex.getModuleForFile(file.getVirtualFile())
if (module != null) { if (module != null) {
val resourceManager = AndroidResourceManager.getInstance(module) val resourceManager = AndroidLayoutXmlFileManager.getInstance(module)
val resDirectories = resourceManager.getModuleResDirectories() val resDirectories = resourceManager.getModuleResDirectories()
val baseDirectory = file.getParent()?.getParent()?.getVirtualFile() val baseDirectory = file.getParent()?.getParent()?.getVirtualFile()
@@ -60,7 +60,7 @@ public class AndroidPsiTreeChangePreprocessor : PsiTreeChangePreprocessor, Simpl
} }
} }
private fun AndroidResourceManager.getModuleResDirectories(): List<VirtualFile> { private fun AndroidLayoutXmlFileManager.getModuleResDirectories(): List<VirtualFile> {
val info = androidModuleInfo ?: return listOf() val info = androidModuleInfo ?: return listOf()
val fileManager = VirtualFileManager.getInstance() val fileManager = VirtualFileManager.getInstance()
@@ -14,34 +14,31 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.plugin.android package org.jetbrains.kotlin.android.synthetic.idea
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
import org.jetbrains.kotlin.idea.caches.resolve.getModuleInfo
import org.jetbrains.kotlin.psi.moduleInfo
import com.intellij.refactoring.rename.RenamePsiElementProcessor
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.psi.JetProperty
import com.intellij.psi.xml.XmlAttributeValue
import com.intellij.psi.PsiField
import com.intellij.psi.PsiClass
import com.intellij.openapi.module.Module
import org.jetbrains.kotlin.idea.caches.resolve.ModuleSourceInfo
import org.jetbrains.kotlin.psi.JetFile
import com.intellij.psi.search.SearchScope
import com.intellij.psi.impl.light.LightElement
import org.jetbrains.kotlin.lang.resolve.android.AndroidUIXmlProcessor
import com.intellij.psi.xml.XmlAttribute
import org.jetbrains.kotlin.lang.resolve.android
import org.jetbrains.kotlin.lang.resolve.android.AndroidConst
import com.intellij.openapi.module.ModuleServiceManager
import org.jetbrains.android.util.AndroidResourceUtil
import org.jetbrains.android.dom.wrappers.LazyValueResourceElementWrapper
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.openapi.components.ServiceManager import com.intellij.openapi.components.ServiceManager
import org.jetbrains.kotlin.lang.resolve.android.isAndroidSyntheticElement import com.intellij.openapi.module.Module
import org.jetbrains.kotlin.lang.resolve.android.nameToIdDeclaration import com.intellij.openapi.module.ModuleServiceManager
import org.jetbrains.kotlin.lang.resolve.android.idToName import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiField
import com.intellij.psi.impl.light.LightElement
import com.intellij.psi.search.SearchScope
import com.intellij.psi.xml.XmlAttribute
import com.intellij.psi.xml.XmlAttributeValue
import com.intellij.refactoring.rename.RenamePsiElementProcessor
import org.jetbrains.android.dom.wrappers.LazyValueResourceElementWrapper
import org.jetbrains.android.util.AndroidResourceUtil
import org.jetbrains.kotlin.android.synthetic.AndroidConst
import org.jetbrains.kotlin.android.synthetic.idToName
import org.jetbrains.kotlin.android.synthetic.isAndroidSyntheticElement
import org.jetbrains.kotlin.android.synthetic.nameToIdDeclaration
import org.jetbrains.kotlin.android.synthetic.res.SyntheticFileGenerator
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
import org.jetbrains.kotlin.idea.caches.resolve.ModuleSourceInfo
import org.jetbrains.kotlin.idea.caches.resolve.getModuleInfo
import org.jetbrains.kotlin.psi.JetProperty
public class AndroidRenameProcessor : RenamePsiElementProcessor() { public class AndroidRenameProcessor : RenamePsiElementProcessor() {
@@ -93,8 +90,8 @@ public class AndroidRenameProcessor : RenamePsiElementProcessor() {
) { ) {
val module = jetProperty.getModule() ?: return val module = jetProperty.getModule() ?: return
val processor = ModuleServiceManager.getService(module, javaClass<AndroidUIXmlProcessor>())!! val processor = ModuleServiceManager.getService(module, javaClass<SyntheticFileGenerator>())!!
val resourceManager = processor.resourceManager val resourceManager = processor.layoutXmlFileManager
val psiElements = resourceManager.propertyToXmlAttributes(jetProperty).map { it as? XmlAttribute }.filterNotNull() val psiElements = resourceManager.propertyToXmlAttributes(jetProperty).map { it as? XmlAttribute }.filterNotNull()
@@ -120,7 +117,7 @@ public class AndroidRenameProcessor : RenamePsiElementProcessor() {
val module = attribute.getModule() ?: ModuleUtilCore.findModuleForFile( val module = attribute.getModule() ?: ModuleUtilCore.findModuleForFile(
attribute.getContainingFile().getVirtualFile(), attribute.getProject()) ?: return attribute.getContainingFile().getVirtualFile(), attribute.getProject()) ?: return
val processor = ModuleServiceManager.getService(module, javaClass<AndroidUIXmlProcessor>())!! val processor = ModuleServiceManager.getService(module, javaClass<SyntheticFileGenerator>())!!
if (element == null) return if (element == null) return
val oldPropName = AndroidResourceUtil.getResourceNameByReferenceText(attribute.getValue()) val oldPropName = AndroidResourceUtil.getResourceNameByReferenceText(attribute.getValue())
val newPropName = idToName(newName) val newPropName = idToName(newName)
@@ -133,9 +130,9 @@ public class AndroidRenameProcessor : RenamePsiElementProcessor() {
allRenames: MutableMap<PsiElement, String>, allRenames: MutableMap<PsiElement, String>,
newPropName: String, newPropName: String,
oldPropName: String, oldPropName: String,
processor: AndroidUIXmlProcessor processor: SyntheticFileGenerator
) { ) {
val props = processor.parseToPsi()?.flatMap { it.findChildrenByClass(javaClass<JetProperty>()).toList() } val props = processor.getSyntheticFiles()?.flatMap { it.findChildrenByClass(javaClass<JetProperty>()).toList() }
val matchedProps = props?.filter { it.getName() == oldPropName } ?: listOf() val matchedProps = props?.filter { it.getName() == oldPropName } ?: listOf()
for (prop in matchedProps) { for (prop in matchedProps) {
allRenames[prop] = newPropName allRenames[prop] = newPropName
@@ -148,7 +145,7 @@ public class AndroidRenameProcessor : RenamePsiElementProcessor() {
allRenames: MutableMap<PsiElement, String> allRenames: MutableMap<PsiElement, String>
) { ) {
val oldName = field.getName()!! val oldName = field.getName()!!
val processor = ServiceManager.getService(field.getProject(), javaClass<AndroidUIXmlProcessor>()) val processor = ServiceManager.getService(field.getProject(), javaClass<SyntheticFileGenerator>())
renameSyntheticProperties(allRenames, newName, oldName, processor) renameSyntheticProperties(allRenames, newName, oldName, processor)
} }
@@ -14,24 +14,24 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.plugin.android package org.jetbrains.kotlin.android.synthetic.idea
import org.jetbrains.kotlin.plugin.references.SimpleNameReferenceExtension
import org.jetbrains.kotlin.idea.references.JetSimpleNameReference
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import org.jetbrains.android.dom.wrappers.ValueResourceElementWrapper import org.jetbrains.android.dom.wrappers.ValueResourceElementWrapper
import org.jetbrains.kotlin.android.synthetic.isAndroidSyntheticElement
import org.jetbrains.kotlin.idea.references.JetSimpleNameReference
import org.jetbrains.kotlin.plugin.references.SimpleNameReferenceExtension
import org.jetbrains.kotlin.psi.JetProperty import org.jetbrains.kotlin.psi.JetProperty
import org.jetbrains.kotlin.psi.JetPsiFactory import org.jetbrains.kotlin.psi.JetPsiFactory
import org.jetbrains.kotlin.lang.resolve.android.isAndroidSyntheticElement
public class AndroidSimpleNameReferenceExtension : SimpleNameReferenceExtension { public class AndroidSimpleNameReferenceExtension : SimpleNameReferenceExtension {
override fun isReferenceTo(reference: JetSimpleNameReference, element: PsiElement): Boolean? { override fun isReferenceTo(reference: JetSimpleNameReference, element: PsiElement): Boolean? {
val resolvedElement = reference.resolve() ?: return null val resolvedElement = reference.resolve() as? JetProperty ?: return null
if (isAndroidSyntheticElement(resolvedElement)) { if (isAndroidSyntheticElement(resolvedElement)) {
if (element is ValueResourceElementWrapper) { if (element is ValueResourceElementWrapper) {
val resource = element.getValue() val resource = element.value
return (resolvedElement as JetProperty).getName() == resource.substring(resource.indexOf('/') + 1) return resolvedElement.name == resource.substring(resource.indexOf('/') + 1)
} }
} }
return null return null
@@ -14,15 +14,16 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.plugin.android package org.jetbrains.kotlin.android.synthetic.idea
import com.intellij.psi.xml.XmlAttribute
import com.intellij.psi.XmlElementVisitor
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.XmlElementVisitor
import com.intellij.psi.xml.XmlAttribute
import com.intellij.psi.xml.XmlElement import com.intellij.psi.xml.XmlElement
import com.intellij.psi.xml.XmlTag import com.intellij.psi.xml.XmlTag
import org.jetbrains.kotlin.lang.resolve.android import org.jetbrains.kotlin.android.synthetic.AndroidConst
import org.jetbrains.kotlin.lang.resolve.android.* import org.jetbrains.kotlin.android.synthetic.idToName
import org.jetbrains.kotlin.android.synthetic.isWidgetTypeIgnored
class AndroidXmlVisitor(val elementCallback: (String, String, XmlAttribute) -> Unit) : XmlElementVisitor() { class AndroidXmlVisitor(val elementCallback: (String, String, XmlAttribute) -> Unit) : XmlElementVisitor() {
@@ -35,7 +36,7 @@ class AndroidXmlVisitor(val elementCallback: (String, String, XmlAttribute) -> U
} }
override fun visitXmlTag(tag: XmlTag?) { override fun visitXmlTag(tag: XmlTag?) {
val localName = tag?.getLocalName() ?: "" val localName = tag?.localName ?: ""
if (isWidgetTypeIgnored(localName)) { if (isWidgetTypeIgnored(localName)) {
tag?.acceptChildren(this) tag?.acceptChildren(this)
return return
@@ -43,9 +44,9 @@ class AndroidXmlVisitor(val elementCallback: (String, String, XmlAttribute) -> U
val idAttribute = tag?.getAttribute(AndroidConst.ID_ATTRIBUTE) val idAttribute = tag?.getAttribute(AndroidConst.ID_ATTRIBUTE)
if (idAttribute != null) { if (idAttribute != null) {
val idAttributeValue = idAttribute.getValue() val idAttributeValue = idAttribute.value
if (idAttributeValue != null) { if (idAttributeValue != null) {
val xmlType = tag?.getAttribute(AndroidConst.CLASS_ATTRIBUTE_NO_NAMESPACE)?.getValue() ?: localName val xmlType = tag?.getAttribute(AndroidConst.CLASS_ATTRIBUTE_NO_NAMESPACE)?.value ?: localName
val name = idToName(idAttributeValue) val name = idToName(idAttributeValue)
if (name != null) elementCallback(name, xmlType, idAttribute) if (name != null) elementCallback(name, xmlType, idAttribute)
} }
@@ -14,15 +14,15 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.plugin.android package org.jetbrains.kotlin.android.synthetic.idea
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.extensions.ExternalDeclarationsProvider
import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.idea.caches.resolve.ModuleSourceInfo
import com.intellij.openapi.module.ModuleServiceManager import com.intellij.openapi.module.ModuleServiceManager
import org.jetbrains.kotlin.lang.resolve.android.AndroidUIXmlProcessor import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.android.synthetic.res.SyntheticFileGenerator
import org.jetbrains.kotlin.extensions.ExternalDeclarationsProvider
import org.jetbrains.kotlin.idea.caches.resolve.ModuleSourceInfo
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.psi.moduleInfo import org.jetbrains.kotlin.psi.moduleInfo
public class IDEAndroidExternalDeclarationsProvider(private val project: Project) : ExternalDeclarationsProvider { public class IDEAndroidExternalDeclarationsProvider(private val project: Project) : ExternalDeclarationsProvider {
@@ -30,8 +30,8 @@ public class IDEAndroidExternalDeclarationsProvider(private val project: Project
if (moduleInfo !is ModuleSourceInfo) return listOf() if (moduleInfo !is ModuleSourceInfo) return listOf()
val module = moduleInfo.module val module = moduleInfo.module
val parser = ModuleServiceManager.getService(module, javaClass<AndroidUIXmlProcessor>())!! val parser = ModuleServiceManager.getService(module, javaClass<SyntheticFileGenerator>())!!
val syntheticFiles = parser.parseToPsi() val syntheticFiles = parser.getSyntheticFiles()
syntheticFiles?.forEach { it.moduleInfo = moduleInfo } syntheticFiles?.forEach { it.moduleInfo = moduleInfo }
return syntheticFiles ?: listOf() return syntheticFiles ?: listOf()
@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.plugin.android; package org.jetbrains.kotlin.android.synthetic.idea;
import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Key;
@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.plugin.android; package org.jetbrains.kotlin.android.synthetic.idea;
import com.intellij.lang.ASTNode; import com.intellij.lang.ASTNode;
import com.intellij.lang.Language; import com.intellij.lang.Language;
@@ -14,21 +14,20 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.plugin.android package org.jetbrains.kotlin.android.synthetic.idea.res
import com.intellij.openapi.module.Module import com.intellij.openapi.module.Module
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import org.jetbrains.android.facet.AndroidFacet import org.jetbrains.android.facet.AndroidFacet
import org.jetbrains.kotlin.lang.resolve.android.AndroidConst import org.jetbrains.kotlin.android.synthetic.AndroidConst
import kotlin.properties.Delegates import org.jetbrains.kotlin.android.synthetic.idea.AndroidXmlVisitor
import org.jetbrains.kotlin.plugin.android.AndroidXmlVisitor import org.jetbrains.kotlin.android.synthetic.res.AndroidModuleInfo
import org.jetbrains.kotlin.lang.resolve.android.AndroidResourceManager import org.jetbrains.kotlin.android.synthetic.res.AndroidLayoutXmlFileManager
import org.jetbrains.kotlin.lang.resolve.android.AndroidModuleInfo
import org.jetbrains.kotlin.psi.JetProperty import org.jetbrains.kotlin.psi.JetProperty
public class IDEAndroidResourceManager(val module: Module) : AndroidResourceManager(module.getProject()) { public class IDEAndroidLayoutXmlFileManager(val module: Module) : AndroidLayoutXmlFileManager(module.getProject()) {
override val androidModuleInfo: AndroidModuleInfo? by Delegates.lazy { module.androidFacet?.toAndroidModuleInfo() } override val androidModuleInfo: AndroidModuleInfo? by lazy { module.androidFacet?.toAndroidModuleInfo() }
override fun propertyToXmlAttributes(property: JetProperty): List<PsiElement> { override fun propertyToXmlAttributes(property: JetProperty): List<PsiElement> {
val fqPath = property.getFqName()?.pathSegments() ?: return listOf() val fqPath = property.getFqName()?.pathSegments() ?: return listOf()
@@ -53,7 +52,7 @@ public class IDEAndroidResourceManager(val module: Module) : AndroidResourceMana
get() = AndroidFacet.getInstance(this) get() = AndroidFacet.getInstance(this)
private fun AndroidFacet.toAndroidModuleInfo(): AndroidModuleInfo? { private fun AndroidFacet.toAndroidModuleInfo(): AndroidModuleInfo? {
val applicationPackage = getManifest()?.getPackage()?.toString() val applicationPackage = manifest?.getPackage()?.toString()
val mainResDirectories = getAllResourceDirectories().map { it.getPath() } val mainResDirectories = getAllResourceDirectories().map { it.getPath() }
return if (applicationPackage != null) { return if (applicationPackage != null) {
@@ -14,21 +14,25 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.plugin.android package org.jetbrains.kotlin.android.synthetic.idea.res
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.module.Module import com.intellij.openapi.module.Module
import com.intellij.psi.JavaPsiFacade import com.intellij.psi.JavaPsiFacade
import org.jetbrains.kotlin.plugin.android.IDEAndroidResourceManager
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.plugin.android.AndroidXmlVisitor import com.intellij.psi.impl.PsiTreeChangePreprocessor
import com.intellij.psi.impl.*
import kotlin.properties.*
import org.jetbrains.kotlin.lang.resolve.android.*
import com.intellij.psi.util.CachedValue import com.intellij.psi.util.CachedValue
import com.intellij.psi.util.CachedValueProvider.Result import com.intellij.psi.util.CachedValueProvider.Result
import org.jetbrains.kotlin.android.synthetic.AndroidConst
import org.jetbrains.kotlin.android.synthetic.idea.AndroidPsiTreeChangePreprocessor
import org.jetbrains.kotlin.android.synthetic.idea.AndroidXmlVisitor
import org.jetbrains.kotlin.android.synthetic.parseAndroidResource
import org.jetbrains.kotlin.android.synthetic.res.AndroidResource
import org.jetbrains.kotlin.android.synthetic.res.AndroidSyntheticFile
import org.jetbrains.kotlin.android.synthetic.res.SyntheticFileGenerator
class IDEAndroidUIXmlProcessor(val module: Module) : AndroidUIXmlProcessor(module.getProject()) { class IDESyntheticFileGenerator(val module: Module) : SyntheticFileGenerator(module.project) {
private val supportV4: Boolean
init { init {
val scope = module.getModuleWithDependenciesAndLibrariesScope(false) val scope = module.getModuleWithDependenciesAndLibrariesScope(false)
@@ -36,26 +40,28 @@ class IDEAndroidUIXmlProcessor(val module: Module) : AndroidUIXmlProcessor(modul
.findClasses(AndroidConst.SUPPORT_FRAGMENT_FQNAME, scope).isNotEmpty() .findClasses(AndroidConst.SUPPORT_FRAGMENT_FQNAME, scope).isNotEmpty()
} }
override val resourceManager: IDEAndroidResourceManager = IDEAndroidResourceManager(module) override fun supportV4() = supportV4
private val psiTreeChangePreprocessor by Delegates.lazy { override val layoutXmlFileManager: IDEAndroidLayoutXmlFileManager = IDEAndroidLayoutXmlFileManager(module)
module.getProject().getExtensions(PsiTreeChangePreprocessor.EP_NAME).first { it is AndroidPsiTreeChangePreprocessor }
private val psiTreeChangePreprocessor by lazy {
module.project.getExtensions(PsiTreeChangePreprocessor.EP_NAME).first { it is AndroidPsiTreeChangePreprocessor }
} }
override val cachedSources: CachedValue<List<AndroidSyntheticFile>> by Delegates.lazy { override val cachedSources: CachedValue<List<AndroidSyntheticFile>> by lazy {
cachedValue { cachedValue {
Result.create(parse(), psiTreeChangePreprocessor) Result.create(generateSyntheticFiles(), psiTreeChangePreprocessor)
} }
} }
override fun parseLayout(files: List<PsiFile>): List<AndroidResource> { override fun extractLayoutResources(files: List<PsiFile>): List<AndroidResource> {
val widgets = arrayListOf<AndroidResource>() val widgets = arrayListOf<AndroidResource>()
val visitor = AndroidXmlVisitor { id, widgetType, attribute -> val visitor = AndroidXmlVisitor { id, widgetType, attribute ->
widgets.add(parseAndroidResource(id, widgetType)) widgets.add(parseAndroidResource(id, widgetType))
} }
files.forEach { it.accept(visitor) } files.forEach { it.accept(visitor) }
return removeDuplicates(widgets) return filterDuplicates(widgets)
} }
} }
@@ -16,16 +16,10 @@
package org.jetbrains.kotlin.android package org.jetbrains.kotlin.android
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.android.synthetic.res.CliSyntheticFileGenerator
import org.jetbrains.kotlin.plugin.android.TestConst
import org.jetbrains.kotlin.lang.resolve.android.CliAndroidUIXmlProcessor
import com.intellij.openapi.module.ModuleManager import com.intellij.openapi.module.ModuleManager
import org.jetbrains.kotlin.plugin.android.IDEAndroidUIXmlProcessor import org.jetbrains.kotlin.android.synthetic.idea.TestConst
import org.jetbrains.kotlin.test.ConfigurationKind import org.jetbrains.kotlin.android.synthetic.idea.res.IDESyntheticFileGenerator
import org.jetbrains.kotlin.test.TestJdkKind
import org.jetbrains.kotlin.test.JetTestUtils
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import java.io.File
import kotlin.test.assertEquals import kotlin.test.assertEquals
public abstract class AbstractParserResultEqualityTest : KotlinAndroidTestCase() { public abstract class AbstractParserResultEqualityTest : KotlinAndroidTestCase() {
@@ -37,11 +31,11 @@ public abstract class AbstractParserResultEqualityTest : KotlinAndroidTestCase()
"$path${it.name}/" "$path${it.name}/"
} }
val cliParser = CliAndroidUIXmlProcessor(project, path + "../AndroidManifest.xml", resDirs) val cliParser = CliSyntheticFileGenerator(project, path + "../AndroidManifest.xml", resDirs, false)
val ideParser = IDEAndroidUIXmlProcessor(ModuleManager.getInstance(project).getModules()[0]) val ideParser = IDESyntheticFileGenerator(ModuleManager.getInstance(project).getModules()[0])
val cliResult = cliParser.parseToPsi()!!.joinToString("\n\n") val cliResult = cliParser.getSyntheticFiles()!!.joinToString("\n\n")
val ideResult = ideParser.parseToPsi()!!.joinToString("\n\n") val ideResult = ideParser.getSyntheticFiles()!!.joinToString("\n\n")
assertEquals(cliResult, ideResult) assertEquals(cliResult, ideResult)
} }