diff --git a/.bunch b/.bunch index e5600a0e5e6..305979d824f 100644 --- a/.bunch +++ b/.bunch @@ -1,4 +1,5 @@ 192 +193_192 191 183_191 as34_183_191 diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt.193 b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt.193 new file mode 100644 index 00000000000..fbad9bcf37d --- /dev/null +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt.193 @@ -0,0 +1,748 @@ +/* + * Copyright 2010-2017 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.cli.jvm.compiler + +import com.intellij.codeInsight.ContainerProvider +import com.intellij.codeInsight.ExternalAnnotationsManager +import com.intellij.codeInsight.InferredAnnotationsManager +import com.intellij.codeInsight.runner.JavaMainMethodProvider +import com.intellij.core.CoreApplicationEnvironment +import com.intellij.core.CoreJavaFileManager +import com.intellij.core.JavaCoreApplicationEnvironment +import com.intellij.core.JavaCoreProjectEnvironment +import com.intellij.ide.highlighter.JavaFileType +import com.intellij.lang.MetaLanguage +import com.intellij.lang.java.JavaParserDefinition +import com.intellij.mock.MockProject +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.TransactionGuard +import com.intellij.openapi.application.TransactionGuardImpl +import com.intellij.openapi.components.ServiceManager +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.extensions.Extensions +import com.intellij.openapi.extensions.ExtensionsArea +import com.intellij.openapi.fileTypes.FileTypeExtensionPoint +import com.intellij.openapi.fileTypes.PlainTextFileType +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Disposer +import com.intellij.openapi.util.JDOMUtil +import com.intellij.openapi.util.NotNullLazyValue +import com.intellij.openapi.util.SystemInfo +import com.intellij.openapi.util.io.FileUtilRt +import com.intellij.openapi.util.text.StringUtil +import com.intellij.openapi.vfs.* +import com.intellij.openapi.vfs.impl.ZipHandler +import com.intellij.psi.FileContextProvider +import com.intellij.psi.PsiElementFinder +import com.intellij.psi.PsiManager +import com.intellij.psi.augment.PsiAugmentProvider +import com.intellij.psi.augment.TypeAnnotationModifier +import com.intellij.psi.compiled.ClassFileDecompilers +import com.intellij.psi.impl.JavaClassSupersImpl +import com.intellij.psi.impl.PsiElementFinderImpl +import com.intellij.psi.impl.PsiTreeChangePreprocessor +import com.intellij.psi.impl.file.impl.JavaFileManager +import com.intellij.psi.meta.MetaDataContributor +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.stubs.BinaryFileStubBuilders +import com.intellij.psi.util.JavaClassSupers +import com.intellij.util.io.URLUtil +import com.intellij.util.lang.UrlClassLoader +import org.jetbrains.annotations.TestOnly +import org.jetbrains.kotlin.asJava.KotlinAsJavaSupport +import org.jetbrains.kotlin.asJava.LightClassGenerationSupport +import org.jetbrains.kotlin.asJava.classes.FacadeCache +import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade +import org.jetbrains.kotlin.asJava.finder.JavaElementFinder +import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension +import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys +import org.jetbrains.kotlin.cli.common.CliModuleVisibilityManagerImpl +import org.jetbrains.kotlin.cli.common.KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY +import org.jetbrains.kotlin.cli.common.config.ContentRoot +import org.jetbrains.kotlin.cli.common.config.KotlinSourceRoot +import org.jetbrains.kotlin.cli.common.config.kotlinSourceRoots +import org.jetbrains.kotlin.cli.common.extensions.ScriptEvaluationExtension +import org.jetbrains.kotlin.cli.common.extensions.ShellExtension +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.ERROR +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.STRONG_WARNING +import org.jetbrains.kotlin.cli.common.messages.MessageCollector +import org.jetbrains.kotlin.cli.common.toBooleanLenient +import org.jetbrains.kotlin.cli.jvm.JvmRuntimeVersionsConsistencyChecker +import org.jetbrains.kotlin.cli.jvm.config.* +import org.jetbrains.kotlin.cli.jvm.index.* +import org.jetbrains.kotlin.cli.jvm.javac.JavacWrapperRegistrar +import org.jetbrains.kotlin.cli.jvm.modules.CliJavaModuleFinder +import org.jetbrains.kotlin.cli.jvm.modules.CliJavaModuleResolver +import org.jetbrains.kotlin.cli.jvm.modules.CoreJrtFileSystem +import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension +import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension +import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar +import org.jetbrains.kotlin.config.APPEND_JAVA_SOURCE_ROOTS_HANDLER_KEY +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.config.JVMConfigurationKeys +import org.jetbrains.kotlin.config.languageVersionSettings +import org.jetbrains.kotlin.extensions.* +import org.jetbrains.kotlin.idea.KotlinFileType +import org.jetbrains.kotlin.js.translate.extensions.JsSyntheticTranslateExtension +import org.jetbrains.kotlin.load.kotlin.KotlinBinaryClassCache +import org.jetbrains.kotlin.load.kotlin.MetadataFinderFactory +import org.jetbrains.kotlin.load.kotlin.ModuleVisibilityManager +import org.jetbrains.kotlin.load.kotlin.VirtualFileFinderFactory +import org.jetbrains.kotlin.parsing.KotlinParserDefinition +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.resolve.CodeAnalyzerInitializer +import org.jetbrains.kotlin.resolve.ModuleAnnotationsResolver +import org.jetbrains.kotlin.resolve.extensions.ExtraImportsProviderExtension +import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension +import org.jetbrains.kotlin.resolve.jvm.KotlinJavaPsiFacade +import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension +import org.jetbrains.kotlin.resolve.jvm.extensions.PackageFragmentProviderExtension +import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleResolver +import org.jetbrains.kotlin.resolve.lazy.declarations.CliDeclarationProviderFactoryService +import org.jetbrains.kotlin.resolve.lazy.declarations.DeclarationProviderFactoryService +import org.jetbrains.kotlin.utils.PathUtil +import java.io.File +import java.lang.reflect.Field +import java.lang.reflect.Modifier +import java.util.zip.ZipFile +import javax.xml.stream.XMLInputFactory + +class KotlinCoreEnvironment private constructor( + private val projectEnvironment: JavaCoreProjectEnvironment, + initialConfiguration: CompilerConfiguration, + configFiles: EnvironmentConfigFiles +) { + + class ProjectEnvironment( + disposable: Disposable, applicationEnvironment: JavaCoreApplicationEnvironment + ) : + KotlinCoreProjectEnvironment(disposable, applicationEnvironment) { + + private var extensionRegistered = false + + override fun preregisterServices() { + registerProjectExtensionPoints(Extensions.getArea(project)) + } + + fun registerExtensionsFromPlugins(configuration: CompilerConfiguration) { + if (!extensionRegistered) { + registerPluginExtensionPoints(project) + registerExtensionsFromPlugins(project, configuration) + extensionRegistered = true + } + } + + override fun registerJavaPsiFacade() { + with(project) { + registerService( + CoreJavaFileManager::class.java, + ServiceManager.getService(this, JavaFileManager::class.java) as CoreJavaFileManager + ) + + registerKotlinLightClassSupport(project) + + registerService(ExternalAnnotationsManager::class.java, MockExternalAnnotationsManager()) + registerService(InferredAnnotationsManager::class.java, MockInferredAnnotationsManager()) + } + + super.registerJavaPsiFacade() + } + } + + private val sourceFiles = mutableListOf() + private val rootsIndex: JvmDependenciesDynamicCompoundIndex + private val packagePartProviders = mutableListOf() + + private val classpathRootsResolver: ClasspathRootsResolver + private val initialRoots: List + + val configuration: CompilerConfiguration = initialConfiguration.apply { setupJdkClasspathRoots(configFiles) }.copy() + + init { + PersistentFSConstants::class.java.getDeclaredField("ourMaxIntellisenseFileSize") + .apply { isAccessible = true } + .setInt(null, FileUtilRt.LARGE_FOR_CONTENT_LOADING) + + val project = projectEnvironment.project + + val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) + + (projectEnvironment as? ProjectEnvironment)?.registerExtensionsFromPlugins(configuration) + // otherwise consider that project environment is properly configured before passing to the environment + // TODO: consider some asserts to check important extension points + + project.registerService(DeclarationProviderFactoryService::class.java, CliDeclarationProviderFactoryService(sourceFiles)) + + val isJvm = configFiles == EnvironmentConfigFiles.JVM_CONFIG_FILES + project.registerService(ModuleVisibilityManager::class.java, CliModuleVisibilityManagerImpl(isJvm)) + + registerProjectServicesForCLI(projectEnvironment) + + registerProjectServices(projectEnvironment.project) + + for (extension in CompilerConfigurationExtension.getInstances(project)) { + extension.updateConfiguration(configuration) + } + + sourceFiles += createKtFiles(project) + + collectAdditionalSources(project) + + sourceFiles.sortBy { it.virtualFile.path } + + val jdkHome = configuration.get(JVMConfigurationKeys.JDK_HOME) + val jrtFileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.JRT_PROTOCOL) + val javaModuleFinder = CliJavaModuleFinder(jdkHome?.path?.let { path -> + jrtFileSystem?.findFileByPath(path + URLUtil.JAR_SEPARATOR) + }) + + val outputDirectory = + configuration.get(JVMConfigurationKeys.MODULES)?.singleOrNull()?.getOutputDirectory() + ?: configuration.get(JVMConfigurationKeys.OUTPUT_DIRECTORY)?.absolutePath + + classpathRootsResolver = ClasspathRootsResolver( + PsiManager.getInstance(project), + messageCollector, + configuration.getList(JVMConfigurationKeys.ADDITIONAL_JAVA_MODULES), + this::contentRootToVirtualFile, + javaModuleFinder, + !configuration.getBoolean(CLIConfigurationKeys.ALLOW_KOTLIN_PACKAGE), + outputDirectory?.let(this::findLocalFile) + ) + + val (initialRoots, javaModules) = + classpathRootsResolver.convertClasspathRoots(configuration.getList(CLIConfigurationKeys.CONTENT_ROOTS)) + this.initialRoots = initialRoots + + if (!configuration.getBoolean(JVMConfigurationKeys.SKIP_RUNTIME_VERSION_CHECK) && messageCollector != null) { + JvmRuntimeVersionsConsistencyChecker.checkCompilerClasspathConsistency( + messageCollector, + configuration, + initialRoots.mapNotNull { (file, type) -> if (type == JavaRoot.RootType.BINARY) file else null } + ) + } + + val (roots, singleJavaFileRoots) = + initialRoots.partition { (file) -> file.isDirectory || file.extension != JavaFileType.DEFAULT_EXTENSION } + + // REPL and kapt2 update classpath dynamically + rootsIndex = JvmDependenciesDynamicCompoundIndex().apply { + addIndex(JvmDependenciesIndexImpl(roots)) + updateClasspathFromRootsIndex(this) + } + + (ServiceManager.getService(project, CoreJavaFileManager::class.java) as KotlinCliJavaFileManagerImpl).initialize( + rootsIndex, + packagePartProviders, + SingleJavaFileRootsIndex(singleJavaFileRoots), + configuration.getBoolean(JVMConfigurationKeys.USE_FAST_CLASS_FILES_READING) + ) + + project.registerService( + JavaModuleResolver::class.java, + CliJavaModuleResolver(classpathRootsResolver.javaModuleGraph, javaModules, javaModuleFinder.systemModules.toList()) + ) + + val finderFactory = CliVirtualFileFinderFactory(rootsIndex) + project.registerService(MetadataFinderFactory::class.java, finderFactory) + project.registerService(VirtualFileFinderFactory::class.java, finderFactory) + + project.putUserData(APPEND_JAVA_SOURCE_ROOTS_HANDLER_KEY, fun(roots: List) { + updateClasspath(roots.map { JavaSourceRoot(it, null) }) + }) + } + + private fun collectAdditionalSources(project: MockProject) { + var unprocessedSources: Collection = sourceFiles + val processedSources = HashSet() + val processedSourcesByExtension = HashMap>() + // repeat feeding extensions with sources while new sources a being added + var sourceCollectionIterations = 0 + while (unprocessedSources.isNotEmpty()) { + if (sourceCollectionIterations++ > 10) { // TODO: consider using some appropriate global constant + throw IllegalStateException("Unable to collect additional sources in reasonable number of iterations") + } + processedSources.addAll(unprocessedSources) + val allNewSources = ArrayList() + for (extension in CollectAdditionalSourcesExtension.getInstances(project)) { + // do not feed the extension with the sources it returned on the previous iteration + val sourcesToProcess = unprocessedSources - (processedSourcesByExtension[extension] ?: emptyList()) + val newSources = extension.collectAdditionalSourcesAndUpdateConfiguration(sourcesToProcess, configuration, project) + if (newSources.isNotEmpty()) { + allNewSources.addAll(newSources) + processedSourcesByExtension[extension] = newSources + } + } + unprocessedSources = allNewSources.filterNot { processedSources.contains(it) }.distinct() + sourceFiles += unprocessedSources + } + } + + fun addKotlinSourceRoots(rootDirs: List) { + val roots = rootDirs.map { KotlinSourceRoot(it.absolutePath, isCommon = false) } + sourceFiles += createSourceFilesFromSourceRoots(configuration, project, roots) + } + + fun createPackagePartProvider(scope: GlobalSearchScope): JvmPackagePartProvider { + return JvmPackagePartProvider(configuration.languageVersionSettings, scope).apply { + addRoots(initialRoots, configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)) + packagePartProviders += this + (ModuleAnnotationsResolver.getInstance(project) as CliModuleAnnotationsResolver).addPackagePartProvider(this) + } + } + + private val VirtualFile.javaFiles: List + get() = mutableListOf().apply { + VfsUtilCore.processFilesRecursively(this@javaFiles) { file -> + if (file.fileType == JavaFileType.INSTANCE) { + add(file) + } + true + } + } + + private val allJavaFiles: List + get() = configuration.javaSourceRoots + .mapNotNull(this::findLocalFile) + .flatMap { it.javaFiles } + .map { File(it.canonicalPath) } + + fun registerJavac( + javaFiles: List = allJavaFiles, + kotlinFiles: List = sourceFiles, + arguments: Array? = null, + bootClasspath: List? = null, + sourcePath: List? = null + ): Boolean { + return JavacWrapperRegistrar.registerJavac( + projectEnvironment.project, configuration, javaFiles, kotlinFiles, arguments, bootClasspath, sourcePath, + LightClassGenerationSupport.getInstance(project) + ) + } + + private val applicationEnvironment: CoreApplicationEnvironment + get() = projectEnvironment.environment + + val project: Project + get() = projectEnvironment.project + + internal fun countLinesOfCode(sourceFiles: List): Int = + sourceFiles.sumBy { sourceFile -> + val text = sourceFile.text + StringUtil.getLineBreakCount(text) + (if (StringUtil.endsWithLineBreak(text)) 0 else 1) + } + + private fun updateClasspathFromRootsIndex(index: JvmDependenciesIndex) { + index.indexedRoots.forEach { + projectEnvironment.addSourcesToClasspath(it.file) + } + } + + fun updateClasspath(contentRoots: List): List? { + // TODO: add new Java modules to CliJavaModuleResolver + val newRoots = classpathRootsResolver.convertClasspathRoots(contentRoots).roots + + for (packagePartProvider in packagePartProviders) { + packagePartProvider.addRoots(newRoots, configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)) + } + + return rootsIndex.addNewIndexForRoots(newRoots)?.let { newIndex -> + updateClasspathFromRootsIndex(newIndex) + newIndex.indexedRoots.mapNotNull { (file) -> + VfsUtilCore.virtualToIoFile(VfsUtilCore.getVirtualFileForJar(file) ?: file) + }.toList() + }.orEmpty() + } + + private fun contentRootToVirtualFile(root: JvmContentRoot): VirtualFile? = + when (root) { + is JvmClasspathRoot -> + if (root.file.isFile) findJarRoot(root.file) else findExistingRoot(root, "Classpath entry") + is JvmModulePathRoot -> + if (root.file.isFile) findJarRoot(root.file) else findExistingRoot(root, "Java module root") + is JavaSourceRoot -> + findExistingRoot(root, "Java source root") + else -> + throw IllegalStateException("Unexpected root: $root") + } + + internal fun findLocalFile(path: String): VirtualFile? = + applicationEnvironment.localFileSystem.findFileByPath(path) + + private fun findExistingRoot(root: JvmContentRoot, rootDescription: String): VirtualFile? { + return findLocalFile(root.file.absolutePath).also { + if (it == null) { + report(STRONG_WARNING, "$rootDescription points to a non-existent location: ${root.file}") + } + } + } + + private fun findJarRoot(file: File): VirtualFile? = + applicationEnvironment.jarFileSystem.findFileByPath("$file${URLUtil.JAR_SEPARATOR}") + + private fun getSourceRootsCheckingForDuplicates(): List { + val uniqueSourceRoots = hashSetOf() + val result = mutableListOf() + + for (root in configuration.kotlinSourceRoots) { + if (!uniqueSourceRoots.add(root.path)) { + report(STRONG_WARNING, "Duplicate source root: ${root.path}") + } + result.add(root) + } + + return result + } + + fun getSourceFiles(): List = sourceFiles + + private fun createKtFiles(project: Project): List = + createSourceFilesFromSourceRoots(configuration, project, getSourceRootsCheckingForDuplicates()) + + internal fun report(severity: CompilerMessageSeverity, message: String) = configuration.report(severity, message) + + companion object { + private val LOG = Logger.getInstance(KotlinCoreEnvironment::class.java) + + private val APPLICATION_LOCK = Object() + private var ourApplicationEnvironment: JavaCoreApplicationEnvironment? = null + private var ourProjectCount = 0 + + @JvmStatic + fun createForProduction( + parentDisposable: Disposable, configuration: CompilerConfiguration, configFiles: EnvironmentConfigFiles + ): KotlinCoreEnvironment { + val appEnv = getOrCreateApplicationEnvironmentForProduction(parentDisposable, configuration) + val projectEnv = ProjectEnvironment(parentDisposable, appEnv) + val environment = KotlinCoreEnvironment(projectEnv, configuration, configFiles) + + synchronized(APPLICATION_LOCK) { + ourProjectCount++ + } + return environment + } + + @JvmStatic + fun createForProduction( + projectEnvironment: JavaCoreProjectEnvironment, configuration: CompilerConfiguration, configFiles: EnvironmentConfigFiles + ): KotlinCoreEnvironment { + val environment = KotlinCoreEnvironment(projectEnvironment, configuration, configFiles) + + if (projectEnvironment.environment == applicationEnvironment) { + // accounting for core environment disposing + synchronized(APPLICATION_LOCK) { + ourProjectCount++ + } + } + return environment + } + + @TestOnly + @JvmStatic + fun createForTests( + parentDisposable: Disposable, initialConfiguration: CompilerConfiguration, extensionConfigs: EnvironmentConfigFiles + ): KotlinCoreEnvironment { + val configuration = initialConfiguration.copy() + // Tests are supposed to create a single project and dispose it right after use + val appEnv = createApplicationEnvironment(parentDisposable, configuration, unitTestMode = true) + val projectEnv = ProjectEnvironment(parentDisposable, appEnv) + return KotlinCoreEnvironment(projectEnv, configuration, extensionConfigs) + } + + // used in the daemon for jar cache cleanup + val applicationEnvironment: JavaCoreApplicationEnvironment? get() = ourApplicationEnvironment + + fun getOrCreateApplicationEnvironmentForProduction( + parentDisposable: Disposable, configuration: CompilerConfiguration + ): JavaCoreApplicationEnvironment { + synchronized(APPLICATION_LOCK) { + if (ourApplicationEnvironment == null) { + val disposable = Disposer.newDisposable() + ourApplicationEnvironment = createApplicationEnvironment(disposable, configuration, unitTestMode = false) + ourProjectCount = 0 + Disposer.register(disposable, Disposable { + synchronized(APPLICATION_LOCK) { + ourApplicationEnvironment = null + } + }) + } + // Disposing of the environment is unsafe in production then parallel builds are enabled, but turning it off universally + // breaks a lot of tests, therefore it is disabled for production and enabled for tests + if (System.getProperty(KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY).toBooleanLenient() != true) { + // JPS may run many instances of the compiler in parallel (there's an option for compiling independent modules in parallel in IntelliJ) + // All projects share the same ApplicationEnvironment, and when the last project is disposed, the ApplicationEnvironment is disposed as well + Disposer.register(parentDisposable, Disposable { + synchronized(APPLICATION_LOCK) { + if (--ourProjectCount <= 0) { + disposeApplicationEnvironment() + } + } + }) + } + + return ourApplicationEnvironment!! + } + } + + private fun disposeApplicationEnvironment() { + synchronized(APPLICATION_LOCK) { + val environment = ourApplicationEnvironment ?: return + ourApplicationEnvironment = null + Disposer.dispose(environment.parentDisposable) + ZipHandler.clearFileAccessorCache() + } + } + + private fun createApplicationEnvironment( + parentDisposable: Disposable, configuration: CompilerConfiguration, unitTestMode: Boolean + ): JavaCoreApplicationEnvironment { + // FIXME: no such method in 193 platform + // Extensions.cleanRootArea(parentDisposable) + registerAppExtensionPoints() + val applicationEnvironment = object : JavaCoreApplicationEnvironment(parentDisposable, unitTestMode) { + override fun createJrtFileSystem(): VirtualFileSystem? = CoreJrtFileSystem() + } + + registerApplicationExtensionPointsAndExtensionsFrom(configuration, "extensions/compiler.xml") + + registerApplicationServicesForCLI(applicationEnvironment) + registerApplicationServices(applicationEnvironment) + + return applicationEnvironment + } + + private fun registerAppExtensionPoints() { + val area = Extensions.getRootArea() + + CoreApplicationEnvironment.registerExtensionPoint(area, BinaryFileStubBuilders.EP_NAME, FileTypeExtensionPoint::class.java) + CoreApplicationEnvironment.registerExtensionPoint(area, FileContextProvider.EP_NAME, FileContextProvider::class.java) + + CoreApplicationEnvironment.registerExtensionPoint(area, MetaDataContributor.EP_NAME, MetaDataContributor::class.java) + CoreApplicationEnvironment.registerExtensionPoint(area, PsiAugmentProvider.EP_NAME, PsiAugmentProvider::class.java) + CoreApplicationEnvironment.registerExtensionPoint(area, JavaMainMethodProvider.EP_NAME, JavaMainMethodProvider::class.java) + + CoreApplicationEnvironment.registerExtensionPoint(area, ContainerProvider.EP_NAME, ContainerProvider::class.java) + CoreApplicationEnvironment.registerExtensionPoint( + area, ClassFileDecompilers.EP_NAME, ClassFileDecompilers.Decompiler::class.java + ) + + CoreApplicationEnvironment.registerExtensionPoint(area, TypeAnnotationModifier.EP_NAME, TypeAnnotationModifier::class.java) + CoreApplicationEnvironment.registerExtensionPoint(area, MetaLanguage.EP_NAME, MetaLanguage::class.java) + + IdeaExtensionPoints.registerVersionSpecificAppExtensionPoints(area) + } + + private fun registerApplicationExtensionPointsAndExtensionsFrom(configuration: CompilerConfiguration, configFilePath: String) { + workaroundIbmJdkStaxReportCdataEventIssue() + + fun File.hasConfigFile(configFile: String): Boolean = + if (isDirectory) File(this, "META-INF" + File.separator + configFile).exists() + else try { + ZipFile(this).use { + it.getEntry("META-INF/$configFile") != null + } + } catch (e: Throwable) { + false + } + + val pluginRoot = + configuration.get(CLIConfigurationKeys.INTELLIJ_PLUGIN_ROOT)?.let(::File) + ?: PathUtil.getResourcePathForClass(this::class.java).takeIf { it.hasConfigFile(configFilePath) } + // hack for load extensions when compiler run directly from project directory (e.g. in tests) + ?: File("compiler/cli/cli-common/resources").takeIf { it.hasConfigFile(configFilePath) } + ?: throw IllegalStateException( + "Unable to find extension point configuration $configFilePath " + + "(cp:\n ${(Thread.currentThread().contextClassLoader as? UrlClassLoader)?.urls?.joinToString("\n ") { it.file }})" + ) + + CoreApplicationEnvironment.registerExtensionPointAndExtensions(pluginRoot, configFilePath, Extensions.getRootArea()) + } + + private fun workaroundIbmJdkStaxReportCdataEventIssue() { + if (!SystemInfo.isIbmJvm) return + + // On IBM JDK, XMLInputFactory does not support "report-cdata-event" property, but JDOMUtil sets it unconditionally in the + // static XML_INPUT_FACTORY field and fails with an exception (Logger.error throws exception in the compiler) if unsuccessful. + // Until this is fixed in the platform, we workaround the issue by setting that field to a value that does not attempt + // to set the unsupported property. + // See IDEA-206446 for more information + val field = JDOMUtil::class.java.getDeclaredField("XML_INPUT_FACTORY") + field.isAccessible = true + Field::class.java.getDeclaredField("modifiers") + .apply { isAccessible = true } + .setInt(field, field.modifiers and Modifier.FINAL.inv()) + field.set(null, object : NotNullLazyValue() { + override fun compute(): XMLInputFactory { + val factory: XMLInputFactory = try { + // otherwise wst can be used (in tests/dev run) + val clazz = Class.forName("com.sun.xml.internal.stream.XMLInputFactoryImpl") + clazz.newInstance() as XMLInputFactory + } catch (e: Exception) { + // ok, use random + XMLInputFactory.newFactory() + } + + factory.setProperty(XMLInputFactory.IS_COALESCING, true) + factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false) + factory.setProperty(XMLInputFactory.SUPPORT_DTD, false) + return factory + } + }) + } + + @JvmStatic + @Suppress("MemberVisibilityCanPrivate") // made public for CLI Android Lint + fun registerPluginExtensionPoints(project: MockProject) { + ExpressionCodegenExtension.registerExtensionPoint(project) + SyntheticResolveExtension.registerExtensionPoint(project) + ClassBuilderInterceptorExtension.registerExtensionPoint(project) + AnalysisHandlerExtension.registerExtensionPoint(project) + PackageFragmentProviderExtension.registerExtensionPoint(project) + StorageComponentContainerContributor.registerExtensionPoint(project) + DeclarationAttributeAltererExtension.registerExtensionPoint(project) + PreprocessedVirtualFileFactoryExtension.registerExtensionPoint(project) + JsSyntheticTranslateExtension.registerExtensionPoint(project) + CompilerConfigurationExtension.registerExtensionPoint(project) + CollectAdditionalSourcesExtension.registerExtensionPoint(project) + ExtraImportsProviderExtension.registerExtensionPoint(project) + IrGenerationExtension.registerExtensionPoint(project) + ScriptEvaluationExtension.registerExtensionPoint(project) + ShellExtension.registerExtensionPoint(project) + } + + internal fun registerExtensionsFromPlugins(project: MockProject, configuration: CompilerConfiguration) { + val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) + for (registrar in configuration.getList(ComponentRegistrar.PLUGIN_COMPONENT_REGISTRARS)) { + try { + registrar.registerProjectComponents(project, configuration) + } catch (e: AbstractMethodError) { + val message = "The provided plugin ${registrar.javaClass.name} is not compatible with this version of compiler" + // Since the scripting plugin is often discovered in the compiler environment, it is often taken from the incompatible + // location, and in many cases this is not a fatal error, therefore strong warning is generated instead of exception + if (registrar.javaClass.simpleName == "ScriptingCompilerConfigurationComponentRegistrar") { + messageCollector?.report(STRONG_WARNING, "Default scripting plugin is disabled: $message") + } else { + throw IllegalStateException(message, e) + } + } + } + } + + + private fun registerApplicationServicesForCLI(applicationEnvironment: JavaCoreApplicationEnvironment) { + // ability to get text from annotations xml files + applicationEnvironment.registerFileType(PlainTextFileType.INSTANCE, "xml") + applicationEnvironment.registerParserDefinition(JavaParserDefinition()) + } + + // made public for Upsource + @Suppress("MemberVisibilityCanBePrivate") + @JvmStatic + fun registerApplicationServices(applicationEnvironment: JavaCoreApplicationEnvironment) { + with(applicationEnvironment) { + registerFileType(KotlinFileType.INSTANCE, "kt") + registerFileType(KotlinFileType.INSTANCE, KotlinParserDefinition.STD_SCRIPT_SUFFIX) + registerParserDefinition(KotlinParserDefinition()) + application.registerService(KotlinBinaryClassCache::class.java, KotlinBinaryClassCache()) + application.registerService(JavaClassSupers::class.java, JavaClassSupersImpl::class.java) + application.registerService(TransactionGuard::class.java, TransactionGuardImpl::class.java) + } + } + + @JvmStatic + fun registerProjectExtensionPoints(area: ExtensionsArea) { + CoreApplicationEnvironment.registerExtensionPoint( + area, PsiTreeChangePreprocessor.EP_NAME, PsiTreeChangePreprocessor::class.java + ) + CoreApplicationEnvironment.registerExtensionPoint(area, PsiElementFinder.EP_NAME, PsiElementFinder::class.java) + + IdeaExtensionPoints.registerVersionSpecificProjectExtensionPoints(area) + } + + // made public for Upsource + @JvmStatic + @Deprecated("Use registerProjectServices(project) instead.", ReplaceWith("registerProjectServices(projectEnvironment.project)")) + fun registerProjectServices(projectEnvironment: JavaCoreProjectEnvironment, messageCollector: MessageCollector?) { + registerProjectServices(projectEnvironment.project) + } + + // made public for Android Lint + @JvmStatic + fun registerProjectServices(project: MockProject) { + with(project) { + registerService(KotlinJavaPsiFacade::class.java, KotlinJavaPsiFacade(this)) + registerService(FacadeCache::class.java, FacadeCache(this)) + registerService(ModuleAnnotationsResolver::class.java, CliModuleAnnotationsResolver()) + } + } + + private fun registerProjectServicesForCLI(@Suppress("UNUSED_PARAMETER") projectEnvironment: JavaCoreProjectEnvironment) { + /** + * Note that Kapt may restart code analysis process, and CLI services should be aware of that. + * Use PsiManager.getModificationTracker() to ensure that all the data you cached is still valid. + */ + } + + // made public for Android Lint + @JvmStatic + fun registerKotlinLightClassSupport(project: MockProject) { + with(project) { + val traceHolder = CliTraceHolder() + val cliLightClassGenerationSupport = CliLightClassGenerationSupport(traceHolder) + val kotlinAsJavaSupport = CliKotlinAsJavaSupport(this, traceHolder) + registerService(LightClassGenerationSupport::class.java, cliLightClassGenerationSupport) + registerService(CliLightClassGenerationSupport::class.java, cliLightClassGenerationSupport) + registerService(KotlinAsJavaSupport::class.java, kotlinAsJavaSupport) + registerService(CodeAnalyzerInitializer::class.java, traceHolder) + + val area = Extensions.getArea(this) + + area.getExtensionPoint(PsiElementFinder.EP_NAME).registerExtension(JavaElementFinder(this, kotlinAsJavaSupport)) + area.getExtensionPoint(PsiElementFinder.EP_NAME).registerExtension( + PsiElementFinderImpl(this, ServiceManager.getService(this, JavaFileManager::class.java)) + ) + } + } + + private fun CompilerConfiguration.setupJdkClasspathRoots(configFiles: EnvironmentConfigFiles) { + if (getBoolean(JVMConfigurationKeys.NO_JDK)) return + + val jvmTarget = configFiles == EnvironmentConfigFiles.JVM_CONFIG_FILES + if (!jvmTarget) return + + val jdkHome = get(JVMConfigurationKeys.JDK_HOME) + val (javaRoot, classesRoots) = if (jdkHome == null) { + val javaHome = File(System.getProperty("java.home")) + put(JVMConfigurationKeys.JDK_HOME, javaHome) + + javaHome to PathUtil.getJdkClassesRootsFromCurrentJre() + } else { + jdkHome to PathUtil.getJdkClassesRoots(jdkHome) + } + + if (!CoreJrtFileSystem.isModularJdk(javaRoot)) { + if (classesRoots.isEmpty()) { + report(ERROR, "No class roots are found in the JDK path: $javaRoot") + } else { + addJvmSdkRoots(classesRoots) + } + } + } + } +} + diff --git a/gradle/versions.properties.193 b/gradle/versions.properties.193 new file mode 100644 index 00000000000..8ddf09c8573 --- /dev/null +++ b/gradle/versions.properties.193 @@ -0,0 +1,15 @@ +versions.intellijSdk=193.2956.37-EAP-SNAPSHOT +versions.androidBuildTools=r23.0.1 +versions.idea.NodeJS=181.3494.12 +versions.jar.asm-all=7.0.1 +versions.jar.guava=25.1-jre +versions.jar.groovy-all=2.4.17 +versions.jar.lombok-ast=0.2.3 +versions.jar.swingx-core=1.6.2-2 +versions.jar.kxml2=2.3.0 +versions.jar.streamex=0.6.8 +versions.jar.gson=2.8.5 +versions.jar.oro=2.0.8 +versions.jar.picocontainer=1.2 +ignore.jar.snappy-in-java=true +versions.gradle-api=4.5.1 diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/JavaClassesInScriptDependenciesShortNameCache.kt.193 b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/JavaClassesInScriptDependenciesShortNameCache.kt.193 new file mode 100644 index 00000000000..a3da71200b7 --- /dev/null +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/dependencies/JavaClassesInScriptDependenciesShortNameCache.kt.193 @@ -0,0 +1,58 @@ +/* + * Copyright 2010-2016 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.idea.core.script.dependencies + +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiField +import com.intellij.psi.PsiMethod +import com.intellij.psi.impl.java.stubs.index.JavaShortClassNameIndex +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.search.PsiShortNamesCache +import com.intellij.psi.stubs.StubIndex +import com.intellij.util.Processor +import com.intellij.util.containers.HashSet +import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager + +// Allow searching java classes in jars in script dependencies, this is needed for stuff like completion and autoimport +class JavaClassesInScriptDependenciesShortNameCache(private val project: Project) : PsiShortNamesCache() { + override fun getAllClassNames() = emptyArray() + + override fun getAllClassNames(dest: HashSet) {} + + override fun getClassesByName(name: String, scope: GlobalSearchScope): Array { + val classpathScope = ScriptConfigurationManager.getInstance(project).getAllScriptsDependenciesClassFilesScope() + val classes = StubIndex.getElements( + JavaShortClassNameIndex.getInstance().key, name, project, classpathScope.intersectWith(scope), PsiClass::class.java + ) + return classes.toTypedArray() + } + + override fun getMethodsByName(name: String, scope: GlobalSearchScope) = PsiMethod.EMPTY_ARRAY + + override fun getAllMethodNames() = emptyArray() + + override fun getFieldsByName(name: String, scope: GlobalSearchScope) = PsiField.EMPTY_ARRAY + + override fun getMethodsByNameIfNotMoreThan(name: String, scope: GlobalSearchScope, maxCount: Int) = PsiMethod.EMPTY_ARRAY + + override fun processMethodsWithName(name: String, scope: GlobalSearchScope, processor: Processor) = true + + override fun getAllFieldNames() = emptyArray() + + override fun getFieldsByNameIfNotMoreThan(name: String, scope: GlobalSearchScope, maxCount: Int) = PsiField.EMPTY_ARRAY +} \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/actions/ShowKotlinGradleDslLogs.kt.193 b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/actions/ShowKotlinGradleDslLogs.kt.193 new file mode 100644 index 00000000000..5a89cf81a6b --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/actions/ShowKotlinGradleDslLogs.kt.193 @@ -0,0 +1,84 @@ +/* + * Copyright 2010-2019 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.idea.actions + +import com.intellij.codeInsight.intention.IntentionAction +import com.intellij.ide.actions.ShowFilePathAction +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.DumbAware +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.MessageType +import com.intellij.openapi.ui.popup.JBPopupFactory +import com.intellij.openapi.util.SystemInfo +import com.intellij.openapi.wm.WindowManager +import com.intellij.psi.PsiFile +import com.intellij.ui.BrowserHyperlinkListener +import java.io.File + +class ShowKotlinGradleDslLogs : IntentionAction, AnAction(), DumbAware { + override fun invoke(project: Project, editor: Editor?, file: PsiFile?) { + openLogsDirIfPresent(project) + } + + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return + openLogsDirIfPresent(project) + } + + override fun isAvailable(project: Project, editor: Editor?, file: PsiFile?) = ShowFilePathAction.isSupported() + + override fun update(e: AnActionEvent) { + val presentation = e.presentation + presentation.isEnabledAndVisible = e.project != null && ShowFilePathAction.isSupported() + presentation.text = NAME + } + + private fun openLogsDirIfPresent(project: Project) { + val logsDir = findLogsDir() + if (logsDir != null) { + ShowFilePathAction.openDirectory(logsDir) + } else { + val parent = WindowManager.getInstance().getStatusBar(project)?.component + ?: WindowManager.getInstance().findVisibleFrame().rootPane + JBPopupFactory.getInstance() + .createHtmlTextBalloonBuilder( + "Gradle DSL Logs cannot be found automatically.
See how to find logs here.", + MessageType.ERROR, + BrowserHyperlinkListener.INSTANCE + ) + .setFadeoutTime(5000) + .createBalloon() + .showInCenterOf(parent) + } + } + + /** The way how to find Gradle logs is described here + * @see org.jetbrains.kotlin.idea.actions.ShowKotlinGradleDslLogs.gradleTroubleshootingLink + */ + private fun findLogsDir(): File? { + val userHome = System.getProperty("user.home") + return when { + SystemInfo.isMac -> File("$userHome/Library/Logs/gradle-kotlin-dsl") + SystemInfo.isLinux -> File("$userHome/.gradle-kotlin-dsl/logs") + SystemInfo.isWindows -> File("$userHome/AppData/Local/gradle-kotlin-dsl/log") + else -> null + }.takeIf { it?.exists() == true } + } + + override fun startInWriteAction() = false + + override fun getText() = NAME + + override fun getFamilyName() = NAME + + companion object { + private const val gradleTroubleshootingLink = "https://docs.gradle.org/current/userguide/kotlin_dsl.html#troubleshooting" + + val NAME = "Show Kotlin Gradle DSL Logs in ${ShowFilePathAction.getFileManagerName()}" + } +} \ No newline at end of file diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/RunScratchAction.kt.193 b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/RunScratchAction.kt.193 new file mode 100644 index 00000000000..bd7ec9cc219 --- /dev/null +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/scratch/actions/RunScratchAction.kt.193 @@ -0,0 +1,115 @@ +/* + * Copyright 2010-2017 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.idea.scratch.actions + +import com.intellij.icons.AllIcons +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.keymap.KeymapManager +import com.intellij.openapi.keymap.KeymapUtil +import com.intellij.openapi.project.DumbService +import com.intellij.task.ProjectTaskContext +import com.intellij.task.ProjectTaskManager +import com.intellij.task.ProjectTaskNotification +import com.intellij.task.ProjectTaskResult +import org.jetbrains.kotlin.idea.KotlinBundle +import org.jetbrains.kotlin.idea.scratch.* +import org.jetbrains.kotlin.idea.scratch.printDebugMessage +import org.jetbrains.kotlin.idea.scratch.LOG as log + +class RunScratchAction : ScratchAction( + KotlinBundle.message("scratch.run.button"), + AllIcons.Actions.Execute +) { + + init { + KeymapManager.getInstance().activeKeymap.getShortcuts("Kotlin.RunScratch").firstOrNull()?.let { + templatePresentation.text += " (${KeymapUtil.getShortcutText(it)})" + } + } + + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return + val scratchFile = getScratchFileFromSelectedEditor(project) ?: return + + doAction(scratchFile, false) + } + + companion object { + fun doAction(scratchFile: ScratchFile, isAutoRun: Boolean) { + val isRepl = scratchFile.options.isRepl + val executor = (if (isRepl) scratchFile.replScratchExecutor else scratchFile.compilingScratchExecutor) ?: return + + log.printDebugMessage("Run Action: isRepl = $isRepl") + + fun executeScratch() { + try { + if (isAutoRun && executor is SequentialScratchExecutor) { + executor.executeNew() + } else { + executor.execute() + } + } catch (ex: Throwable) { + executor.errorOccurs("Exception occurs during Run Scratch Action", ex, true) + } + } + + val isMakeBeforeRun = scratchFile.options.isMakeBeforeRun + log.printDebugMessage("Run Action: isMakeBeforeRun = $isMakeBeforeRun") + + val module = scratchFile.module + log.printDebugMessage("Run Action: module = ${module?.name}") + + if (!isAutoRun && module != null && isMakeBeforeRun) { + val project = scratchFile.project + ProjectTaskManager.getInstance(project).build(arrayOf(module), object : ProjectTaskNotification { + override fun finished(context: ProjectTaskContext, executionResult: ProjectTaskResult) { + if (executionResult.isAborted || executionResult.errors > 0) { + executor.errorOccurs("There were compilation errors in module ${module.name}") + } + + if (DumbService.isDumb(project)) { + DumbService.getInstance(project).smartInvokeLater { + executeScratch() + } + } else { + executeScratch() + } + } + }) + } else { + executeScratch() + } + } + } + + override fun update(e: AnActionEvent) { + super.update(e) + + e.presentation.isEnabled = !ScratchCompilationSupport.isAnyInProgress() + + if (e.presentation.isEnabled) { + e.presentation.text = templatePresentation.text + } else { + e.presentation.text = "Other Scratch file execution is in progress" + } + + val project = e.project ?: return + val scratchFile = getScratchFileFromSelectedEditor(project) ?: return + + e.presentation.isVisible = !ScratchCompilationSupport.isInProgress(scratchFile) + } +} \ No newline at end of file diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/console/ConsoleCompilerHelper.kt.193 b/idea/idea-repl/src/org/jetbrains/kotlin/console/ConsoleCompilerHelper.kt.193 new file mode 100644 index 00000000000..02f938dbdd8 --- /dev/null +++ b/idea/idea-repl/src/org/jetbrains/kotlin/console/ConsoleCompilerHelper.kt.193 @@ -0,0 +1,54 @@ +/* + * 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.console + +import com.intellij.execution.ExecutionManager +import com.intellij.execution.Executor +import com.intellij.execution.ui.RunContentDescriptor +import com.intellij.openapi.compiler.CompilerManager +import com.intellij.openapi.module.Module +import com.intellij.openapi.project.Project +import com.intellij.task.ProjectTaskContext +import com.intellij.task.ProjectTaskManager +import com.intellij.task.ProjectTaskNotification +import com.intellij.task.ProjectTaskResult + +class ConsoleCompilerHelper( + private val project: Project, + private val module: Module, + private val executor: Executor, + private val contentDescriptor: RunContentDescriptor +) { + + fun moduleIsUpToDate(): Boolean { + val compilerManager = CompilerManager.getInstance(project) + val compilerScope = compilerManager.createModuleCompileScope(module, true) + return compilerManager.isUpToDate(compilerScope) + } + + fun compileModule() { + if (ExecutionManager.getInstance(project).contentManager.removeRunContent(executor, contentDescriptor)) { + ProjectTaskManager.getInstance(project).build(arrayOf(module), object : ProjectTaskNotification { + override fun finished(context: ProjectTaskContext, executionResult: ProjectTaskResult) { + if (!module.isDisposed) { + KotlinConsoleKeeper.getInstance(project).run(module, previousCompilationFailed = executionResult.errors > 0) + } + } + }) + } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/migration/kotlinMigrationProfile.kt.193 b/idea/src/org/jetbrains/kotlin/idea/migration/kotlinMigrationProfile.kt.193 new file mode 100644 index 00000000000..2b0968a71ae --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/migration/kotlinMigrationProfile.kt.193 @@ -0,0 +1,67 @@ +/* + * Copyright 2010-2018 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.idea.migration + +import com.intellij.codeInspection.ex.InspectionManagerEx +import com.intellij.codeInspection.ex.InspectionProfileImpl +import com.intellij.codeInspection.ex.InspectionToolWrapper +import com.intellij.codeInspection.ex.createSimple +import com.intellij.openapi.util.InvalidDataException +import com.intellij.openapi.util.WriteExternalException +import com.intellij.profile.codeInspection.InspectionProfileManager +import com.intellij.psi.PsiElement +import org.jdom.Element +import org.jetbrains.kotlin.idea.configuration.MigrationInfo +import org.jetbrains.kotlin.idea.quickfix.migration.MigrationFix +import java.util.* + +fun createMigrationProfile( + managerEx: InspectionManagerEx, + psiElement: PsiElement?, + migrationInfo: MigrationInfo? = null +): InspectionProfileImpl { + val rootProfile = InspectionProfileManager.getInstance().currentProfile + + val migrationFixWrappers = applicableMigrationToolsImpl(migrationInfo) + + val allWrappers = LinkedHashSet>() + for (toolWrapper in migrationFixWrappers) { + allWrappers.add(toolWrapper) + rootProfile.collectDependentInspections(toolWrapper, allWrappers, managerEx.project) + } + + val model = createSimple("Migration", managerEx.project, migrationFixWrappers) + try { + val element = Element("toCopy") + for (wrapper in migrationFixWrappers) { + wrapper.tool.writeSettings(element) + val tw = (if (psiElement == null) + model.getInspectionTool(wrapper.shortName, managerEx.project) + else + model.getInspectionTool(wrapper.shortName, psiElement))!! + + tw.tool.readSettings(element) + } + } catch (ignored: WriteExternalException) { + } catch (ignored: InvalidDataException) { + } + + return model +} + +fun applicableMigrationTools(migrationInfo: MigrationInfo) = applicableMigrationToolsImpl(migrationInfo) + +private fun applicableMigrationToolsImpl(migrationInfo: MigrationInfo?): List> { + val rootProfile = InspectionProfileManager.getInstance().currentProfile + + return rootProfile.allTools.asSequence() + .map { it.tool } + .filter { toolWrapper: InspectionToolWrapper<*, *> -> + val tool = toolWrapper.tool + tool is MigrationFix && (migrationInfo == null || tool.isApplicable(migrationInfo)) + } + .toList() +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/kotlinRefactoringUtil.kt.193 b/idea/src/org/jetbrains/kotlin/idea/refactoring/kotlinRefactoringUtil.kt.193 new file mode 100644 index 00000000000..d7662ac0029 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/kotlinRefactoringUtil.kt.193 @@ -0,0 +1,1045 @@ +/* + * Copyright 2000-2018 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.idea.refactoring + +import com.intellij.codeInsight.daemon.impl.quickfix.CreateFromUsageUtils +import com.intellij.codeInsight.unwrap.RangeSplitter +import com.intellij.codeInsight.unwrap.UnwrapHandler +import com.intellij.ide.IdeBundle +import com.intellij.ide.util.PsiElementListCellRenderer +import com.intellij.lang.injection.InjectedLanguageManager +import com.intellij.lang.java.JavaLanguage +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.TransactionGuard +import com.intellij.openapi.command.CommandAdapter +import com.intellij.openapi.command.CommandEvent +import com.intellij.openapi.command.CommandProcessor +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.colors.EditorColors +import com.intellij.openapi.editor.colors.EditorColorsManager +import com.intellij.openapi.editor.markup.HighlighterTargetArea +import com.intellij.openapi.editor.markup.RangeHighlighter +import com.intellij.openapi.editor.markup.TextAttributes +import com.intellij.openapi.options.ConfigurationException +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.openapi.ui.Messages +import com.intellij.openapi.ui.popup.* +import com.intellij.openapi.util.Pass +import com.intellij.openapi.util.TextRange +import com.intellij.openapi.util.text.StringUtil +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.* +import com.intellij.psi.impl.file.PsiPackageBase +import com.intellij.psi.impl.light.LightElement +import com.intellij.psi.presentation.java.SymbolPresentationUtil +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.refactoring.BaseRefactoringProcessor.ConflictsInTestsException +import com.intellij.refactoring.changeSignature.ChangeSignatureUtil +import com.intellij.refactoring.listeners.RefactoringEventData +import com.intellij.refactoring.listeners.RefactoringEventListener +import com.intellij.refactoring.ui.ConflictsDialog +import com.intellij.refactoring.util.ConflictsUtil +import com.intellij.refactoring.util.RefactoringUIUtil +import com.intellij.ui.components.JBList +import com.intellij.usageView.UsageViewTypeLocation +import com.intellij.util.VisibilityUtil +import com.intellij.util.containers.MultiMap +import org.jetbrains.kotlin.asJava.LightClassUtil +import org.jetbrains.kotlin.asJava.elements.KtLightMethod +import org.jetbrains.kotlin.asJava.getAccessorLightMethods +import org.jetbrains.kotlin.asJava.namedUnwrappedElement +import org.jetbrains.kotlin.asJava.toLightClass +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor +import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor +import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.idea.KotlinBundle +import org.jetbrains.kotlin.idea.KotlinFileType +import org.jetbrains.kotlin.idea.KotlinLanguage +import org.jetbrains.kotlin.idea.analysis.analyzeAsReplacement +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny +import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.util.getJavaMemberDescriptor +import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde +import org.jetbrains.kotlin.idea.core.* +import org.jetbrains.kotlin.idea.core.util.showYesNoCancelDialog +import org.jetbrains.kotlin.idea.project.languageVersionSettings +import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinValVar +import org.jetbrains.kotlin.idea.refactoring.changeSignature.toValVar +import org.jetbrains.kotlin.idea.refactoring.memberInfo.KtPsiClassWrapper +import org.jetbrains.kotlin.idea.refactoring.rename.canonicalRender +import org.jetbrains.kotlin.idea.roots.isOutsideKotlinAwareSourceRoot +import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers +import org.jetbrains.kotlin.idea.util.ProjectRootsUtil +import org.jetbrains.kotlin.idea.util.actualsForExpected +import org.jetbrains.kotlin.idea.util.liftToExpected +import org.jetbrains.kotlin.idea.util.string.collapseSpaces +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.FqNameUnsafe +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.* +import org.jetbrains.kotlin.renderer.DescriptorRenderer +import org.jetbrains.kotlin.resolve.* +import org.jetbrains.kotlin.resolve.calls.callUtil.getCallWithAssert +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver +import org.jetbrains.kotlin.resolve.source.getPsi +import java.lang.annotation.Retention +import java.util.* +import javax.swing.Icon +import kotlin.math.min + +import org.jetbrains.kotlin.idea.core.util.getLineCount as newGetLineCount +import org.jetbrains.kotlin.idea.core.util.toPsiDirectory as newToPsiDirectory +import org.jetbrains.kotlin.idea.core.util.toPsiFile as newToPsiFile + +const val CHECK_SUPER_METHODS_YES_NO_DIALOG = "CHECK_SUPER_METHODS_YES_NO_DIALOG" + +@JvmOverloads +fun getOrCreateKotlinFile( + fileName: String, + targetDir: PsiDirectory, + packageName: String? = targetDir.getFqNameWithImplicitPrefix()?.asString() +): KtFile? = + (targetDir.findFile(fileName) ?: createKotlinFile(fileName, targetDir, packageName)) as? KtFile + +fun createKotlinFile( + fileName: String, + targetDir: PsiDirectory, + packageName: String? = targetDir.getFqNameWithImplicitPrefix()?.asString() +): KtFile { + targetDir.checkCreateFile(fileName) + val packageFqName = packageName?.let(::FqName) ?: FqName.ROOT + val file = PsiFileFactory.getInstance(targetDir.project).createFileFromText( + fileName, KotlinFileType.INSTANCE, if (!packageFqName.isRoot) "package ${packageFqName.quoteSegmentsIfNeeded()} \n\n" else "" + ) + + return targetDir.add(file) as KtFile +} + +fun PsiElement.getUsageContext(): PsiElement { + return when (this) { + is KtElement -> PsiTreeUtil.getParentOfType( + this, + KtPropertyAccessor::class.java, + KtProperty::class.java, + KtNamedFunction::class.java, + KtConstructor::class.java, + KtClassOrObject::class.java + ) ?: containingFile + else -> ConflictsUtil.getContainer(this) + } +} + +fun PsiElement.isInKotlinAwareSourceRoot(): Boolean = + !isOutsideKotlinAwareSourceRoot(containingFile) + +fun KtFile.createTempCopy(text: String? = null): KtFile { + val tmpFile = KtPsiFactory(this).createAnalyzableFile(name, text ?: this.text ?: "", this) + tmpFile.originalFile = this + return tmpFile +} + +fun PsiElement.getAllExtractionContainers(strict: Boolean = true): List { + val containers = ArrayList() + + var objectOrNonInnerNestedClassFound = false + val parents = if (strict) parents else parentsWithSelf + for (element in parents) { + val isValidContainer = when (element) { + is KtFile -> true + is KtClassBody -> !objectOrNonInnerNestedClassFound || element.parent is KtObjectDeclaration + is KtBlockExpression -> !objectOrNonInnerNestedClassFound + else -> false + } + if (!isValidContainer) continue + + containers.add(element as KtElement) + + if (!objectOrNonInnerNestedClassFound) { + val bodyParent = (element as? KtClassBody)?.parent + objectOrNonInnerNestedClassFound = + (bodyParent is KtObjectDeclaration && !bodyParent.isObjectLiteral()) + || (bodyParent is KtClass && !bodyParent.isInner()) + } + } + + return containers +} + +fun PsiElement.getExtractionContainers(strict: Boolean = true, includeAll: Boolean = false): List { + fun getEnclosingDeclaration(element: PsiElement, strict: Boolean): PsiElement? { + return (if (strict) element.parents else element.parentsWithSelf) + .filter { + (it is KtDeclarationWithBody && it !is KtFunctionLiteral && !(it is KtNamedFunction && it.name == null)) + || it is KtAnonymousInitializer + || it is KtClassBody + || it is KtFile + } + .firstOrNull() + } + + if (includeAll) return getAllExtractionContainers(strict) + + val enclosingDeclaration = getEnclosingDeclaration(this, strict)?.let { + if (it is KtDeclarationWithBody || it is KtAnonymousInitializer) getEnclosingDeclaration(it, true) else it + } + + return when (enclosingDeclaration) { + is KtFile -> Collections.singletonList(enclosingDeclaration) + is KtClassBody -> getAllExtractionContainers(strict).filterIsInstance() + else -> { + val targetContainer = when (enclosingDeclaration) { + is KtDeclarationWithBody -> enclosingDeclaration.bodyExpression + is KtAnonymousInitializer -> enclosingDeclaration.body + else -> null + } + if (targetContainer is KtBlockExpression) Collections.singletonList(targetContainer) else Collections.emptyList() + } + } +} + +fun Project.checkConflictsInteractively( + conflicts: MultiMap, + onShowConflicts: () -> Unit = {}, + onAccept: () -> Unit +) { + if (!conflicts.isEmpty) { + if (ApplicationManager.getApplication()!!.isUnitTestMode) throw ConflictsInTestsException(conflicts.values()) + + val dialog = ConflictsDialog(this, conflicts) { onAccept() } + dialog.show() + if (!dialog.isOK) { + if (dialog.isShowConflicts) { + onShowConflicts() + } + return + } + } + + onAccept() +} + +fun reportDeclarationConflict( + conflicts: MultiMap, + declaration: PsiElement, + message: (renderedDeclaration: String) -> String +) { + conflicts.putValue(declaration, message(RefactoringUIUtil.getDescription(declaration, true).capitalize())) +} + +fun getPsiElementPopup( + editor: Editor, + elements: List, + renderer: PsiElementListCellRenderer, + title: String?, + highlightSelection: Boolean, + toPsi: (T) -> E, + processor: (T) -> Boolean +): JBPopup { + val highlighter = if (highlightSelection) SelectionAwareScopeHighlighter(editor) else null + + val list = JBList(elements.map(toPsi)) + list.cellRenderer = renderer + list.addListSelectionListener { + highlighter?.dropHighlight() + val index = list.selectedIndex + if (index >= 0) { + highlighter?.highlight(list.model!!.getElementAt(index) as PsiElement) + } + } + + return with(PopupChooserBuilder(list)) { + title?.let { setTitle(it) } + renderer.installSpeedSearch(this, true) + setItemChoosenCallback { + val index = list.selectedIndex + if (index >= 0) { + processor(elements[index]) + } + } + addListener(object : JBPopupAdapter() { + override fun onClosed(event: LightweightWindowEvent) { + highlighter?.dropHighlight() + } + }) + + createPopup() + } +} + +class SelectionAwareScopeHighlighter(val editor: Editor) { + private val highlighters = ArrayList() + + private fun addHighlighter(r: TextRange, attr: TextAttributes) { + highlighters.add( + editor.markupModel.addRangeHighlighter( + r.startOffset, + r.endOffset, + UnwrapHandler.HIGHLIGHTER_LEVEL, + attr, + HighlighterTargetArea.EXACT_RANGE + ) + ) + } + + fun highlight(wholeAffected: PsiElement) { + dropHighlight() + + val affectedRange = wholeAffected.textRange ?: return + + val attributes = EditorColorsManager.getInstance().globalScheme.getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES)!! + val selectedRange = with(editor.selectionModel) { TextRange(selectionStart, selectionEnd) } + for (r in RangeSplitter.split(affectedRange, Collections.singletonList(selectedRange))) { + addHighlighter(r, attributes) + } + } + + fun dropHighlight() { + highlighters.forEach { it.dispose() } + highlighters.clear() + } +} + +@Deprecated( + "Use org.jetbrains.kotlin.idea.core.util.getLineStartOffset() instead", + ReplaceWith("this.getLineStartOffset(line)", "org.jetbrains.kotlin.idea.core.util.getLineStartOffset"), + DeprecationLevel.ERROR +) +fun PsiFile.getLineStartOffset(line: Int): Int? { + val doc = viewProvider.document ?: PsiDocumentManager.getInstance(project).getDocument(this) + if (doc != null && line >= 0 && line < doc.lineCount) { + val startOffset = doc.getLineStartOffset(line) + val element = findElementAt(startOffset) ?: return startOffset + + if (element is PsiWhiteSpace || element is PsiComment) { + return PsiTreeUtil.skipSiblingsForward(element, PsiWhiteSpace::class.java, PsiComment::class.java)?.startOffset ?: startOffset + } + return startOffset + } + + return null +} + +@Deprecated( + "Use org.jetbrains.kotlin.idea.core.util.getLineEndOffset() instead", + ReplaceWith("this.getLineEndOffset(line)", "org.jetbrains.kotlin.idea.core.util.getLineEndOffset"), + DeprecationLevel.ERROR +) +fun PsiFile.getLineEndOffset(line: Int): Int? { + val document = viewProvider.document ?: PsiDocumentManager.getInstance(project).getDocument(this) + return document?.getLineEndOffset(line) +} + +fun PsiElement.getLineNumber(start: Boolean = true): Int { + val document = containingFile.viewProvider.document ?: PsiDocumentManager.getInstance(project).getDocument(containingFile) + val index = if (start) this.startOffset else this.endOffset + if (index > document?.textLength ?: 0) return 0 + return document?.getLineNumber(index) ?: 0 +} + +class SeparateFileWrapper(manager: PsiManager) : LightElement(manager, KotlinLanguage.INSTANCE) { + override fun toString() = "" +} + +fun chooseContainerElement( + containers: List, + editor: Editor, + title: String, + highlightSelection: Boolean, + toPsi: (T) -> PsiElement, + onSelect: (T) -> Unit +) { + val popup = getPsiElementPopup( + editor, + containers, + object : PsiElementListCellRenderer() { + private fun PsiElement.renderName(): String = when { + this is KtPropertyAccessor -> property.renderName() + if (isGetter) ".get" else ".set" + this is KtObjectDeclaration && isCompanion() -> { + val name = getStrictParentOfType()?.renderName() ?: "" + "Companion object of $name" + } + else -> (this as? PsiNamedElement)?.name ?: "" + } + + private fun PsiElement.renderDeclaration(): String? { + if (this is KtFunctionLiteral || isFunctionalExpression()) return renderText() + + val descriptor = when { + this is KtFile -> name + this is KtElement -> analyze()[BindingContext.DECLARATION_TO_DESCRIPTOR, this] + this is PsiMember -> getJavaMemberDescriptor() + else -> null + } ?: return null + val name = renderName() + val params = (descriptor as? FunctionDescriptor)?.valueParameters?.joinToString( + ", ", + "(", + ")" + ) { DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(it.type) } ?: "" + return "$name$params" + } + + private fun PsiElement.renderText(): String = when (this) { + is SeparateFileWrapper -> "Extract to separate file" + is PsiPackageBase -> qualifiedName + else -> { + val text = text ?: "" + StringUtil.shortenTextWithEllipsis(text.collapseSpaces(), 53, 0) + } + } + + private fun PsiElement.getRepresentativeElement(): PsiElement = when (this) { + is KtBlockExpression -> (parent as? KtDeclarationWithBody) ?: this + is KtClassBody -> parent as KtClassOrObject + else -> this + } + + override fun getElementText(element: PsiElement): String? { + val representativeElement = element.getRepresentativeElement() + return representativeElement.renderDeclaration() ?: representativeElement.renderText() + } + + override fun getContainerText(element: PsiElement, name: String?): String? = null + + override fun getIconFlags(): Int = 0 + + override fun getIcon(element: PsiElement): Icon? = + super.getIcon(element.getRepresentativeElement()) + }, + title, + highlightSelection, + toPsi, + { + onSelect(it) + true + } + ) + ApplicationManager.getApplication().invokeLater { + popup.showInBestPositionFor(editor) + } +} + +fun chooseContainerElementIfNecessary( + containers: List, + editor: Editor, + title: String, + highlightSelection: Boolean, + toPsi: (T) -> PsiElement, + onSelect: (T) -> Unit +) { + when { + containers.isEmpty() -> return + containers.size == 1 || ApplicationManager.getApplication()!!.isUnitTestMode -> onSelect(containers.first()) + else -> chooseContainerElement(containers, editor, title, highlightSelection, toPsi, onSelect) + } +} + +fun PsiElement.isTrueJavaMethod(): Boolean = this is PsiMethod && this !is KtLightMethod + +fun PsiElement.canRefactor(): Boolean = when { + !isValid -> false + this is PsiPackage -> directories.any { it.canRefactor() } + this is KtElement || this is PsiMember && language == JavaLanguage.INSTANCE || this is PsiDirectory -> ProjectRootsUtil.isInProjectSource( + this, + includeScriptsOutsideSourceRoots = true + ) + else -> false +} + +private fun copyModifierListItems(from: PsiModifierList, to: PsiModifierList, withPsiModifiers: Boolean = true) { + if (withPsiModifiers) { + for (modifier in PsiModifier.MODIFIERS) { + if (from.hasExplicitModifier(modifier)) { + to.setModifierProperty(modifier, true) + } + } + } + for (annotation in from.annotations) { + val annotationName = annotation.qualifiedName ?: continue + + if (Retention::class.java.name != annotationName) { + to.addAnnotation(annotationName) + } + } +} + +private fun copyTypeParameters( + from: T, + to: T, + inserter: (T, PsiTypeParameterList) -> Unit +) where T : PsiTypeParameterListOwner, T : PsiNameIdentifierOwner { + val factory = PsiElementFactory.SERVICE.getInstance((from as PsiElement).project) + val templateTypeParams = from.typeParameterList?.typeParameters ?: PsiTypeParameter.EMPTY_ARRAY + if (templateTypeParams.isNotEmpty()) { + inserter(to, factory.createTypeParameterList()) + val targetTypeParamList = to.typeParameterList + val newTypeParams = templateTypeParams.map { + factory.createTypeParameter(it.name!!, it.extendsList.referencedTypes) + } + ChangeSignatureUtil.synchronizeList( + targetTypeParamList, + newTypeParams, + { it!!.typeParameters.toList() }, + BooleanArray(newTypeParams.size) + ) + } +} + +fun createJavaMethod(function: KtFunction, targetClass: PsiClass): PsiMethod { + val template = LightClassUtil.getLightClassMethod(function) + ?: throw AssertionError("Can't generate light method: ${function.getElementTextWithContext()}") + return createJavaMethod(template, targetClass) +} + +fun createJavaMethod(template: PsiMethod, targetClass: PsiClass): PsiMethod { + val factory = PsiElementFactory.SERVICE.getInstance(template.project) + val methodToAdd = if (template.isConstructor) { + factory.createConstructor(template.name) + } else { + factory.createMethod(template.name, template.returnType) + } + val method = targetClass.add(methodToAdd) as PsiMethod + + copyModifierListItems(template.modifierList, method.modifierList) + if (targetClass.isInterface) { + method.modifierList.setModifierProperty(PsiModifier.FINAL, false) + } + + copyTypeParameters(template, method) { psiMethod, typeParameterList -> + psiMethod.addAfter(typeParameterList, psiMethod.modifierList) + } + + val targetParamList = method.parameterList + val newParams = template.parameterList.parameters.map { + val param = factory.createParameter(it.name!!, it.type) + copyModifierListItems(it.modifierList!!, param.modifierList!!) + param + } + ChangeSignatureUtil.synchronizeList( + targetParamList, + newParams, + { it.parameters.toList() }, + BooleanArray(newParams.size) + ) + + if (template.modifierList.hasModifierProperty(PsiModifier.ABSTRACT) || targetClass.isInterface) { + method.body!!.delete() + } else if (!template.isConstructor) { + CreateFromUsageUtils.setupMethodBody(method) + } + + return method +} + +fun createJavaField(property: KtNamedDeclaration, targetClass: PsiClass): PsiField { + val accessorLightMethods = property.getAccessorLightMethods() + val template = accessorLightMethods.getter + ?: throw AssertionError("Can't generate light method: ${property.getElementTextWithContext()}") + + val factory = PsiElementFactory.SERVICE.getInstance(template.project) + val field = targetClass.add(factory.createField(property.name!!, template.returnType!!)) as PsiField + + with(field.modifierList!!) { + val templateModifiers = template.modifierList + setModifierProperty(VisibilityUtil.getVisibilityModifier(templateModifiers), true) + if ((property as KtValVarKeywordOwner).valOrVarKeyword.toValVar() != KotlinValVar.Var || targetClass.isInterface) { + setModifierProperty(PsiModifier.FINAL, true) + } + copyModifierListItems(templateModifiers, this, false) + } + + return field +} + +fun createJavaClass(klass: KtClass, targetClass: PsiClass?, forcePlainClass: Boolean = false): PsiClass { + val kind = if (forcePlainClass) ClassKind.CLASS else (klass.unsafeResolveToDescriptor() as ClassDescriptor).kind + + val factory = PsiElementFactory.SERVICE.getInstance(klass.project) + val className = klass.name!! + val javaClassToAdd = when (kind) { + ClassKind.CLASS -> factory.createClass(className) + ClassKind.INTERFACE -> factory.createInterface(className) + ClassKind.ANNOTATION_CLASS -> factory.createAnnotationType(className) + ClassKind.ENUM_CLASS -> factory.createEnum(className) + else -> throw AssertionError("Unexpected class kind: ${klass.getElementTextWithContext()}") + } + val javaClass = (targetClass?.add(javaClassToAdd) ?: javaClassToAdd) as PsiClass + + val template = klass.toLightClass() ?: throw AssertionError("Can't generate light class: ${klass.getElementTextWithContext()}") + + copyModifierListItems(template.modifierList!!, javaClass.modifierList!!) + if (template.isInterface) { + javaClass.modifierList!!.setModifierProperty(PsiModifier.ABSTRACT, false) + } + + copyTypeParameters(template, javaClass) { clazz, typeParameterList -> + clazz.addAfter(typeParameterList, clazz.nameIdentifier) + } + + // Turning interface to class + if (!javaClass.isInterface && template.isInterface) { + val implementsList = factory.createReferenceListWithRole( + template.extendsList?.referenceElements ?: PsiJavaCodeReferenceElement.EMPTY_ARRAY, + PsiReferenceList.Role.IMPLEMENTS_LIST + ) + implementsList?.let { javaClass.implementsList?.replace(it) } + } else { + val extendsList = factory.createReferenceListWithRole( + template.extendsList?.referenceElements ?: PsiJavaCodeReferenceElement.EMPTY_ARRAY, + PsiReferenceList.Role.EXTENDS_LIST + ) + extendsList?.let { javaClass.extendsList?.replace(it) } + + val implementsList = factory.createReferenceListWithRole( + template.implementsList?.referenceElements ?: PsiJavaCodeReferenceElement.EMPTY_ARRAY, + PsiReferenceList.Role.IMPLEMENTS_LIST + ) + implementsList?.let { javaClass.implementsList?.replace(it) } + } + + for (method in template.methods) { + val hasParams = method.parameterList.parametersCount > 0 + val needSuperCall = !template.isEnum && + (template.superClass?.constructors ?: PsiMethod.EMPTY_ARRAY).all { + it.parameterList.parametersCount > 0 + } + if (method.isConstructor && !(hasParams || needSuperCall)) continue + with(createJavaMethod(method, javaClass)) { + if (isConstructor && needSuperCall) { + body!!.add(factory.createStatementFromText("super();", this)) + } + } + } + + return javaClass +} + +internal fun broadcastRefactoringExit(project: Project, refactoringId: String) { + project.messageBus.syncPublisher(KotlinRefactoringEventListener.EVENT_TOPIC).onRefactoringExit(refactoringId) +} + +// IMPORTANT: Target refactoring must support KotlinRefactoringEventListener +internal abstract class CompositeRefactoringRunner( + val project: Project, + val refactoringId: String +) { + protected abstract fun runRefactoring() + + protected open fun onRefactoringDone() {} + protected open fun onExit() {} + + fun run() { + val connection = project.messageBus.connect() + connection.subscribe( + RefactoringEventListener.REFACTORING_EVENT_TOPIC, + object : RefactoringEventListener { + override fun undoRefactoring(refactoringId: String) { + + } + + override fun refactoringStarted(refactoringId: String, beforeData: RefactoringEventData?) { + + } + + override fun conflictsDetected(refactoringId: String, conflictsData: RefactoringEventData) { + + } + + override fun refactoringDone(refactoringId: String, afterData: RefactoringEventData?) { + if (refactoringId == this@CompositeRefactoringRunner.refactoringId) { + onRefactoringDone() + } + } + } + ) + connection.subscribe( + KotlinRefactoringEventListener.EVENT_TOPIC, + object : KotlinRefactoringEventListener { + override fun onRefactoringExit(refactoringId: String) { + if (refactoringId == this@CompositeRefactoringRunner.refactoringId) { + try { + onExit() + } finally { + connection.disconnect() + } + } + } + } + ) + runRefactoring() + } +} + +@Throws(ConfigurationException::class) +fun KtElement?.validateElement(errorMessage: String) { + if (this == null) throw ConfigurationException(errorMessage) + + try { + AnalyzingUtils.checkForSyntacticErrors(this) + } catch (e: Exception) { + throw ConfigurationException(errorMessage) + } +} + +fun invokeOnceOnCommandFinish(action: () -> Unit) { + val commandProcessor = CommandProcessor.getInstance() + val listener = object : CommandAdapter() { + override fun beforeCommandFinished(event: CommandEvent) { + action() + commandProcessor.removeCommandListener(this) + } + } + commandProcessor.addCommandListener(listener) +} + +fun FqNameUnsafe.hasIdentifiersOnly(): Boolean = pathSegments().all { it.asString().quoteIfNeeded().isIdentifier() } + +fun FqName.hasIdentifiersOnly(): Boolean = pathSegments().all { it.asString().quoteIfNeeded().isIdentifier() } + +fun PsiNamedElement.isInterfaceClass(): Boolean = when (this) { + is KtClass -> isInterface() + is PsiClass -> isInterface + is KtPsiClassWrapper -> psiClass.isInterface + else -> false +} + +fun KtNamedDeclaration.isAbstract(): Boolean = when { + hasModifier(KtTokens.ABSTRACT_KEYWORD) -> true + containingClassOrObject?.isInterfaceClass() != true -> false + this is KtProperty -> initializer == null && delegate == null && accessors.isEmpty() + this is KtNamedFunction -> !hasBody() + else -> false +} + +fun KtNamedDeclaration.isConstructorDeclaredProperty() = this is KtParameter && ownerFunction is KtPrimaryConstructor && hasValOrVar() + +fun replaceListPsiAndKeepDelimiters( + originalList: ListType, + newList: ListType, + @Suppress("UNCHECKED_CAST") listReplacer: ListType.(ListType) -> ListType = { replace(it) as ListType }, + itemsFun: ListType.() -> List +): ListType { + originalList.children.takeWhile { it is PsiErrorElement }.forEach { it.delete() } + + val oldParameters = originalList.itemsFun().toMutableList() + val newParameters = newList.itemsFun() + val oldCount = oldParameters.size + val newCount = newParameters.size + + val commonCount = min(oldCount, newCount) + for (i in 0 until commonCount) { + oldParameters[i] = oldParameters[i].replace(newParameters[i]) as KtElement + } + + if (commonCount == 0) return originalList.listReplacer(newList) + + val lastOriginalParameter = oldParameters.last() + + if (oldCount > commonCount) { + originalList.deleteChildRange(oldParameters[commonCount - 1].nextSibling, lastOriginalParameter) + } else if (newCount > commonCount) { + val psiBeforeLastParameter = lastOriginalParameter.prevSibling + val withMultiline = + (psiBeforeLastParameter is PsiWhiteSpace || psiBeforeLastParameter is PsiComment) && psiBeforeLastParameter.textContains('\n') + val extraSpace = if (withMultiline) KtPsiFactory(originalList).createNewLine() else null + originalList.addRangeAfter(newParameters[commonCount - 1].nextSibling, newParameters.last(), lastOriginalParameter) + if (extraSpace != null) { + val addedItems = originalList.itemsFun().subList(commonCount, newCount) + for (addedItem in addedItems) { + val elementBefore = addedItem.prevSibling + if ((elementBefore !is PsiWhiteSpace && elementBefore !is PsiComment) || !elementBefore.textContains('\n')) { + addedItem.parent.addBefore(extraSpace, addedItem) + } + } + } + } + + return originalList +} + +fun Pass(body: (T) -> Unit) = object : Pass() { + override fun pass(t: T) = body(t) +} + +fun KtExpression.removeTemplateEntryBracesIfPossible(): KtExpression { + val parent = parent as? KtBlockStringTemplateEntry ?: return this + val newEntry = if (parent.canDropBraces()) parent.dropBraces() else parent + return newEntry.expression!! +} + +fun dropOverrideKeywordIfNecessary(element: KtNamedDeclaration) { + val callableDescriptor = element.resolveToDescriptorIfAny() as? CallableDescriptor ?: return + if (callableDescriptor.overriddenDescriptors.isEmpty()) { + element.removeModifier(KtTokens.OVERRIDE_KEYWORD) + } +} + +fun dropOperatorKeywordIfNecessary(element: KtNamedDeclaration) { + val callableDescriptor = element.resolveToDescriptorIfAny() as? CallableDescriptor ?: return + val diagnosticHolder = BindingTraceContext() + OperatorModifierChecker.check(element, callableDescriptor, diagnosticHolder, element.languageVersionSettings) + if (diagnosticHolder.bindingContext.diagnostics.any { it.factory == Errors.INAPPLICABLE_OPERATOR_MODIFIER }) { + element.removeModifier(KtTokens.OPERATOR_KEYWORD) + } +} + +fun getQualifiedTypeArgumentList(initializer: KtExpression): KtTypeArgumentList? { + val call = initializer.resolveToCall() ?: return null + val typeArgumentMap = call.typeArguments + val typeArguments = call.candidateDescriptor.typeParameters.mapNotNull { typeArgumentMap[it] } + val renderedList = typeArguments.joinToString(prefix = "<", postfix = ">") { + IdeDescriptorRenderers.SOURCE_CODE_NOT_NULL_TYPE_APPROXIMATION.renderType(it) + } + return KtPsiFactory(initializer).createTypeArguments(renderedList) +} + +fun addTypeArgumentsIfNeeded(expression: KtExpression, typeArgumentList: KtTypeArgumentList) { + val context = expression.analyze(BodyResolveMode.PARTIAL_WITH_DIAGNOSTICS) + val call = expression.getCallWithAssert(context) + val callElement = call.callElement as? KtCallExpression ?: return + if (call.typeArgumentList != null) return + val callee = call.calleeExpression ?: return + if (context.diagnostics.forElement(callee).all { + it.factory != Errors.TYPE_INFERENCE_NO_INFORMATION_FOR_PARAMETER && + it.factory != Errors.NEW_INFERENCE_NO_INFORMATION_FOR_PARAMETER + } + ) { + return + } + + callElement.addAfter(typeArgumentList, callElement.calleeExpression) + ShortenReferences.DEFAULT.process(callElement.typeArgumentList!!) +} + +internal fun DeclarationDescriptor.getThisLabelName(): String { + if (!name.isSpecial) return name.asString() + if (this is AnonymousFunctionDescriptor) { + val function = source.getPsi() as? KtFunction + val argument = function?.parent as? KtValueArgument + ?: (function?.parent as? KtLambdaExpression)?.parent as? KtValueArgument + val callElement = argument?.getStrictParentOfType() + val callee = callElement?.calleeExpression as? KtSimpleNameExpression + if (callee != null) return callee.text + } + return "" +} + +internal fun DeclarationDescriptor.explicateAsTextForReceiver(): String { + val labelName = getThisLabelName() + return if (labelName.isEmpty()) "this" else "this@$labelName" +} + +internal fun ImplicitReceiver.explicateAsText(): String { + return declarationDescriptor.explicateAsTextForReceiver() +} + +val PsiFile.isInjectedFragment: Boolean + get() = InjectedLanguageManager.getInstance(project).isInjectedFragment(this) + +val PsiElement.isInsideInjectedFragment: Boolean + get() = containingFile.isInjectedFragment + +fun checkSuperMethods( + declaration: KtDeclaration, + ignore: Collection?, + actionString: String +): List { + fun getClassDescriptions(overriddenElementsToDescriptor: Map): List { + return overriddenElementsToDescriptor.entries.map { entry -> + val (element, descriptor) = entry + val description = when (element) { + is KtNamedFunction, is KtProperty, is KtParameter -> formatClassDescriptor(descriptor.containingDeclaration) + is PsiMethod -> { + val psiClass = element.containingClass ?: error("Invalid element: ${element.getText()}") + formatPsiClass(psiClass, markAsJava = true, inCode = false) + } + else -> error("Unexpected element: ${element.getElementTextWithContext()}") + } + " $description\n" + } + } + + fun askUserForMethodsToSearch( + declarationDescriptor: CallableDescriptor, + overriddenElementsToDescriptor: Map + ): List { + val superClassDescriptions = getClassDescriptions(overriddenElementsToDescriptor) + + val message = KotlinBundle.message( + "x.overrides.y.in.class.list", + DescriptorRenderer.COMPACT_WITH_SHORT_TYPES.render(declarationDescriptor), + "\n${superClassDescriptions.joinToString(separator = "")}", + actionString + ) + + val exitCode = showYesNoCancelDialog( + CHECK_SUPER_METHODS_YES_NO_DIALOG, + declaration.project, message, IdeBundle.message("title.warning"), Messages.getQuestionIcon(), Messages.YES + ) + return when (exitCode) { + Messages.YES -> overriddenElementsToDescriptor.keys.toList() + Messages.NO -> listOf(declaration) + else -> emptyList() + } + } + + + val declarationDescriptor = declaration.unsafeResolveToDescriptor() as CallableDescriptor + + if (declarationDescriptor is LocalVariableDescriptor) return listOf(declaration) + + val project = declaration.project + val overriddenElementsToDescriptor = HashMap() + for (overriddenDescriptor in DescriptorUtils.getAllOverriddenDescriptors(declarationDescriptor)) { + val overriddenDeclaration = DescriptorToSourceUtilsIde.getAnyDeclaration(project, overriddenDescriptor) ?: continue + if (overriddenDeclaration is KtNamedFunction || overriddenDeclaration is KtProperty || overriddenDeclaration is PsiMethod || overriddenDeclaration is KtParameter) { + overriddenElementsToDescriptor[overriddenDeclaration] = overriddenDescriptor + } + } + if (ignore != null) { + overriddenElementsToDescriptor.keys.removeAll(ignore) + } + + if (overriddenElementsToDescriptor.isEmpty()) return listOf(declaration) + + return askUserForMethodsToSearch(declarationDescriptor, overriddenElementsToDescriptor) +} + +fun checkSuperMethodsWithPopup( + declaration: KtNamedDeclaration, + deepestSuperMethods: List, + actionString: String, + editor: Editor, + action: (List) -> Unit +) { + if (deepestSuperMethods.isEmpty()) return action(listOf(declaration)) + + val superMethod = deepestSuperMethods.first() + + val (superClass, isAbstract) = when (superMethod) { + is PsiMember -> superMethod.containingClass to superMethod.hasModifierProperty(PsiModifier.ABSTRACT) + is KtNamedDeclaration -> superMethod.containingClassOrObject to superMethod.isAbstract() + else -> null + } ?: return action(listOf(declaration)) + if (superClass == null) return action(listOf(declaration)) + + if (ApplicationManager.getApplication().isUnitTestMode) return action(deepestSuperMethods) + + val kind = when (declaration) { + is KtNamedFunction -> "function" + is KtProperty, is KtParameter -> "property" + else -> return + } + + val unwrappedSupers = deepestSuperMethods.mapNotNull { it.namedUnwrappedElement } + val hasJavaMethods = unwrappedSupers.any { it is PsiMethod } + val hasKtMembers = unwrappedSupers.any { it is KtNamedDeclaration } + val superKind = when { + hasJavaMethods && hasKtMembers -> "member" + hasJavaMethods -> "method" + else -> kind + } + + val renameBase = actionString + " base $superKind" + (if (deepestSuperMethods.size > 1) "s" else "") + val renameCurrent = "$actionString only current $kind" + val title = buildString { + append(declaration.name) + append(if (isAbstract) " implements " else " overrides ") + append(ElementDescriptionUtil.getElementDescription(superMethod, UsageViewTypeLocation.INSTANCE)) + append(" of ") + append(SymbolPresentationUtil.getSymbolPresentableText(superClass)) + } + val list = JBList(renameBase, renameCurrent) + JBPopupFactory.getInstance() + .createListPopupBuilder(list) + .setTitle(title) + .setMovable(false) + .setResizable(false) + .setRequestFocus(true) + .setItemChoosenCallback { + val value = list.selectedValue ?: return@setItemChoosenCallback + val chosenElements = if (value == renameBase) deepestSuperMethods + declaration else listOf(declaration) + action(chosenElements) + } + .createPopup() + .showInBestPositionFor(editor) +} + +fun KtNamedDeclaration.isCompanionMemberOf(klass: KtClassOrObject): Boolean { + val containingObject = containingClassOrObject as? KtObjectDeclaration ?: return false + return containingObject.isCompanion() && containingObject.containingClassOrObject == klass +} + +internal fun KtDeclaration.withExpectedActuals(): List { + val expect = liftToExpected() ?: return listOf(this) + val actuals = expect.actualsForExpected() + return listOf(expect) + actuals +} + +internal fun KtDeclaration.resolveToExpectedDescriptorIfPossible(): DeclarationDescriptor { + val descriptor = unsafeResolveToDescriptor() + return descriptor.liftToExpected() ?: descriptor +} + +fun DialogWrapper.showWithTransaction() { + TransactionGuard.submitTransaction(disposable, Runnable { show() }) +} + +fun PsiMethod.checkDeclarationConflict(name: String, conflicts: MultiMap, callables: List) { + containingClass + ?.findMethodsByName(name, true) + // as is necessary here: see KT-10386 + ?.firstOrNull { it.parameterList.parametersCount == 0 && !callables.contains(it.namedUnwrappedElement as PsiElement?) } + ?.let { reportDeclarationConflict(conflicts, it) { s -> "$s already exists" } } +} + +fun T.replaceWithCopyWithResolveCheck( + resolveStrategy: (T, BindingContext) -> DeclarationDescriptor?, + context: BindingContext = analyze(), + preHook: T.() -> Unit = {}, + postHook: T.() -> T? = { this } +): T? { + val originDescriptor = resolveStrategy(this, context) ?: return null + @Suppress("UNCHECKED_CAST") val elementCopy = copy() as T + elementCopy.preHook() + val newContext = elementCopy.analyzeAsReplacement(this, context) + val newDescriptor = resolveStrategy(elementCopy, newContext) ?: return null + + return if (originDescriptor.canonicalRender() == newDescriptor.canonicalRender()) elementCopy.postHook() else null +} + +@Deprecated( + "Use org.jetbrains.kotlin.idea.core.util.getLineCount() instead", + ReplaceWith("this.getLineCount()", "org.jetbrains.kotlin.idea.core.util.getLineCount"), + DeprecationLevel.ERROR +) +fun PsiElement.getLineCount(): Int { + return newGetLineCount() +} + +@Deprecated( + "Use org.jetbrains.kotlin.idea.core.util.toPsiDirectory() instead", + ReplaceWith("this.toPsiDirectory()", "org.jetbrains.kotlin.idea.core.util.toPsiDirectory"), + DeprecationLevel.ERROR +) +fun VirtualFile.toPsiDirectory(project: Project): PsiDirectory? { + return newToPsiDirectory(project) +} + +@Deprecated( + "Use org.jetbrains.kotlin.idea.core.util.toPsiFile() instead", + ReplaceWith("this.toPsiFile()", "org.jetbrains.kotlin.idea.core.util.toPsiFile"), + DeprecationLevel.ERROR +) +fun VirtualFile.toPsiFile(project: Project): PsiFile? { + return newToPsiFile(project) +} \ No newline at end of file