Skip to content

Commit

Permalink
implemented an extendible quick fix functionality for type mismatch
Browse files Browse the repository at this point in the history
errors
  • Loading branch information
ikuraj committed Aug 21, 2012
1 parent f95b2ce commit cb2e25c
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 15 deletions.
@@ -1,33 +1,45 @@
package scala.tools.eclipse.quickfix

// Eclipse
import org.eclipse.core.runtime.CoreException
import org.eclipse.core.runtime.NullProgressMonitor
import org.eclipse.ui.IEditorPart
import org.eclipse.jface.text.source.Annotation
import org.eclipse.ui.texteditor.ITextEditor
import org.eclipse.jdt.internal.ui.javaeditor.CompilationUnitDocumentProvider.ProblemAnnotation
import org.eclipse.jdt.ui.JavaUI
import org.eclipse.core.runtime.CoreException
import org.eclipse.jdt.core.ICompilationUnit
import org.eclipse.jdt.core.search.IJavaSearchConstants
import org.eclipse.jdt.internal.codeassist.ISearchRequestor
import org.eclipse.jdt.internal.compiler.env.{ AccessRestriction }
import org.eclipse.jdt.internal.core.{ DefaultWorkingCopyOwner, JavaProject }
import org.eclipse.jdt.internal.ui.text.correction.{ SimilarElement, SimilarElementsRequestor }
import org.eclipse.jdt.ui.text.java._
import org.eclipse.core.resources.IMarker
import org.eclipse.jdt.core.search.{ TypeNameMatch, SearchEngine }
import org.eclipse.jdt.core.IJavaElement
import org.eclipse.jdt.internal.corext.util.TypeNameMatchCollector
import org.eclipse.jface.text.source.Annotation
import org.eclipse.jface.text.Position
import org.eclipse.jface.text.IDocument

// Scala IDE
import scala.tools.eclipse.javaelements.ScalaSourceFile
import scala.tools.eclipse.util.FileUtils
import scala.tools.eclipse.util.EditorUtils.getAnnotationsAtOffset
import scala.tools.eclipse.semantichighlighting.implicits.ImplicitHighlightingPresenter
import scala.tools.eclipse.logging.HasLogger

// Scala
import scala.util.matching.Regex
import org.eclipse.jdt.core.search.TypeNameMatch
import org.eclipse.jdt.core.search.SearchEngine
import org.eclipse.jdt.core.IJavaElement
import org.eclipse.jdt.internal.corext.util.TypeNameMatchCollector
import org.eclipse.core.runtime.NullProgressMonitor
import collection.JavaConversions._

