J2k post-processings based on diagnostics are included into the common scheme

This commit is contained in:
Valentin Kipyatkov
2015-08-11 23:01:14 +03:00
parent e66d0f71ac
commit 21639c4785
5 changed files with 102 additions and 139 deletions
@@ -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
}