172: Revert "Fix compilation after moving to idea 173"
This reverts commit fe121cc6aeadf63ca2c2819c9c7df59cc81caf5d.
This commit is contained in:
+115
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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.test.testFramework.mock;
|
||||
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.FileViewProvider;
|
||||
import com.intellij.psi.PsiDirectory;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.SingleRootFileViewProvider;
|
||||
import com.intellij.psi.impl.PsiManagerEx;
|
||||
import com.intellij.psi.impl.file.impl.FileManager;
|
||||
import com.intellij.util.containers.ConcurrentWeakFactoryMap;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.FactoryMap;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class MockFileManager implements FileManager {
|
||||
private final PsiManagerEx myManager;
|
||||
// in mock tests it's LightVirtualFile, they're only alive when they're referenced,
|
||||
// and there can not be several instances representing the same file
|
||||
private final FactoryMap<VirtualFile, FileViewProvider> myViewProviders = new ConcurrentWeakFactoryMap<VirtualFile, FileViewProvider>() {
|
||||
@Override
|
||||
protected Map<VirtualFile, FileViewProvider> createMap() {
|
||||
return ContainerUtil.createConcurrentWeakKeyWeakValueMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FileViewProvider create(VirtualFile key) {
|
||||
return new SingleRootFileViewProvider(myManager, key);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public FileViewProvider createFileViewProvider(@NotNull VirtualFile file, boolean eventSystemEnabled) {
|
||||
return new SingleRootFileViewProvider(myManager, file, eventSystemEnabled);
|
||||
}
|
||||
|
||||
public MockFileManager(PsiManagerEx manager) {
|
||||
myManager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
throw new UnsupportedOperationException("Method dispose is not yet implemented in " + getClass().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public PsiFile findFile(@NotNull VirtualFile vFile) {
|
||||
return getCachedPsiFile(vFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public PsiDirectory findDirectory(@NotNull VirtualFile vFile) {
|
||||
throw new UnsupportedOperationException("Method findDirectory is not yet implemented in " + getClass().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reloadFromDisk(@NotNull PsiFile file) //Q: move to PsiFile(Impl)?
|
||||
{
|
||||
throw new UnsupportedOperationException("Method reloadFromDisk is not yet implemented in " + getClass().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public PsiFile getCachedPsiFile(@NotNull VirtualFile vFile) {
|
||||
FileViewProvider provider = findCachedViewProvider(vFile);
|
||||
return provider.getPsi(provider.getBaseLanguage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanupForNextTest() {
|
||||
myViewProviders.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileViewProvider findViewProvider(@NotNull VirtualFile file) {
|
||||
throw new UnsupportedOperationException("Method findViewProvider is not yet implemented in " + getClass().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileViewProvider findCachedViewProvider(@NotNull VirtualFile file) {
|
||||
return myViewProviders.get(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setViewProvider(@NotNull VirtualFile virtualFile, FileViewProvider fileViewProvider) {
|
||||
myViewProviders.put(virtualFile, fileViewProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public List<PsiFile> getAllCachedFiles() {
|
||||
throw new UnsupportedOperationException("Method getAllCachedFiles is not yet implemented in " + getClass().getName());
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.caches.lightClasses
|
||||
|
||||
import com.intellij.psi.PsiClass
|
||||
import com.intellij.psi.PsiField
|
||||
import com.intellij.psi.PsiMethod
|
||||
import com.intellij.psi.impl.compiled.ClsClassImpl
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClassBase
|
||||
import org.jetbrains.kotlin.asJava.elements.KtLightFieldImpl
|
||||
import org.jetbrains.kotlin.asJava.elements.KtLightMethodImpl
|
||||
import org.jetbrains.kotlin.idea.decompiler.classFile.KtClsFile
|
||||
import org.jetbrains.kotlin.load.java.structure.LightClassOriginKind
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
|
||||
class KtLightClassForDecompiledDeclaration(
|
||||
override val clsDelegate: ClsClassImpl,
|
||||
override val kotlinOrigin: KtClassOrObject?,
|
||||
private val file: KtClsFile
|
||||
) : KtLightClassBase(clsDelegate.manager) {
|
||||
val fqName = kotlinOrigin?.fqName ?: FqName(clsDelegate.qualifiedName)
|
||||
|
||||
override fun copy() = this
|
||||
|
||||
override fun getOwnInnerClasses(): List<PsiClass> {
|
||||
val nestedClasses = kotlinOrigin?.declarations?.filterIsInstance<KtClassOrObject>() ?: emptyList()
|
||||
return clsDelegate.ownInnerClasses.map { innerClsClass ->
|
||||
KtLightClassForDecompiledDeclaration(innerClsClass as ClsClassImpl,
|
||||
nestedClasses.firstOrNull { innerClsClass.name == it.name }, file
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getOwnFields(): List<PsiField> {
|
||||
return clsDelegate.ownFields.map { KtLightFieldImpl.create(LightMemberOriginForCompiledField(it, file), it, this) }
|
||||
}
|
||||
|
||||
override fun getOwnMethods(): List<PsiMethod> {
|
||||
return clsDelegate.ownMethods.map { KtLightMethodImpl.create(it, LightMemberOriginForCompiledMethod(it, file), this) }
|
||||
}
|
||||
|
||||
override fun getNavigationElement() = kotlinOrigin?.navigationElement ?: file
|
||||
|
||||
override fun getParent() = clsDelegate.parent
|
||||
|
||||
override fun equals(other: Any?): Boolean =
|
||||
other is KtLightClassForDecompiledDeclaration &&
|
||||
fqName == other.fqName
|
||||
|
||||
override fun hashCode(): Int =
|
||||
fqName.hashCode()
|
||||
|
||||
override val originKind: LightClassOriginKind
|
||||
get() = LightClassOriginKind.BINARY
|
||||
}
|
||||
|
||||
+362
@@ -0,0 +1,362 @@
|
||||
/*
|
||||
* 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.codeInsight.gradle
|
||||
|
||||
import com.intellij.codeInspection.LocalInspectionTool
|
||||
import com.intellij.codeInspection.ProblemDescriptorBase
|
||||
import com.intellij.openapi.util.Ref
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import org.jetbrains.kotlin.idea.inspections.gradle.DeprecatedGradleDependencyInspection
|
||||
import org.jetbrains.kotlin.idea.inspections.gradle.DifferentKotlinGradleVersionInspection
|
||||
import org.jetbrains.kotlin.idea.inspections.gradle.DifferentStdlibGradleVersionInspection
|
||||
import org.jetbrains.kotlin.idea.inspections.runInspection
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
|
||||
class GradleInspectionTest : GradleImportingTestCase() {
|
||||
@Test
|
||||
fun testDifferentStdlibGradleVersion() {
|
||||
val localFile = createProjectSubFile(
|
||||
"build.gradle", """
|
||||
group 'Again'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.0.2")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin'
|
||||
|
||||
dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib:1.0.3"
|
||||
}
|
||||
"""
|
||||
)
|
||||
importProject()
|
||||
|
||||
val tool = DifferentStdlibGradleVersionInspection()
|
||||
val problems = getInspectionResult(tool, localFile)
|
||||
|
||||
Assert.assertTrue(problems.size == 1)
|
||||
Assert.assertEquals("Plugin version (1.0.2) is not the same as library version (1.0.3)", problems.single())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDifferentStdlibGradleVersionWithImplementation() {
|
||||
val localFile = createProjectSubFile(
|
||||
"build.gradle", """
|
||||
group 'Again'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.0.2")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin'
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.0.3"
|
||||
}
|
||||
"""
|
||||
)
|
||||
importProject()
|
||||
|
||||
val tool = DifferentStdlibGradleVersionInspection()
|
||||
val problems = getInspectionResult(tool, localFile)
|
||||
|
||||
Assert.assertTrue(problems.size == 1)
|
||||
Assert.assertEquals("Plugin version (1.0.2) is not the same as library version (1.0.3)", problems.single())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDifferentStdlibJre7GradleVersion() {
|
||||
val localFile = createProjectSubFile(
|
||||
"build.gradle", """
|
||||
group 'Again'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url 'http://dl.bintray.com/kotlin/kotlin-eap-1.1'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.0-beta-17")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin'
|
||||
|
||||
dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:1.1.0-beta-22"
|
||||
}
|
||||
"""
|
||||
)
|
||||
importProject()
|
||||
|
||||
val tool = DifferentStdlibGradleVersionInspection()
|
||||
val problems = getInspectionResult(tool, localFile)
|
||||
|
||||
Assert.assertTrue(problems.size == 1)
|
||||
Assert.assertEquals("Plugin version (1.1.0-beta-17) is not the same as library version (1.1.0-beta-22)", problems.single())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDifferentStdlibJdk7GradleVersion() {
|
||||
val localFile = createProjectSubFile(
|
||||
"build.gradle", """
|
||||
group 'Again'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url 'http://dl.bintray.com/kotlin/kotlin-eap-1.1'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.0-beta-17")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin'
|
||||
|
||||
dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.1.0-beta-22"
|
||||
}
|
||||
"""
|
||||
)
|
||||
importProject()
|
||||
|
||||
val tool = DifferentStdlibGradleVersionInspection()
|
||||
val problems = getInspectionResult(tool, localFile)
|
||||
|
||||
Assert.assertTrue(problems.size == 1)
|
||||
Assert.assertEquals("Plugin version (1.1.0-beta-17) is not the same as library version (1.1.0-beta-22)", problems.single())
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testDifferentStdlibGradleVersionWithVariables() {
|
||||
createProjectSubFile(
|
||||
"gradle.properties", """
|
||||
|kotlin=1.0.1
|
||||
|lib_version=1.0.3""".trimMargin()
|
||||
)
|
||||
val localFile = createProjectSubFile(
|
||||
"build.gradle", """
|
||||
group 'Again'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${'$'}kotlin")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin'
|
||||
|
||||
dependencies {
|
||||
compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib', version: lib_version
|
||||
}
|
||||
"""
|
||||
)
|
||||
importProject()
|
||||
|
||||
val tool = DifferentStdlibGradleVersionInspection()
|
||||
val problems = getInspectionResult(tool, localFile)
|
||||
|
||||
Assert.assertTrue(problems.size == 1)
|
||||
Assert.assertEquals("Plugin version (1.0.1) is not the same as library version (1.0.3)", problems.single())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDifferentKotlinGradleVersion() {
|
||||
createProjectSubFile("gradle.properties", """test=1.0.1""")
|
||||
val localFile = createProjectSubFile(
|
||||
"build.gradle", """
|
||||
group 'Again'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${'$'}{test}")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin'
|
||||
"""
|
||||
)
|
||||
importProject()
|
||||
|
||||
val tool = DifferentKotlinGradleVersionInspection()
|
||||
tool.testVersionMessage = "\$PLUGIN_VERSION"
|
||||
val problems = getInspectionResult(tool, localFile)
|
||||
|
||||
Assert.assertTrue(problems.size == 1)
|
||||
Assert.assertEquals(
|
||||
"Kotlin version that is used for building with Gradle (1.0.1) differs from the one bundled into the IDE plugin (\$PLUGIN_VERSION)",
|
||||
problems.single()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testJreInOldVersion() {
|
||||
val localFile = createProjectSubFile(
|
||||
"build.gradle", """
|
||||
group 'Again'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.60")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin'
|
||||
|
||||
dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:1.1.60"
|
||||
}
|
||||
"""
|
||||
)
|
||||
importProject()
|
||||
|
||||
val tool = DeprecatedGradleDependencyInspection()
|
||||
val problems = getInspectionResult(tool, localFile)
|
||||
|
||||
Assert.assertTrue(problems.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testJreIsDeprecated() {
|
||||
val localFile = createProjectSubFile(
|
||||
"build.gradle", """
|
||||
group 'Again'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.60")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin'
|
||||
|
||||
dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.0"
|
||||
}
|
||||
"""
|
||||
)
|
||||
importProject()
|
||||
|
||||
val tool = DeprecatedGradleDependencyInspection()
|
||||
val problems = getInspectionResult(tool, localFile)
|
||||
|
||||
Assert.assertTrue(problems.size == 1)
|
||||
Assert.assertEquals(
|
||||
"kotlin-stdlib-jre7 is deprecated since 1.2.0 and should be replaced with kotlin-stdlib-jdk7",
|
||||
problems.single()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testJreIsDeprecatedWithImplementation() {
|
||||
val localFile = createProjectSubFile(
|
||||
"build.gradle", """
|
||||
group 'Again'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.0")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin'
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.0"
|
||||
}
|
||||
"""
|
||||
)
|
||||
importProject()
|
||||
|
||||
val tool = DeprecatedGradleDependencyInspection()
|
||||
val problems = getInspectionResult(tool, localFile)
|
||||
|
||||
Assert.assertTrue(problems.size == 1)
|
||||
Assert.assertEquals(
|
||||
"kotlin-stdlib-jre7 is deprecated since 1.2.0 and should be replaced with kotlin-stdlib-jdk7",
|
||||
problems.single()
|
||||
)
|
||||
}
|
||||
|
||||
fun getInspectionResult(tool: LocalInspectionTool, file: VirtualFile): List<String> {
|
||||
val resultRef = Ref<List<String>>()
|
||||
invokeTestRunnable {
|
||||
val presentation = runInspection(tool, myProject, listOf(file))
|
||||
|
||||
val foundProblems = presentation.problemElements
|
||||
.values
|
||||
.flatMap { it.toList() }
|
||||
.mapNotNull { it as? ProblemDescriptorBase }
|
||||
.map { it.descriptionTemplate }
|
||||
|
||||
resultRef.set(foundProblems)
|
||||
}
|
||||
|
||||
return resultRef.get()
|
||||
}
|
||||
}
|
||||
+334
@@ -0,0 +1,334 @@
|
||||
/*
|
||||
* 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.debugger
|
||||
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.engine.DebugProcess
|
||||
import com.intellij.debugger.jdi.VirtualMachineProxyImpl
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.compiler.CompilerPaths
|
||||
import com.intellij.openapi.compiler.ex.CompilerPathsEx
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.roots.ProjectFileIndex
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.util.containers.ConcurrentWeakFactoryMap
|
||||
import com.intellij.util.containers.ContainerUtil
|
||||
import com.sun.jdi.Location
|
||||
import com.sun.jdi.ReferenceType
|
||||
import org.jetbrains.kotlin.codegen.inline.API
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
||||
import org.jetbrains.kotlin.idea.refactoring.getLineCount
|
||||
import org.jetbrains.kotlin.idea.refactoring.getLineStartOffset
|
||||
import org.jetbrains.kotlin.idea.refactoring.toPsiFile
|
||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.load.kotlin.VirtualFileFinder
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.name.tail
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parents
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
import org.jetbrains.kotlin.utils.getOrPutNullable
|
||||
import org.jetbrains.org.objectweb.asm.*
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
fun isInlineFunctionLineNumber(file: VirtualFile, lineNumber: Int, project: Project): Boolean {
|
||||
if (ProjectRootsUtil.isProjectSourceFile(project, file)) {
|
||||
val linesInFile = file.toPsiFile(project)?.getLineCount() ?: return false
|
||||
return lineNumber > linesInFile
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun readBytecodeInfo(project: Project,
|
||||
jvmName: JvmClassName,
|
||||
file: VirtualFile): BytecodeDebugInfo? {
|
||||
return KotlinDebuggerCaches.getOrReadDebugInfoFromBytecode(project, jvmName, file)
|
||||
}
|
||||
|
||||
fun ktLocationInfo(location: Location, isDexDebug: Boolean, project: Project,
|
||||
preferInlined: Boolean = false, locationFile: KtFile? = null): Pair<Int, KtFile?> {
|
||||
if (isDexDebug && (locationFile == null || location.lineNumber() > locationFile.getLineCount())) {
|
||||
if (!preferInlined) {
|
||||
val thisFunLine = runReadAction { getLastLineNumberForLocation(location, project) }
|
||||
if (thisFunLine != null && thisFunLine != location.lineNumber()) {
|
||||
return thisFunLine to locationFile
|
||||
}
|
||||
}
|
||||
|
||||
val inlinePosition = runReadAction { getOriginalPositionOfInlinedLine(location, project) }
|
||||
if (inlinePosition != null) {
|
||||
val (file, line) = inlinePosition
|
||||
return line + 1 to file
|
||||
}
|
||||
}
|
||||
|
||||
return location.lineNumber() to locationFile
|
||||
}
|
||||
|
||||
/**
|
||||
* Only the first line number is stored for instruction in dex. It can be obtained through location.lineNumber().
|
||||
* This method allows to get last stored linenumber for instruction.
|
||||
*/
|
||||
fun getLastLineNumberForLocation(location: Location, project: Project, searchScope: GlobalSearchScope = GlobalSearchScope.allScope(project)): Int? {
|
||||
val lineNumber = location.lineNumber()
|
||||
val fqName = FqName(location.declaringType().name())
|
||||
val fileName = location.sourceName()
|
||||
|
||||
val method = location.method() ?: return null
|
||||
val name = method.name() ?: return null
|
||||
val signature = method.signature() ?: return null
|
||||
|
||||
val debugInfo = findAndReadClassFile(fqName, fileName, project, searchScope, { isInlineFunctionLineNumber(it, lineNumber, project) }) ?: return null
|
||||
|
||||
val lineMapping = debugInfo.lineTableMapping[BytecodeMethodKey(name, signature)] ?: return null
|
||||
return lineMapping.values.firstOrNull { it.contains(lineNumber) }?.last()
|
||||
}
|
||||
|
||||
class WeakBytecodeDebugInfoStorage : ConcurrentWeakFactoryMap<BinaryCacheKey, BytecodeDebugInfo?>() {
|
||||
override fun create(key: BinaryCacheKey): BytecodeDebugInfo? {
|
||||
val bytes = readClassFileImpl(key.project, key.jvmName, key.file) ?: return null
|
||||
|
||||
val smapData = readDebugInfo(bytes)
|
||||
val lineNumberMapping = readLineNumberTableMapping(bytes)
|
||||
|
||||
return BytecodeDebugInfo(smapData, lineNumberMapping)
|
||||
}
|
||||
override fun createMap(): Map<BinaryCacheKey, BytecodeDebugInfo?> {
|
||||
return ContainerUtil.createConcurrentWeakKeyWeakValueMap()
|
||||
}
|
||||
}
|
||||
|
||||
class BytecodeDebugInfo(val smapData: SmapData?, val lineTableMapping: Map<BytecodeMethodKey, Map<String, Set<Int>>>)
|
||||
|
||||
data class BytecodeMethodKey(val methodName: String, val signature: String)
|
||||
|
||||
data class BinaryCacheKey(val project: Project, val jvmName: JvmClassName, val file: VirtualFile)
|
||||
|
||||
private fun readClassFileImpl(project: Project,
|
||||
jvmName: JvmClassName,
|
||||
file: VirtualFile): ByteArray? {
|
||||
val fqNameWithInners = jvmName.fqNameForClassNameWithoutDollars.tail(jvmName.packageFqName)
|
||||
|
||||
fun readFromLibrary(): ByteArray? {
|
||||
if (!ProjectRootsUtil.isLibrarySourceFile(project, file)) return null
|
||||
|
||||
val classId = ClassId(jvmName.packageFqName, Name.identifier(fqNameWithInners.asString()))
|
||||
|
||||
val fileFinder = VirtualFileFinder.getInstance(project)
|
||||
val classFile = fileFinder.findVirtualFileWithHeader(classId) ?: return null
|
||||
return classFile.contentsToByteArray(false)
|
||||
}
|
||||
|
||||
fun readFromOutput(isForTestClasses: Boolean): ByteArray? {
|
||||
if (!ProjectRootsUtil.isProjectSourceFile(project, file)) return null
|
||||
|
||||
val module = ProjectFileIndex.SERVICE.getInstance(project).getModuleForFile(file) ?: return null
|
||||
|
||||
val outputPaths = CompilerPathsEx.getOutputPaths(arrayOf(module)).toList()
|
||||
val className = fqNameWithInners.asString().replace('.', '$')
|
||||
var classFile = findClassFileByPaths(jvmName.packageFqName.asString(), className, outputPaths)
|
||||
|
||||
if (classFile == null) {
|
||||
if (!isForTestClasses) {
|
||||
return null
|
||||
}
|
||||
|
||||
val outputDir = CompilerPaths.getModuleOutputDirectory(module, /*forTests = */ isForTestClasses) ?: return null
|
||||
|
||||
val outputModeDirName = outputDir.name
|
||||
// FIXME: It looks like this doesn't work anymore after Kotlin gradle plugin have stopped generating Kotlin classes in java output dir
|
||||
// Originally this code did mapping like 'path/classes/test/debug' -> 'path/classes/androidTest/debug'
|
||||
val androidTestOutputDir = outputDir.parent?.parent?.findChild("androidTest")?.findChild(outputModeDirName) ?: return null
|
||||
|
||||
classFile = findClassFileByPath(jvmName.packageFqName.asString(), className, androidTestOutputDir.path) ?: return null
|
||||
}
|
||||
|
||||
return classFile.readBytes()
|
||||
}
|
||||
|
||||
fun readFromSourceOutput(): ByteArray? = readFromOutput(false)
|
||||
|
||||
fun readFromTestOutput(): ByteArray? = readFromOutput(true)
|
||||
|
||||
return readFromLibrary() ?:
|
||||
readFromSourceOutput() ?:
|
||||
readFromTestOutput()
|
||||
}
|
||||
|
||||
private fun findClassFileByPaths(packageName: String, className: String, paths: List<String>): File? =
|
||||
paths.mapNotNull { path -> findClassFileByPath(packageName, className, path) }.maxBy { it.lastModified() }
|
||||
|
||||
private fun findClassFileByPath(packageName: String, className: String, outputDirPath: String): File? {
|
||||
val outDirFile = File(outputDirPath).takeIf(File::exists) ?: return null
|
||||
|
||||
val parentDirectory = File(outDirFile, packageName.replace(".", File.separator))
|
||||
if (!parentDirectory.exists()) return null
|
||||
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||
val beforeDexFileClassFile = File(parentDirectory, className + ".class.before_dex")
|
||||
if (beforeDexFileClassFile.exists()) {
|
||||
return beforeDexFileClassFile
|
||||
}
|
||||
}
|
||||
|
||||
val classFile = File(parentDirectory, className + ".class")
|
||||
if (classFile.exists()) {
|
||||
return classFile
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun readLineNumberTableMapping(bytes: ByteArray): Map<BytecodeMethodKey, Map<String, Set<Int>>> {
|
||||
val lineNumberMapping = HashMap<BytecodeMethodKey, Map<String, Set<Int>>>()
|
||||
|
||||
ClassReader(bytes).accept(object : ClassVisitor(API) {
|
||||
override fun visitMethod(access: Int, name: String?, desc: String?, signature: String?, exceptions: Array<out String>?): MethodVisitor? {
|
||||
if (name == null || desc == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val methodKey = BytecodeMethodKey(name, desc)
|
||||
val methodLinesMapping = HashMap<String, MutableSet<Int>>()
|
||||
lineNumberMapping[methodKey] = methodLinesMapping
|
||||
|
||||
return object : MethodVisitor(Opcodes.ASM5, null) {
|
||||
override fun visitLineNumber(line: Int, start: Label?) {
|
||||
if (start != null) {
|
||||
methodLinesMapping.getOrPutNullable(start.toString(), { LinkedHashSet<Int>() }).add(line)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, ClassReader.SKIP_FRAMES and ClassReader.SKIP_CODE)
|
||||
|
||||
return lineNumberMapping
|
||||
}
|
||||
|
||||
internal fun getOriginalPositionOfInlinedLine(location: Location, project: Project): Pair<KtFile, Int>? {
|
||||
val lineNumber = location.lineNumber()
|
||||
val fqName = FqName(location.declaringType().name())
|
||||
val fileName = location.sourceName()
|
||||
val searchScope = GlobalSearchScope.allScope(project)
|
||||
|
||||
val debugInfo = findAndReadClassFile(fqName, fileName, project, searchScope, { isInlineFunctionLineNumber(it, lineNumber, project) }) ?:
|
||||
return null
|
||||
val smapData = debugInfo.smapData ?: return null
|
||||
|
||||
return mapStacktraceLineToSource(smapData, lineNumber, project, SourceLineKind.EXECUTED_LINE, searchScope)
|
||||
}
|
||||
|
||||
private fun findAndReadClassFile(
|
||||
fqName: FqName, fileName: String, project: Project, searchScope: GlobalSearchScope,
|
||||
fileFilter: (VirtualFile) -> Boolean): BytecodeDebugInfo? {
|
||||
val internalName = fqName.asString().replace('.', '/')
|
||||
val jvmClassName = JvmClassName.byInternalName(internalName)
|
||||
|
||||
val file = DebuggerUtils.findSourceFileForClassIncludeLibrarySources(project, searchScope, jvmClassName, fileName) ?: return null
|
||||
|
||||
val virtualFile = file.virtualFile ?: return null
|
||||
if (!fileFilter(virtualFile)) return null
|
||||
|
||||
return readBytecodeInfo(project, jvmClassName, virtualFile)
|
||||
}
|
||||
|
||||
internal fun getLocationsOfInlinedLine(type: ReferenceType, position: SourcePosition, sourceSearchScope: GlobalSearchScope): List<Location> {
|
||||
val line = position.line
|
||||
val file = position.file
|
||||
val project = position.file.project
|
||||
|
||||
val lineStartOffset = file.getLineStartOffset(line) ?: return listOf()
|
||||
val element = file.findElementAt(lineStartOffset) ?: return listOf()
|
||||
val ktElement = element.parents.firstIsInstanceOrNull<KtElement>() ?: return listOf()
|
||||
|
||||
val isInInline = runReadAction { element.parents.any { it is KtFunction && it.hasModifier(KtTokens.INLINE_KEYWORD) } }
|
||||
|
||||
if (!isInInline) {
|
||||
// Lambdas passed to crossinline arguments are inlined when they are used in non-inlined lambdas
|
||||
val isInCrossinlineArgument = isInCrossinlineArgument(ktElement)
|
||||
if (!isInCrossinlineArgument) {
|
||||
return listOf()
|
||||
}
|
||||
}
|
||||
|
||||
val lines = inlinedLinesNumbers(line + 1, position.file.name, FqName(type.name()), type.sourceName(), project, sourceSearchScope)
|
||||
|
||||
return lines.flatMap { type.locationsOfLine(it) }
|
||||
}
|
||||
|
||||
fun isInCrossinlineArgument(ktElement: KtElement): Boolean {
|
||||
val argumentFunctions = runReadAction {
|
||||
ktElement.parents.filter {
|
||||
when (it) {
|
||||
is KtFunctionLiteral -> it.parent is KtLambdaExpression && (it.parent.parent is KtValueArgument || it.parent.parent is KtLambdaArgument)
|
||||
is KtFunction -> it.parent is KtValueArgument
|
||||
else -> false
|
||||
}
|
||||
}.filterIsInstance<KtFunction>()
|
||||
}
|
||||
|
||||
val bindingContext = ktElement.analyze(BodyResolveMode.PARTIAL)
|
||||
return argumentFunctions.any {
|
||||
val argumentDescriptor = InlineUtil.getInlineArgumentDescriptor(it, bindingContext)
|
||||
argumentDescriptor?.isCrossinline ?: false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun inlinedLinesNumbers(
|
||||
inlineLineNumber: Int, inlineFileName: String,
|
||||
destinationTypeFqName: FqName, destinationFileName: String,
|
||||
project: Project, sourceSearchScope: GlobalSearchScope): List<Int> {
|
||||
val internalName = destinationTypeFqName.asString().replace('.', '/')
|
||||
val jvmClassName = JvmClassName.byInternalName(internalName)
|
||||
|
||||
val file = DebuggerUtils.findSourceFileForClassIncludeLibrarySources(project, sourceSearchScope, jvmClassName, destinationFileName) ?:
|
||||
return listOf()
|
||||
|
||||
val virtualFile = file.virtualFile ?: return listOf()
|
||||
|
||||
val debugInfo = readBytecodeInfo(project, jvmClassName, virtualFile) ?: return listOf()
|
||||
val smapData = debugInfo.smapData ?: return listOf()
|
||||
|
||||
val smap = smapData.kotlinStrata ?: return listOf()
|
||||
|
||||
val mappingsToInlinedFile = smap.fileMappings.filter { it.name == inlineFileName }
|
||||
val mappingIntervals = mappingsToInlinedFile.flatMap { it.lineMappings }
|
||||
|
||||
return mappingIntervals.asSequence().
|
||||
filter { rangeMapping -> rangeMapping.hasMappingForSource(inlineLineNumber) }.
|
||||
map { rangeMapping -> rangeMapping.mapSourceToDest(inlineLineNumber) }.
|
||||
filter { line -> line != -1 }.
|
||||
toList()
|
||||
}
|
||||
|
||||
@Volatile var emulateDexDebugInTests: Boolean = false
|
||||
|
||||
fun DebugProcess.isDexDebug() =
|
||||
(emulateDexDebugInTests && ApplicationManager.getApplication().isUnitTestMode) ||
|
||||
(this.virtualMachineProxy as? VirtualMachineProxyImpl)?.virtualMachine?.name() == "Dalvik" // TODO: check other machine names
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* 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.maven
|
||||
|
||||
import com.intellij.codeInspection.CommonProblemDescriptor
|
||||
import com.intellij.codeInspection.ProblemDescriptorBase
|
||||
import com.intellij.codeInspection.QuickFix
|
||||
import com.intellij.ide.highlighter.JavaFileType
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.Result
|
||||
import com.intellij.openapi.application.WriteAction
|
||||
import com.intellij.openapi.command.CommandProcessor
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.psi.search.FileTypeIndex
|
||||
import org.jetbrains.jps.model.java.JavaSourceRootType
|
||||
import org.jetbrains.kotlin.idea.inspections.runInspection
|
||||
import org.jetbrains.kotlin.idea.maven.inspections.KotlinMavenPluginPhaseInspection
|
||||
import org.jetbrains.kotlin.idea.refactoring.toPsiDirectory
|
||||
import org.jetbrains.kotlin.idea.util.projectStructure.allModules
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import org.jetbrains.kotlin.utils.keysToMap
|
||||
import java.io.File
|
||||
|
||||
abstract class AbstractKotlinMavenInspectionTest : MavenImportingTestCase() {
|
||||
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
repositoryPath = File(myDir, "repo").path
|
||||
createStdProjectFolders()
|
||||
}
|
||||
|
||||
fun doTest(fileName: String) {
|
||||
val pomFile = File(fileName)
|
||||
val pomText = pomFile.readText()
|
||||
|
||||
createPomFile(fileName)
|
||||
importProject()
|
||||
myProject.allModules().forEach {
|
||||
setupJdkForModule(it.name)
|
||||
}
|
||||
|
||||
if (pomText.contains("<!--\\s*mkjava\\s*-->".toRegex(RegexOption.MULTILINE))) {
|
||||
mkJavaFile()
|
||||
}
|
||||
|
||||
val inspectionClassName = "<!--\\s*inspection:\\s*([\\S]+)\\s-->".toRegex().find(pomText)?.groups?.get(1)?.value
|
||||
?: KotlinMavenPluginPhaseInspection::class.qualifiedName!!
|
||||
val inspectionClass = Class.forName(inspectionClassName)
|
||||
|
||||
val matcher = "<!--\\s*problem:\\s*on\\s*([^,]+),\\s*title\\s*(.+)\\s*-->".toRegex()
|
||||
val expectedProblemsText = pomText.lines()
|
||||
.filter { matcher.matches(it) }
|
||||
.joinToString("\n")
|
||||
|
||||
val problemElements = runInspection(inspectionClass, myProject).problemElements
|
||||
val actualProblems = problemElements
|
||||
.filter { it.key.name == "pom.xml" }
|
||||
.values
|
||||
.flatMap { it.toList() }
|
||||
.mapNotNull { it as? ProblemDescriptorBase }
|
||||
|
||||
val actual = actualProblems
|
||||
.map { SimplifiedProblemDescription(it.descriptionTemplate, it.psiElement.text.replace("\\s+".toRegex(), "")) to it }
|
||||
.sortedBy { it.first.text }
|
||||
|
||||
val actualProblemsText = actual
|
||||
.map { it.first }
|
||||
.joinToString("\n") { "<!-- problem: on ${it.elementText}, title ${it.text} -->"}
|
||||
|
||||
assertEquals(expectedProblemsText, actualProblemsText)
|
||||
|
||||
val suggestedFixes = actual.flatMap { p -> p.second.fixes?.sortedBy { it.familyName }?.map { p.second to it } ?: emptyList() }
|
||||
|
||||
val filenamePrefix = pomFile.nameWithoutExtension + ".fixed."
|
||||
val fixFiles =
|
||||
pomFile.parentFile.listFiles { _, name -> name.startsWith(filenamePrefix) && name.endsWith(".xml") }.sortedBy { it.name }
|
||||
|
||||
val rangesToFixFiles: Map<File, IntRange> = fixFiles.keysToMap {
|
||||
val fixFileName = it.name
|
||||
val fixRangeStr = fixFileName.substringBeforeLast('.').substringAfterLast('.')
|
||||
val numbers = fixRangeStr.split('-').map { it.toInt() }
|
||||
when (numbers.size) {
|
||||
0 -> error("No number in fix file $fixFileName")
|
||||
1 -> IntRange(numbers[0], numbers[0])
|
||||
2 -> IntRange(numbers[0], numbers[1])
|
||||
else -> error("Bad range `$fixRangeStr` in fix file $fixFileName")
|
||||
}
|
||||
}
|
||||
|
||||
val sortedFixRanges = rangesToFixFiles.values.sortedBy { it.start }
|
||||
sortedFixRanges.forEachIndexed { i, range ->
|
||||
if (i > 0) {
|
||||
val previous = sortedFixRanges[i - 1]
|
||||
if (previous.endInclusive + 1 != range.start) {
|
||||
error("Bad ranges in fix files: $previous and $range")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val numberOfFixDataFiles = sortedFixRanges.lastOrNull()?.endInclusive ?: 0
|
||||
if (numberOfFixDataFiles > suggestedFixes.size) {
|
||||
fail("Not all fixes were suggested by the inspection: expected count: ${fixFiles.size}, actual fixes count: ${suggestedFixes.size}")
|
||||
}
|
||||
if (numberOfFixDataFiles < suggestedFixes.size) {
|
||||
fail("Not all fixes covered by *.fixed.N.xml files")
|
||||
}
|
||||
|
||||
val documentManager = PsiDocumentManager.getInstance(myProject)
|
||||
val document = documentManager.getDocument(PsiManager.getInstance(myProject).findFile(myProjectPom)!!)!!
|
||||
val originalText = document.text
|
||||
|
||||
suggestedFixes.forEachIndexed { index, suggestedFix ->
|
||||
val (problem, quickfix) = suggestedFix
|
||||
val file = rangesToFixFiles.entries.first { (_, range) -> index + 1 in range }.key
|
||||
|
||||
quickfix.applyFix(problem)
|
||||
|
||||
KotlinTestUtils.assertEqualsToFile(file, document.text.trim())
|
||||
|
||||
ApplicationManager.getApplication().runWriteAction {
|
||||
document.setText(originalText)
|
||||
documentManager.commitDocument(document)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createPomFile(fileName: String) {
|
||||
myProjectPom = myProjectRoot.findChild("pom.xml")
|
||||
if (myProjectPom == null) {
|
||||
myProjectPom = object : WriteAction<VirtualFile>() {
|
||||
override fun run(result: Result<VirtualFile>) {
|
||||
val res = myProjectRoot.createChildData(null, "pom.xml")
|
||||
result.setResult(res)
|
||||
}
|
||||
}.execute().resultObject
|
||||
}
|
||||
myAllPoms.add(myProjectPom!!)
|
||||
|
||||
ApplicationManager.getApplication().runWriteAction {
|
||||
myProjectPom!!.setBinaryContent(File(fileName).readBytes())
|
||||
}
|
||||
}
|
||||
|
||||
private fun QuickFix<CommonProblemDescriptor>.applyFix(desc: ProblemDescriptorBase) {
|
||||
CommandProcessor.getInstance().executeCommand(myProject, {
|
||||
ApplicationManager.getApplication().runWriteAction {
|
||||
applyFix(myProject, desc)
|
||||
|
||||
val manager = PsiDocumentManager.getInstance(myProject)
|
||||
val document = manager.getDocument(PsiManager.getInstance(myProject).findFile(myProjectPom)!!)!!
|
||||
manager.doPostponedOperationsAndUnblockDocument(document)
|
||||
manager.commitDocument(document)
|
||||
FileDocumentManager.getInstance().saveDocument(document)
|
||||
|
||||
}
|
||||
|
||||
println(myProjectPom.contentsToByteArray().toString(Charsets.UTF_8))
|
||||
}, "quick-fix-$name", "Kotlin")
|
||||
}
|
||||
|
||||
private fun mkJavaFile() {
|
||||
val sourceFolder =
|
||||
getContentRoots(myProject.allModules().single().name).single().getSourceFolders(JavaSourceRootType.SOURCE).single()
|
||||
ApplicationManager.getApplication().runWriteAction {
|
||||
val javaFile = sourceFolder.file?.toPsiDirectory(myProject)?.createFile("Test.java") ?: throw IllegalStateException()
|
||||
javaFile.viewProvider.document!!.setText("class Test {}\n")
|
||||
}
|
||||
|
||||
assertTrue(FileTypeIndex.containsFileOfType(JavaFileType.INSTANCE, myProject.allModules().single().moduleScope))
|
||||
}
|
||||
|
||||
private data class SimplifiedProblemDescription(val text: String, val elementText: String)
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* 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.console
|
||||
|
||||
import com.intellij.execution.process.OSProcessHandler
|
||||
import com.intellij.execution.process.ProcessOutputTypes
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import org.jetbrains.kotlin.console.actions.logError
|
||||
import org.jetbrains.kotlin.diagnostics.Severity
|
||||
import org.jetbrains.kotlin.utils.repl.ReplEscapeType
|
||||
import org.w3c.dom.Element
|
||||
import org.xml.sax.InputSource
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.nio.charset.Charset
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
import org.jetbrains.kotlin.utils.repl.ReplEscapeType.*
|
||||
|
||||
val XML_REPLACEMENTS: Array<String> = arrayOf("#n", "#diez")
|
||||
val SOURCE_CHARS: Array<String> = arrayOf("\n", "#")
|
||||
|
||||
data class SeverityDetails(val severity: Severity, val description: String, val range: TextRange)
|
||||
|
||||
class ReplOutputHandler(
|
||||
private val runner: KotlinConsoleRunner,
|
||||
process: Process,
|
||||
commandLine: String
|
||||
) : OSProcessHandler(process, commandLine) {
|
||||
|
||||
private var isBuildInfoChecked = false
|
||||
private val factory = DocumentBuilderFactory.newInstance()
|
||||
private val outputProcessor = ReplOutputProcessor(runner)
|
||||
private val inputBuffer = StringBuilder()
|
||||
|
||||
override fun isSilentlyDestroyOnClose() = true
|
||||
|
||||
override fun notifyTextAvailable(text: String, key: Key<*>?) {
|
||||
// hide warning about adding test folder to classpath
|
||||
if (text.startsWith("warning: classpath entry points to a non-existent location")) return
|
||||
|
||||
if (key == ProcessOutputTypes.STDOUT) {
|
||||
inputBuffer.append(text)
|
||||
val resultingText = inputBuffer.toString()
|
||||
if (resultingText.endsWith("\n")) {
|
||||
handleReplMessage(resultingText)
|
||||
inputBuffer.setLength(0)
|
||||
}
|
||||
}
|
||||
else {
|
||||
super.notifyTextAvailable(text, key)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleReplMessage(text: String) {
|
||||
if (text.isBlank()) return
|
||||
val output = try {
|
||||
factory.newDocumentBuilder().parse(strToSource(text))
|
||||
}
|
||||
catch (e: Exception) {
|
||||
logError(ReplOutputHandler::class.java, "Couldn't parse REPL output: $text")
|
||||
return
|
||||
}
|
||||
|
||||
val root = output.firstChild as Element
|
||||
val outputType = ReplEscapeType.valueOfOrNull(root.getAttribute("type"))
|
||||
val content = StringUtil.replace(root.textContent, XML_REPLACEMENTS, SOURCE_CHARS)
|
||||
|
||||
when (outputType) {
|
||||
INITIAL_PROMPT -> buildWarningIfNeededBeforeInit(content)
|
||||
HELP_PROMPT -> outputProcessor.printHelp(content)
|
||||
USER_OUTPUT -> outputProcessor.printUserOutput(content)
|
||||
REPL_RESULT -> outputProcessor.printResultWithGutterIcon(content)
|
||||
READLINE_START -> runner.isReadLineMode = true
|
||||
READLINE_END -> runner.isReadLineMode = false
|
||||
REPL_INCOMPLETE,
|
||||
COMPILE_ERROR -> outputProcessor.highlightCompilerErrors(createCompilerMessages(content))
|
||||
RUNTIME_ERROR -> outputProcessor.printRuntimeError("${content.trim()}\n")
|
||||
INTERNAL_ERROR -> outputProcessor.printInternalErrorMessage(content)
|
||||
SUCCESS -> runner.commandHistory.lastUnprocessedEntry()?.entryText?.let { runner.successfulLine(it) }
|
||||
null -> logError(ReplOutputHandler::class.java, "Unexpected output type:\n$outputType")
|
||||
}
|
||||
|
||||
if (outputType in setOf(SUCCESS, COMPILE_ERROR, INTERNAL_ERROR, RUNTIME_ERROR, READLINE_END)) {
|
||||
runner.commandHistory.entryProcessed()
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildWarningIfNeededBeforeInit(content: String) {
|
||||
if (!isBuildInfoChecked) {
|
||||
outputProcessor.printBuildInfoWarningIfNeeded()
|
||||
isBuildInfoChecked = true
|
||||
}
|
||||
outputProcessor.printInitialPrompt(content)
|
||||
}
|
||||
|
||||
private fun strToSource(s: String, encoding: Charset = Charsets.UTF_8) = InputSource(ByteArrayInputStream(s.toByteArray(encoding)))
|
||||
|
||||
private fun createCompilerMessages(runtimeErrorsReport: String): List<SeverityDetails> {
|
||||
val compilerMessages = arrayListOf<SeverityDetails>()
|
||||
|
||||
val report = factory.newDocumentBuilder().parse(strToSource(runtimeErrorsReport, Charsets.UTF_16))
|
||||
val entries = report.getElementsByTagName("reportEntry")
|
||||
for (i in 0..entries.length - 1) {
|
||||
val reportEntry = entries.item(i) as Element
|
||||
|
||||
val severityLevel = reportEntry.getAttribute("severity").toSeverity()
|
||||
val rangeStart = reportEntry.getAttribute("rangeStart").toInt()
|
||||
val rangeEnd = reportEntry.getAttribute("rangeEnd").toInt()
|
||||
val description = reportEntry.textContent
|
||||
|
||||
compilerMessages.add(SeverityDetails(severityLevel, description, TextRange(rangeStart, rangeEnd)))
|
||||
}
|
||||
|
||||
return compilerMessages
|
||||
}
|
||||
|
||||
private fun String.toSeverity() = when (this) {
|
||||
"ERROR" -> Severity.ERROR
|
||||
"WARNING" -> Severity.WARNING
|
||||
"INFO" -> Severity.INFO
|
||||
else -> throw IllegalArgumentException("Unsupported Severity: '$this'") // this case shouldn't occur
|
||||
}
|
||||
}
|
||||
@@ -68,11 +68,6 @@ public class MockParameterInfoUIContext implements ParameterInfoUIContext {
|
||||
return myParameterOwner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSingleParameterInfo() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getDefaultParameterColor() {
|
||||
return null;
|
||||
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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.idea.parameterInfo;
|
||||
|
||||
import com.intellij.lang.parameterInfo.UpdateParameterInfoContext;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class MockUpdateParameterInfoContext implements UpdateParameterInfoContext {
|
||||
private int myCurrentParameter = -1;
|
||||
private PsiFile myFile;
|
||||
private JavaCodeInsightTestFixture myFixture;
|
||||
|
||||
MockUpdateParameterInfoContext(PsiFile file, JavaCodeInsightTestFixture fixture) {
|
||||
myFile = file;
|
||||
myFixture = fixture;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void removeHint() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParameterOwner(PsiElement o) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PsiElement getParameterOwner() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHighlightedParameter(Object parameter) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getHighlightedParameter() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCurrentParameter(int index) {
|
||||
myCurrentParameter = index;
|
||||
}
|
||||
|
||||
public int getCurrentParameter() {
|
||||
return myCurrentParameter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUIComponentEnabled(int index) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUIComponentEnabled(int index, boolean b) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getParameterListStart() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] getObjectsToView() {
|
||||
return ArrayUtil.EMPTY_OBJECT_ARRAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Project getProject() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PsiFile getFile() {
|
||||
return myFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOffset() {
|
||||
return myFixture.getCaretOffset();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Editor getEditor() {
|
||||
return myFixture.getEditor();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user