class ScalaQuickFixProcessor extends IQuickFixProcessor {
class ScalaQuickFixProcessor extends IQuickFixProcessor with HasLogger {
private val typeNotFoundError = new Regex("not found: type (.*)")
private val valueNotFoundError = new Regex("not found: value (.*)")
private val xxxxxNotFoundError = new Regex("not found: (.*)")

// regex for extracting expected and required type on type mismatch
private val typeMismatchError = new Regex("type mismatch;\\s*found\\s*: (\\S*)\\s*required: (.*)")

/**
* Checks if the processor has any corrections.
*
Expand All @@ -36,7 +48,6 @@ class ScalaQuickFixProcessor extends IQuickFixProcessor {
* slightly more responsive.
*/
def hasCorrections(unit : ICompilationUnit, problemId : Int) : Boolean = true


/**
* Collects corrections or code manipulations for the given context.
Expand All @@ -53,9 +64,15 @@ class ScalaQuickFixProcessor extends IQuickFixProcessor {
val editor = JavaUI.openInEditor(context.getCompilationUnit)
var corrections : List[IJavaCompletionProposal] = Nil
for (location <- locations)
for (ann <- getAnnotationsAtOffset(editor, location.getOffset)) {
val fix = suggestFix(context.getCompilationUnit(), ann.getText)
corrections = corrections ++ fix
for ((ann, pos) <- getAnnotationsAtOffset(editor, location.getOffset)) {
val importFix = suggestImportFix(context.getCompilationUnit(), ann.getText)

// compute all possible type mismatch quick fixes
val document = (editor.asInstanceOf[ITextEditor]).getDocumentProvider().getDocument(editor.getEditorInput())
val typeMismatchFix = suggestTypeMismatchFix(document, ann.getText, pos)

// concatenate lists of found quick fixes
corrections = corrections ++ importFix ++ typeMismatchFix
}
corrections match {
case Nil => null
Expand All @@ -65,7 +82,8 @@ class ScalaQuickFixProcessor extends IQuickFixProcessor {
case _ => null
}

private def getAnnotationsAtOffset(part: IEditorPart, offset: Int): List[Annotation] = {
// XXX is this code duplication? -- check scala.tools.eclipse.util.EditorUtils.getAnnotationsAtOffset
private def getAnnotationsAtOffsetXXX(part: IEditorPart, offset: Int): List[Annotation] = {
import ScalaQuickFixProcessor._

var ret = List[Annotation]()
Expand All @@ -81,7 +99,7 @@ class ScalaQuickFixProcessor extends IQuickFixProcessor {
}

private
def suggestFix(compilationUnit : ICompilationUnit, problemMessage : String) : List[IJavaCompletionProposal] = {
def suggestImportFix(compilationUnit : ICompilationUnit, problemMessage : String) : List[IJavaCompletionProposal] = {
/**
* Import a type could solve several error message :
*
Expand All @@ -106,6 +124,30 @@ class ScalaQuickFixProcessor extends IQuickFixProcessor {
case _ => Nil
}
}
private
def suggestTypeMismatchFix(document : IDocument, problemMessage : String, location: Position) : List[IJavaCompletionProposal] = {
// get the annotation string
val annotationString = document.get(location.getOffset, location.getLength)
// match problem message
return problemMessage match {
// extract found and required type
case typeMismatchError(foundType, requiredType) =>
// utilize type mismatch computer to find quick fixes
val replacementStringList = TypeMismatchQuickFixProcessor(foundType, requiredType, annotationString)

// map replacements strings into expanding proposals
replacementStringList map {
replacementString =>
// make markers message in form: "... =>replacement"
val markersMessage = annotationString + " " + ImplicitHighlightingPresenter.DisplayStringSeparator + replacementString
// construct a proposal with the appropriate location
new ExpandingProposalBase(markersMessage, "Transform your code expression: ", location)
}
// no match found for the problem message
case _ => Nil
}
}

}

object ScalaQuickFixProcessor {
Expand Down
@@ -0,0 +1,114 @@
package scala.tools.eclipse.quickfix

// Java imports
import java.util.regex.Pattern

/**
* @author ivcha
* This object is used for applying code transformations based on the found and required type
* extract from the annotation message (such as quick fix message) and the expression in the source code.
* The object arguments are: found type string, required type string and annotation string, respective and
* the result is a list of strings which should replace the annotation string
*/
object TypeMismatchQuickFixProcessor extends
((String, String, String) => List[String]) {

/** list containing all type mismatch quick fix cases that this object should go through */
val cases: List[TypeMismatchQuickFixCase] =
List(
// "type mismatch: List[T] but found List[List[T]]
FoundToRequiredTypeCase(
List("%s.flatten", "%s.head", "%s.last"),
Pattern.compile("List\\[List\\[(.*)\\]\\]"), Pattern.compile("List\\[(.*)\\]"), Pattern.compile("^(.*)$")
),
// "type mismatch: Array[T] but found List[T]
FoundToRequiredTypeCase(
List("%s.toArray"),
Pattern.compile("List\\[(.*)\\]"), Pattern.compile("Array\\[(.*)\\]"), Pattern.compile("^(.*)$")
),
// "type mismatch: found T; required Option[T]" -> suggest to wrap the result in Some()
FoundToRequiredTypeCase(
List("Some(%s)"),
Pattern.compile("(.*)"), Pattern.compile("Option\\[(.*)\\]"), Pattern.compile("^(.*)$")
)
)

/**
* apply method for getting list of replacement strings
* @param foundType extracted found type string
* @param requiredType extracted required type string
* @param annotationString extracted expression string from the source code
* @return list of strings which should replace the annotation string
*/
def apply(foundType: String, requiredType: String, annotationString: String): List[String] =
// go through all cases and collect lists of replacement strings
(List[String]() /: cases) {
case (list, ftrtc:FoundToRequiredTypeCase) => {
list ++ ftrtc.apply(foundType, requiredType, annotationString)
}
}

}

/** trait marking all type mismatch quick fix cases */
trait TypeMismatchQuickFixCase

/**
* class which is to be inherited if quick fix simply injects a sequence of strings into a format strings of
* form "... %s... %s..."
*/
abstract class SimpleFormatQuickFixCase(formatStrings: List[String]) extends TypeMismatchQuickFixCase {
def apply(listOfInjectStrings: Seq[String]*) =
for (
// iterate through all sequences of strings to inject
injectString <- listOfInjectStrings;
// iterate through all given format
formatString <- formatStrings
// yield a string when inject strings are applied to the format string
) yield { formatString.format( injectString:_* ) }
}

/**
* class which checks whether found type string and required type string match - it does by
* capturing all groups according to the found and required patterns and compares them for match -
* if all match, the replacement is proceeded by extracting inject strings from the annotation pattern
* and applying them to SimpleFormatQuickFixCase
* found and required patterns should extract the same number of groups
* annotation string should extract required number of groups to feed the given format string
*/
case class FoundToRequiredTypeCase(formatStrings: List[String],
found: Pattern, required: Pattern, annotationExtract: Pattern) extends SimpleFormatQuickFixCase(formatStrings) {
def apply(foundType: String, requiredType: String, annotationString: String): Seq[String] = {
// get matchers
val foundMatcher = found.matcher(foundType)
val requiredMatcher = required.matcher(requiredType)

// if both matched
// NOTE (we expect only a single match)
if (foundMatcher.find && requiredMatcher.find) {
// check if all groups match
if (
// fold all groups and compare them - capturing group count must be the same for both patterns
(true /: (1 to foundMatcher.groupCount()) ) {
case (false, _) => false
case (result, ind) => foundMatcher.group(ind) == requiredMatcher.group(ind)
}
) {
// get annotation matcher
val annotationMatcher = annotationExtract.matcher(annotationString)
// check if find can pass (only single match is expected)
if (annotationMatcher.find) {
// get injection strings
val injectStrings =
for (ind <- 1 to annotationMatcher.groupCount()) yield { annotationMatcher.group(ind) }
// apply them to the format string
super.apply(injectStrings)
// in case annotation matcher cannot find
} else Nil
}
// in case groups don't match
else Nil
// in case matchers fail
} else Nil
}
}

0 comments on commit cb2e25c

Please sign in to comment.