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
}
@@ -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
View File
@@ -11,7 +11,7 @@ public object Test {
return false
}
val result = true
if (parent.isDirectory()) {
if (parent.isDirectory) {
return true
} else
return false