diff --git a/idea/src/org/jetbrains/kotlin/idea/j2k/J2kPostProcessings.kt b/idea/src/org/jetbrains/kotlin/idea/j2k/J2kPostProcessings.kt index e48c021e5ea..cf597a55b8d 100644 --- a/idea/src/org/jetbrains/kotlin/idea/j2k/J2kPostProcessings.kt +++ b/idea/src/org/jetbrains/kotlin/idea/j2k/J2kPostProcessings.kt @@ -16,14 +16,23 @@ package org.jetbrains.kotlin.idea.j2k +import com.intellij.psi.search.LocalSearchScope +import com.intellij.psi.search.searches.ReferencesSearch +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.diagnostics.DiagnosticFactory +import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.idea.intentions.* import org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions.IfThenToElvisIntention import org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions.IfThenToSafeAccessIntention +import org.jetbrains.kotlin.idea.quickfix.RemoveModifierFix +import org.jetbrains.kotlin.idea.quickfix.RemoveRightPartOfBinaryExpressionFix +import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics import java.util.ArrayList interface J2kPostProcessing { - fun createAction(element: JetElement): (() -> Unit)? + fun createAction(element: JetElement, diagnostics: Diagnostics): (() -> Unit)? } object J2KPostProcessingRegistrar { @@ -37,19 +46,51 @@ object J2KPostProcessingRegistrar { _processings.add(MoveLambdaOutsideParenthesesProcessing()) _processings.add(ConvertToStringTemplateProcessing()) - registerTypicalIntentionBasedProcessing(UsePropertyAccessSyntaxIntention()) { applyTo(it) } - registerTypicalIntentionBasedProcessing(IfThenToSafeAccessIntention()) { applyTo(it) } - registerTypicalIntentionBasedProcessing(IfThenToElvisIntention()) { applyTo(it) } - registerTypicalIntentionBasedProcessing(IfNullToElvisIntention()) { applyTo(it) } - registerTypicalIntentionBasedProcessing(SimplifyNegatedBinaryExpressionIntention()) { applyTo(it) } + registerIntentionBasedProcessing(UsePropertyAccessSyntaxIntention()) { applyTo(it) } + registerIntentionBasedProcessing(IfThenToSafeAccessIntention()) { applyTo(it) } + registerIntentionBasedProcessing(IfThenToElvisIntention()) { applyTo(it) } + registerIntentionBasedProcessing(IfNullToElvisIntention()) { applyTo(it) } + registerIntentionBasedProcessing(SimplifyNegatedBinaryExpressionIntention()) { applyTo(it) } + + registerDiagnosticBasedProcessing(Errors.USELESS_CAST) { element, diagnostic -> + val expression = RemoveRightPartOfBinaryExpressionFix(element, "").invoke() + + val variable = expression.parent as? JetProperty + if (variable != null && expression == variable.initializer && variable.isLocal) { + val refs = ReferencesSearch.search(variable, LocalSearchScope(variable.containingFile)).findAll() + for (ref in refs) { + val usage = ref.element as? JetSimpleNameExpression ?: continue + usage.replace(expression) + } + variable.delete() + } + } + + registerDiagnosticBasedProcessing(Errors.REDUNDANT_PROJECTION) { element, diagnostic -> + val fix = RemoveModifierFix.createRemoveProjectionFactory(true).createActions(diagnostic).single() as RemoveModifierFix + fix.invoke() + } + + registerDiagnosticBasedProcessing(Errors.UNNECESSARY_NOT_NULL_ASSERTION) { element, diagnostic -> + val exclExclExpr = element.parent as JetUnaryExpression + exclExclExpr.replace(exclExclExpr.baseExpression!!) + } + + registerDiagnosticBasedProcessing(Errors.VAL_REASSIGNMENT) { element, diagnostic -> + val property = element.mainReference.resolve() as? JetProperty + if (property != null && !property.isVar) { + val factory = JetPsiFactory(element.project) + property.valOrVarKeyword.replace(factory.createVarKeyword()) + } + } } - private inline fun > registerTypicalIntentionBasedProcessing( + private inline fun > registerIntentionBasedProcessing( intention: TIntention, inlineOptions(InlineOption.ONLY_LOCAL_RETURN) apply: TIntention.(TElement) -> Unit ) { _processings.add(object : J2kPostProcessing { - override fun createAction(element: JetElement): (() -> Unit)? { + override fun createAction(element: JetElement, diagnostics: Diagnostics): (() -> Unit)? { if (!javaClass().isInstance(element)) return null @suppress("UNCHECKED_CAST") if (intention.applicabilityRange(element as TElement) == null) return null @@ -58,8 +99,21 @@ object J2KPostProcessingRegistrar { }) } + private inline fun registerDiagnosticBasedProcessing( + diagnosticFactory: DiagnosticFactory<*>, + inlineOptions(InlineOption.ONLY_LOCAL_RETURN) fix: (TElement, Diagnostic) -> Unit + ) { + _processings.add(object : J2kPostProcessing { + override fun createAction(element: JetElement, diagnostics: Diagnostics): (() -> Unit)? { + if (!javaClass().isInstance(element)) return null + val diagnostic = diagnostics.forElement(element).firstOrNull { it.factory == diagnosticFactory } ?: return null + return { fix(element as TElement, diagnostic) } + } + }) + } + private class RemoveExplicitTypeArgumentsProcessing : J2kPostProcessing { - override fun createAction(element: JetElement): (() -> Unit)? { + override fun createAction(element: JetElement, diagnostics: Diagnostics): (() -> Unit)? { if (element !is JetTypeArgumentList || !RemoveExplicitTypeArgumentsIntention.isApplicableTo(element, approximateFlexible = true)) return null return { element.delete() } @@ -69,7 +123,7 @@ object J2KPostProcessingRegistrar { private class MoveLambdaOutsideParenthesesProcessing : J2kPostProcessing { private val intention = MoveLambdaOutsideParenthesesIntention() - override fun createAction(element: JetElement): (() -> Unit)? { + override fun createAction(element: JetElement, diagnostics: Diagnostics): (() -> Unit)? { if (element !is JetCallExpression) return null val literalArgument = element.valueArguments.lastOrNull()?.getArgumentExpression()?.unpackFunctionLiteral() ?: return null if (!intention.isApplicableTo(element, literalArgument.textOffset)) return null @@ -80,7 +134,7 @@ object J2KPostProcessingRegistrar { private class ConvertToStringTemplateProcessing : J2kPostProcessing { private val intention = ConvertToStringTemplateIntention() - override fun createAction(element: JetElement): (() -> Unit)? { + override fun createAction(element: JetElement, diagnostics: Diagnostics): (() -> Unit)? { if (element is JetBinaryExpression && intention.isApplicableTo(element) && intention.isConversionResultSimple(element)) { return { intention.applyTo(element) } } @@ -89,5 +143,4 @@ object J2KPostProcessingRegistrar { } } } - } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/j2k/J2kPostProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/j2k/J2kPostProcessor.kt index 404210c81b1..274ec7f8f3c 100644 --- a/idea/src/org/jetbrains/kotlin/idea/j2k/J2kPostProcessor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/j2k/J2kPostProcessor.kt @@ -21,66 +21,23 @@ import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiElement import com.intellij.psi.PsiRecursiveElementVisitor import com.intellij.psi.codeStyle.CodeStyleManager -import com.intellij.psi.search.LocalSearchScope -import com.intellij.psi.search.searches.ReferencesSearch import com.intellij.util.SmartList -import org.jetbrains.kotlin.diagnostics.Diagnostic -import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference -import org.jetbrains.kotlin.idea.quickfix.RemoveModifierFix -import org.jetbrains.kotlin.idea.quickfix.RemoveRightPartOfBinaryExpressionFix -import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.idea.conversion.copy.range import org.jetbrains.kotlin.idea.util.ImportInsertHelper import org.jetbrains.kotlin.j2k.PostProcessor import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.JetElement +import org.jetbrains.kotlin.psi.JetFile import org.jetbrains.kotlin.psi.psiUtil.elementsInRange -import org.jetbrains.kotlin.resolve.BindingContext -import java.util.HashSet +import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics import java.util.LinkedHashMap public class J2kPostProcessor(private val formatCode: Boolean) : PostProcessor { - override fun analyzeFile(file: JetFile, range: TextRange?): BindingContext { - val elements = if (range == null) { - listOf(file) - } - else { - file.elementsInRange(range).filterIsInstance() - } - if (elements.isEmpty()) return BindingContext.EMPTY - return file.getResolutionFacade().analyzeFullyAndGetResult(elements).bindingContext - } - override fun insertImport(file: JetFile, fqName: FqName) { val descriptors = file.resolveImportReference(fqName) - descriptors.firstOrNull()?.let { ImportInsertHelper.getInstance(file.getProject()).importDescriptor(file, it) } - } - - override fun fixForProblem(problem: Diagnostic): (() -> Unit)? { - val psiElement = problem.getPsiElement() - return when (problem.getFactory()) { - Errors.USELESS_CAST -> { -> - val expression = RemoveRightPartOfBinaryExpressionFix(psiElement as JetBinaryExpressionWithTypeRHS, "").invoke() - - val variable = expression.getParent() as? JetProperty - if (variable != null && expression == variable.getInitializer() && variable.isLocal()) { - val refs = ReferencesSearch.search(variable, LocalSearchScope(variable.getContainingFile())).findAll() - for (ref in refs) { - val usage = ref.getElement() as? JetSimpleNameExpression ?: continue - usage.replace(expression) - } - variable.delete() - } - } - - Errors.REDUNDANT_PROJECTION -> { -> - val fix = RemoveModifierFix.createRemoveProjectionFactory(true).createActions(problem).single() as RemoveModifierFix - fix.invoke() - } - - else -> super.fixForProblem(problem) - } + descriptors.firstOrNull()?.let { ImportInsertHelper.getInstance(file.project).importDescriptor(file, it) } } private enum class RangeFilterResult { @@ -93,7 +50,7 @@ public class J2kPostProcessor(private val formatCode: Boolean) : PostProcessor { var elementToActions = collectAvailableActions(file, rangeMarker) while (elementToActions.isNotEmpty()) { - val processingsToRerun = HashSet() + var modificationStamp: Long? = file.modificationStamp for ((element, actions) in elementToActions) { for ((action, processing) in actions) { @@ -101,21 +58,21 @@ public class J2kPostProcessor(private val formatCode: Boolean) : PostProcessor { action() } else { - processingsToRerun.add(processing) + modificationStamp = null } } } - if (processingsToRerun.isEmpty()) break - //TODO: it looks like there are no such cases currently, add tests later on or drop this - elementToActions = collectAvailableActions(file, rangeMarker, processingFilter = { it in processingsToRerun }) + if (modificationStamp == file.modificationStamp) break + + elementToActions = collectAvailableActions(file, rangeMarker) } if (formatCode) { - val codeStyleManager = CodeStyleManager.getInstance(file.getProject()) + val codeStyleManager = CodeStyleManager.getInstance(file.project) if (rangeMarker != null) { - if (rangeMarker.isValid()) { - codeStyleManager.reformatRange(file, rangeMarker.getStartOffset(), rangeMarker.getEndOffset()) + if (rangeMarker.isValid) { + codeStyleManager.reformatRange(file, rangeMarker.startOffset, rangeMarker.endOffset) } } else { @@ -126,12 +83,9 @@ public class J2kPostProcessor(private val formatCode: Boolean) : PostProcessor { private data class ActionData(val action: () -> Unit, val processing: J2kPostProcessing) - private fun collectAvailableActions( - file: JetFile, - rangeMarker: RangeMarker?, - processingFilter: (J2kPostProcessing) -> Boolean = { true } - ): LinkedHashMap> { - val processings = J2KPostProcessingRegistrar.processings.filter(processingFilter) + private fun collectAvailableActions(file: JetFile, rangeMarker: RangeMarker?): LinkedHashMap> { + val diagnostics = analyzeFileRange(file, rangeMarker) + val elementToActions = LinkedHashMap>() file.accept(object : PsiRecursiveElementVisitor(){ @@ -143,8 +97,8 @@ public class J2kPostProcessor(private val formatCode: Boolean) : PostProcessor { super.visitElement(element) if (rangeResult == RangeFilterResult.PROCESS) { - processings.forEach { processing -> - val action = processing.createAction(element) + J2KPostProcessingRegistrar.processings.forEach { processing -> + val action = processing.createAction(element, diagnostics) if (action != null) { elementToActions.getOrPut(element) { SmartList() }.add(ActionData(action, processing)) } @@ -157,6 +111,18 @@ public class J2kPostProcessor(private val formatCode: Boolean) : PostProcessor { return elementToActions } + private fun analyzeFileRange(file: JetFile, rangeMarker: RangeMarker?): Diagnostics { + val elements = if (rangeMarker == null) + listOf(file) + else + file.elementsInRange(rangeMarker.range!!).filterIsInstance() + + return if (elements.isNotEmpty()) + file.getResolutionFacade().analyzeFullyAndGetResult(elements).bindingContext.diagnostics + else + Diagnostics.EMPTY + } + private fun rangeFilter(element: PsiElement, rangeMarker: RangeMarker?): RangeFilterResult { if (rangeMarker == null) return RangeFilterResult.PROCESS if (!rangeMarker.isValid) return RangeFilterResult.SKIP @@ -168,6 +134,4 @@ public class J2kPostProcessor(private val formatCode: Boolean) : PostProcessor { else -> RangeFilterResult.SKIP } } - - override fun simpleNameReference(nameExpression: JetSimpleNameExpression) = nameExpression.mainReference } diff --git a/j2k/src/org/jetbrains/kotlin/j2k/AfterConversionPass.kt b/j2k/src/org/jetbrains/kotlin/j2k/AfterConversionPass.kt index 4c4cade5729..45a1a77192d 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/AfterConversionPass.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/AfterConversionPass.kt @@ -18,44 +18,21 @@ package org.jetbrains.kotlin.j2k import com.intellij.openapi.project.Project import com.intellij.openapi.util.TextRange -import com.intellij.psi.PsiElement -import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.psi.JetFile public class AfterConversionPass(val project: Project, val postProcessor: PostProcessor) { public fun run(kotlinFile: JetFile, range: TextRange?) { - val bindingContext = postProcessor.analyzeFile(kotlinFile, range) - - fun fixForProblem(diagnostic: Diagnostic): (() -> Unit)? { - val psiElement = diagnostic.getPsiElement() - if (range != null && psiElement.getTextRange() !in range) return null - return postProcessor.fixForProblem(diagnostic) - } - - val fixes = bindingContext.getDiagnostics() - .map { - val fix = fixForProblem(it) - if (fix != null) Pair(it.getPsiElement(), fix) else null - } - .filterNotNull() - val rangeMarker = if (range != null) { - val document = kotlinFile.getViewProvider().getDocument()!! - val marker = document.createRangeMarker(range.getStartOffset(), range.getEndOffset()) - marker.setGreedyToLeft(true) - marker.setGreedyToRight(true) + val document = kotlinFile.viewProvider.document!! + val marker = document.createRangeMarker(range.startOffset, range.endOffset) + marker.isGreedyToLeft = true + marker.isGreedyToRight = true marker } else { null } - for ((psiElement, fix) in fixes) { - if (psiElement.isValid()) { - fix() - } - } - postProcessor.doAdditionalProcessing(kotlinFile, rangeMarker) } } diff --git a/j2k/src/org/jetbrains/kotlin/j2k/JavaToKotlinConverter.kt b/j2k/src/org/jetbrains/kotlin/j2k/JavaToKotlinConverter.kt index 4b541787591..05c91d78a25 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/JavaToKotlinConverter.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/JavaToKotlinConverter.kt @@ -17,59 +17,28 @@ package org.jetbrains.kotlin.j2k import com.intellij.lang.java.JavaLanguage -import com.intellij.openapi.application.ModalityState import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.editor.RangeMarker import com.intellij.openapi.progress.* import com.intellij.openapi.project.Project -import com.intellij.openapi.util.TextRange import com.intellij.psi.* import com.intellij.psi.impl.source.DummyHolder -import org.jetbrains.kotlin.diagnostics.Diagnostic -import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.idea.JetLanguage import org.jetbrains.kotlin.j2k.ast.Element import org.jetbrains.kotlin.j2k.usageProcessing.UsageProcessing import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.JetFile +import org.jetbrains.kotlin.psi.JetPsiFactory import org.jetbrains.kotlin.psi.psiUtil.isAncestor import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf -import org.jetbrains.kotlin.resolve.BindingContext import java.util.ArrayList import java.util.Comparator import java.util.LinkedHashMap public interface PostProcessor { - public fun analyzeFile(file: JetFile, range: TextRange?): BindingContext - public fun insertImport(file: JetFile, fqName: FqName) - public open fun fixForProblem(problem: Diagnostic): (() -> Unit)? { - val psiElement = problem.getPsiElement() - return when (problem.getFactory()) { - Errors.UNNECESSARY_NOT_NULL_ASSERTION -> { -> - val exclExclOp = psiElement as JetSimpleNameExpression - val exclExclExpr = exclExclOp.getParent() as JetUnaryExpression - exclExclExpr.replace(exclExclExpr.getBaseExpression()!!) - } - - Errors.VAL_REASSIGNMENT -> { -> - if (psiElement is JetSimpleNameExpression) { - val property = simpleNameReference(psiElement).resolve() as? JetProperty - if (property != null && !property.isVar()) { - val factory = JetPsiFactory(psiElement.getProject()) - property.getValOrVarKeyword().replace(factory.createVarKeyword()) - } - } - } - - else -> null - } - } - public fun doAdditionalProcessing(file: JetFile, rangeMarker: RangeMarker?) - - public fun simpleNameReference(nameExpression: JetSimpleNameExpression): PsiReference } public enum class ParseContext { diff --git a/j2k/testData/fileOrElement/issues/kt-824-isDir.kt b/j2k/testData/fileOrElement/issues/kt-824-isDir.kt index 11476257c81..ea45e7794bf 100644 --- a/j2k/testData/fileOrElement/issues/kt-824-isDir.kt +++ b/j2k/testData/fileOrElement/issues/kt-824-isDir.kt @@ -11,7 +11,7 @@ public object Test { return false } val result = true - if (parent.isDirectory()) { + if (parent.isDirectory) { return true } else return false