J2k post-processings based on diagnostics are included into the common scheme
This commit is contained in:
@@ -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<JetBinaryExpressionWithTypeRHS>(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<JetTypeProjection>(Errors.REDUNDANT_PROJECTION) { element, diagnostic ->
|
||||
val fix = RemoveModifierFix.createRemoveProjectionFactory(true).createActions(diagnostic).single() as RemoveModifierFix
|
||||
fix.invoke()
|
||||
}
|
||||
|
||||
registerDiagnosticBasedProcessing<JetSimpleNameExpression>(Errors.UNNECESSARY_NOT_NULL_ASSERTION) { element, diagnostic ->
|
||||
val exclExclExpr = element.parent as JetUnaryExpression
|
||||
exclExclExpr.replace(exclExclExpr.baseExpression!!)
|
||||
}
|
||||
|
||||
registerDiagnosticBasedProcessing<JetSimpleNameExpression>(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 <reified TElement : JetElement, TIntention: JetSelfTargetingRangeIntention<TElement>> registerTypicalIntentionBasedProcessing(
|
||||
private inline fun <reified TElement : JetElement, TIntention: JetSelfTargetingRangeIntention<TElement>> 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<TElement>().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 <reified TElement : JetElement> 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<TElement>().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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<JetElement>()
|
||||
}
|
||||
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<J2kPostProcessing>()
|
||||
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<JetElement, SmartList<ActionData>> {
|
||||
val processings = J2KPostProcessingRegistrar.processings.filter(processingFilter)
|
||||
private fun collectAvailableActions(file: JetFile, rangeMarker: RangeMarker?): LinkedHashMap<JetElement, SmartList<ActionData>> {
|
||||
val diagnostics = analyzeFileRange(file, rangeMarker)
|
||||
|
||||
val elementToActions = LinkedHashMap<JetElement, SmartList<ActionData>>()
|
||||
|
||||
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<JetElement>()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ public object Test {
|
||||
return false
|
||||
}
|
||||
val result = true
|
||||
if (parent.isDirectory()) {
|
||||
if (parent.isDirectory) {
|
||||
return true
|
||||
} else
|
||||
return false
|
||||
|
||||
Reference in New Issue
Block a user