Implemented more general scheme of post-processing with intentions in J2K + added replacement of get/set with property access there

This commit is contained in:
Valentin Kipyatkov
2015-08-11 19:07:10 +03:00
parent 671b9f5d56
commit e66d0f71ac
9 changed files with 204 additions and 95 deletions
@@ -16,7 +16,6 @@
package org.jetbrains.kotlin.idea.intentions package org.jetbrains.kotlin.idea.intentions
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithVisibility import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithVisibility
@@ -28,7 +27,6 @@ import org.jetbrains.kotlin.idea.codeInsight.ReferenceVariantsHelper
import org.jetbrains.kotlin.idea.core.getResolutionScope import org.jetbrains.kotlin.idea.core.getResolutionScope
import org.jetbrains.kotlin.idea.core.isVisible import org.jetbrains.kotlin.idea.core.isVisible
import org.jetbrains.kotlin.idea.inspections.IntentionBasedInspection import org.jetbrains.kotlin.idea.inspections.IntentionBasedInspection
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelector import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelector
@@ -49,6 +47,10 @@ class UsePropertyAccessSyntaxIntention : JetSelfTargetingOffsetIndependentIntent
} }
override fun applyTo(element: JetCallExpression, editor: Editor) { override fun applyTo(element: JetCallExpression, editor: Editor) {
applyTo(element)
}
public fun applyTo(element: JetCallExpression) {
val propertyName = findExtensionPropertyToUse(element)!!.getName() val propertyName = findExtensionPropertyToUse(element)!!.getName()
val arguments = element.getValueArguments() val arguments = element.getValueArguments()
when (arguments.size()) { when (arguments.size()) {
@@ -0,0 +1,93 @@
/*
* 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.j2k
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.psi.*
import java.util.ArrayList
interface J2kPostProcessing {
fun createAction(element: JetElement): (() -> Unit)?
}
object J2KPostProcessingRegistrar {
private val _processings = ArrayList<J2kPostProcessing>()
val processings: Collection<J2kPostProcessing>
get() = _processings
init {
_processings.add(RemoveExplicitTypeArgumentsProcessing())
_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) }
}
private inline fun <reified TElement : JetElement, TIntention: JetSelfTargetingRangeIntention<TElement>> registerTypicalIntentionBasedProcessing(
intention: TIntention,
inlineOptions(InlineOption.ONLY_LOCAL_RETURN) apply: TIntention.(TElement) -> Unit
) {
_processings.add(object : J2kPostProcessing {
override fun createAction(element: JetElement): (() -> Unit)? {
if (!javaClass<TElement>().isInstance(element)) return null
@suppress("UNCHECKED_CAST")
if (intention.applicabilityRange(element as TElement) == null) return null
return { intention.apply(element) }
}
})
}
private class RemoveExplicitTypeArgumentsProcessing : J2kPostProcessing {
override fun createAction(element: JetElement): (() -> Unit)? {
if (element !is JetTypeArgumentList || !RemoveExplicitTypeArgumentsIntention.isApplicableTo(element, approximateFlexible = true)) return null
return { element.delete() }
}
}
private class MoveLambdaOutsideParenthesesProcessing : J2kPostProcessing {
private val intention = MoveLambdaOutsideParenthesesIntention()
override fun createAction(element: JetElement): (() -> Unit)? {
if (element !is JetCallExpression) return null
val literalArgument = element.valueArguments.lastOrNull()?.getArgumentExpression()?.unpackFunctionLiteral() ?: return null
if (!intention.isApplicableTo(element, literalArgument.textOffset)) return null
return { intention.applyTo(element) }
}
}
private class ConvertToStringTemplateProcessing : J2kPostProcessing {
private val intention = ConvertToStringTemplateIntention()
override fun createAction(element: JetElement): (() -> Unit)? {
if (element is JetBinaryExpression && intention.isApplicableTo(element) && intention.isConversionResultSimple(element)) {
return { intention.applyTo(element) }
}
else {
return null
}
}
}
}
@@ -19,16 +19,15 @@ package org.jetbrains.kotlin.idea.j2k
import com.intellij.openapi.editor.RangeMarker import com.intellij.openapi.editor.RangeMarker
import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiRecursiveElementVisitor
import com.intellij.psi.codeStyle.CodeStyleManager import com.intellij.psi.codeStyle.CodeStyleManager
import com.intellij.psi.search.LocalSearchScope import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.search.searches.ReferencesSearch import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.util.SmartList
import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference
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.RemoveModifierFix
import org.jetbrains.kotlin.idea.quickfix.RemoveRightPartOfBinaryExpressionFix import org.jetbrains.kotlin.idea.quickfix.RemoveRightPartOfBinaryExpressionFix
import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.references.mainReference
@@ -38,7 +37,8 @@ import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.elementsInRange import org.jetbrains.kotlin.psi.psiUtil.elementsInRange
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import java.util.ArrayList import java.util.HashSet
import java.util.LinkedHashMap
public class J2kPostProcessor(private val formatCode: Boolean) : PostProcessor { public class J2kPostProcessor(private val formatCode: Boolean) : PostProcessor {
override fun analyzeFile(file: JetFile, range: TextRange?): BindingContext { override fun analyzeFile(file: JetFile, range: TextRange?): BindingContext {
@@ -90,101 +90,25 @@ public class J2kPostProcessor(private val formatCode: Boolean) : PostProcessor {
} }
override fun doAdditionalProcessing(file: JetFile, rangeMarker: RangeMarker?) { override fun doAdditionalProcessing(file: JetFile, rangeMarker: RangeMarker?) {
fun rangeFilter(element: PsiElement): RangeFilterResult { var elementToActions = collectAvailableActions(file, rangeMarker)
if (rangeMarker == null) return RangeFilterResult.PROCESS
if (!rangeMarker.isValid()) return RangeFilterResult.SKIP
val range = TextRange(rangeMarker.getStartOffset(), rangeMarker.getEndOffset())
val elementRange = element.getTextRange()
return when {
range.contains(elementRange) -> RangeFilterResult.PROCESS
range.intersects(elementRange) -> RangeFilterResult.GO_INSIDE
else -> RangeFilterResult.SKIP
}
}
val redundantTypeArgs = ArrayList<JetTypeArgumentList>() while (elementToActions.isNotEmpty()) {
file.accept(object : JetTreeVisitorVoid(){ val processingsToRerun = HashSet<J2kPostProcessing>()
override fun visitElement(element: PsiElement) {
if (rangeFilter(element) != RangeFilterResult.SKIP) {
super.visitElement(element)
}
}
override fun visitTypeArgumentList(typeArgumentList: JetTypeArgumentList) { for ((element, actions) in elementToActions) {
if (rangeFilter(typeArgumentList) == RangeFilterResult.PROCESS && RemoveExplicitTypeArgumentsIntention.isApplicableTo(typeArgumentList, approximateFlexible = true)) { for ((action, processing) in actions) {
redundantTypeArgs.add(typeArgumentList) if (element.isValid) {
return action()
}
super.visitTypeArgumentList(typeArgumentList)
}
override fun visitPrefixExpression(expression: JetPrefixExpression) {
super.visitPrefixExpression(expression)
val intention = SimplifyNegatedBinaryExpressionIntention()
if (rangeFilter(expression) == RangeFilterResult.PROCESS && intention.isApplicableTo(expression)) {
intention.applyTo(expression)
}
}
override fun visitIfExpression(expression: JetIfExpression) {
super.visitIfExpression(expression)
if (rangeFilter(expression) == RangeFilterResult.PROCESS) {
run {
val intention = IfThenToSafeAccessIntention()
if (intention.isApplicableTo(expression)) {
intention.applyTo(expression)
return
}
} }
else {
run { processingsToRerun.add(processing)
val intention = IfThenToElvisIntention()
if (intention.isApplicableTo(expression)) {
intention.applyTo(expression)
return
}
}
run {
val intention = IfNullToElvisIntention()
if (intention.applicabilityRange(expression) != null) {
intention.applyTo(expression)
return
}
}
}
}
override fun visitBinaryExpression(expression: JetBinaryExpression) {
val intention = ConvertToStringTemplateIntention()
if (intention.isApplicableTo(expression) && intention.isConversionResultSimple(expression)) {
val result = intention.applyTo(expression)
result.accept(this)
}
else {
super.visitBinaryExpression(expression)
}
}
override fun visitCallExpression(expression: JetCallExpression) {
super.visitCallExpression(expression)
val literalArgument = expression.valueArguments.lastOrNull()?.getArgumentExpression()?.unpackFunctionLiteral()
if (literalArgument != null) {
val intention = MoveLambdaOutsideParenthesesIntention()
if (intention.isApplicableTo(expression, literalArgument.textOffset)) {
intention.applyTo(expression)
} }
} }
} }
})
for (typeArgs in redundantTypeArgs) { if (processingsToRerun.isEmpty()) break
typeArgs.delete() //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 (formatCode) { if (formatCode) {
@@ -200,5 +124,50 @@ 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<JetElement, SmartList<ActionData>> {
val processings = J2KPostProcessingRegistrar.processings.filter(processingFilter)
val elementToActions = LinkedHashMap<JetElement, SmartList<ActionData>>()
file.accept(object : PsiRecursiveElementVisitor(){
override fun visitElement(element: PsiElement) {
if (element is JetElement) {
val rangeResult = rangeFilter(element, rangeMarker)
if (rangeResult == RangeFilterResult.SKIP) return
super.visitElement(element)
if (rangeResult == RangeFilterResult.PROCESS) {
processings.forEach { processing ->
val action = processing.createAction(element)
if (action != null) {
elementToActions.getOrPut(element) { SmartList() }.add(ActionData(action, processing))
}
}
}
}
}
})
return elementToActions
}
private fun rangeFilter(element: PsiElement, rangeMarker: RangeMarker?): RangeFilterResult {
if (rangeMarker == null) return RangeFilterResult.PROCESS
if (!rangeMarker.isValid) return RangeFilterResult.SKIP
val range = TextRange(rangeMarker.startOffset, rangeMarker.endOffset)
val elementRange = element.textRange
return when {
range.contains(elementRange) -> RangeFilterResult.PROCESS
range.intersects(elementRange) -> RangeFilterResult.GO_INSIDE
else -> RangeFilterResult.SKIP
}
}
override fun simpleNameReference(nameExpression: JetSimpleNameExpression) = nameExpression.mainReference override fun simpleNameReference(nameExpression: JetSimpleNameExpression) = nameExpression.mainReference
} }
+3
View File
@@ -49,6 +49,9 @@ public enum E {
public class Base { public class Base {
public @Nullable String foo(@Nullable String s) { return s; } public @Nullable String foo(@Nullable String s) { return s; }
public int getProperty() { return 1; }
public void setProperty(int value) {}
} }
public class Derived extends Base { public class Derived extends Base {
@@ -2,7 +2,7 @@ import java.io.File
class C { class C {
fun foo(file: File): String { fun foo(file: File): String {
val parent = file.getParentFile() ?: return "" val parent = file.parentFile ?: return ""
return parent.getName() return parent.getName()
} }
} }
@@ -0,0 +1,15 @@
import javaApi.Base;
class C extends Base {
public void f() {
Base other = Base();
int value = other.getProperty() + getProperty();
other.setProperty(1);
setProperty(other.getProperty() + value);
getBase(getProperty()).setProperty(0);
}
private Base getBase(int i) {
return new Base();
}
}
@@ -0,0 +1,15 @@
import javaApi.Base
class C : Base() {
public fun f() {
val other = Base()
val value = other.property + property
other.property = 1
property = other.property + value
getBase(property).property = 0
}
private fun getBase(i: Int): Base {
return Base()
}
}
@@ -990,6 +990,12 @@ public class JavaToKotlinConverterForWebDemoTestGenerated extends AbstractJavaTo
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/codeSimplifications/RedundantTypeCastAndInline.java"); String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/codeSimplifications/RedundantTypeCastAndInline.java");
doTest(fileName); doTest(fileName);
} }
@TestMetadata("SyntheticExtensionPropertyAccess.java")
public void testSyntheticExtensionPropertyAccess() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/codeSimplifications/SyntheticExtensionPropertyAccess.java");
doTest(fileName);
}
} }
@TestMetadata("j2k/testData/fileOrElement/comments") @TestMetadata("j2k/testData/fileOrElement/comments")
@@ -990,6 +990,12 @@ public class JavaToKotlinConverterSingleFileTestGenerated extends AbstractJavaTo
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/codeSimplifications/RedundantTypeCastAndInline.java"); String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/codeSimplifications/RedundantTypeCastAndInline.java");
doTest(fileName); doTest(fileName);
} }
@TestMetadata("SyntheticExtensionPropertyAccess.java")
public void testSyntheticExtensionPropertyAccess() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/codeSimplifications/SyntheticExtensionPropertyAccess.java");
doTest(fileName);
}
} }
@TestMetadata("j2k/testData/fileOrElement/comments") @TestMetadata("j2k/testData/fileOrElement/comments")