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:
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.intentions
|
||||
|
||||
import com.intellij.codeInspection.ProblemHighlightType
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
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.isVisible
|
||||
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.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelector
|
||||
@@ -49,6 +47,10 @@ class UsePropertyAccessSyntaxIntention : JetSelfTargetingOffsetIndependentIntent
|
||||
}
|
||||
|
||||
override fun applyTo(element: JetCallExpression, editor: Editor) {
|
||||
applyTo(element)
|
||||
}
|
||||
|
||||
public fun applyTo(element: JetCallExpression) {
|
||||
val propertyName = findExtensionPropertyToUse(element)!!.getName()
|
||||
val arguments = element.getValueArguments()
|
||||
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.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.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
|
||||
@@ -38,7 +37,8 @@ import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.elementsInRange
|
||||
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 {
|
||||
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?) {
|
||||
fun rangeFilter(element: PsiElement): RangeFilterResult {
|
||||
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
|
||||
}
|
||||
}
|
||||
var elementToActions = collectAvailableActions(file, rangeMarker)
|
||||
|
||||
val redundantTypeArgs = ArrayList<JetTypeArgumentList>()
|
||||
file.accept(object : JetTreeVisitorVoid(){
|
||||
override fun visitElement(element: PsiElement) {
|
||||
if (rangeFilter(element) != RangeFilterResult.SKIP) {
|
||||
super.visitElement(element)
|
||||
}
|
||||
}
|
||||
while (elementToActions.isNotEmpty()) {
|
||||
val processingsToRerun = HashSet<J2kPostProcessing>()
|
||||
|
||||
override fun visitTypeArgumentList(typeArgumentList: JetTypeArgumentList) {
|
||||
if (rangeFilter(typeArgumentList) == RangeFilterResult.PROCESS && RemoveExplicitTypeArgumentsIntention.isApplicableTo(typeArgumentList, approximateFlexible = true)) {
|
||||
redundantTypeArgs.add(typeArgumentList)
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
for ((element, actions) in elementToActions) {
|
||||
for ((action, processing) in actions) {
|
||||
if (element.isValid) {
|
||||
action()
|
||||
}
|
||||
|
||||
run {
|
||||
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)
|
||||
else {
|
||||
processingsToRerun.add(processing)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
for (typeArgs in redundantTypeArgs) {
|
||||
typeArgs.delete()
|
||||
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 (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
|
||||
}
|
||||
|
||||
Vendored
+3
@@ -49,6 +49,9 @@ public enum E {
|
||||
|
||||
public class Base {
|
||||
public @Nullable String foo(@Nullable String s) { return s; }
|
||||
|
||||
public int getProperty() { return 1; }
|
||||
public void setProperty(int value) {}
|
||||
}
|
||||
|
||||
public class Derived extends Base {
|
||||
|
||||
@@ -2,7 +2,7 @@ import java.io.File
|
||||
|
||||
class C {
|
||||
fun foo(file: File): String {
|
||||
val parent = file.getParentFile() ?: return ""
|
||||
val parent = file.parentFile ?: return ""
|
||||
return parent.getName()
|
||||
}
|
||||
}
|
||||
|
||||
+15
@@ -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();
|
||||
}
|
||||
}
|
||||
+15
@@ -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");
|
||||
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")
|
||||
|
||||
@@ -990,6 +990,12 @@ public class JavaToKotlinConverterSingleFileTestGenerated extends AbstractJavaTo
|
||||
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/codeSimplifications/RedundantTypeCastAndInline.java");
|
||||
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")
|
||||
|
||||
Reference in New Issue
Block a user