Saturday, November 24, 2007

Creating Netbeans Ruby Hints with Scala, Part 2

In this part I'm going to show a quick fix for the Netbeans Ruby hint presented in part 1.

The quick fix class

The definition of the quick fix for the hint presented in part 1 is the following:


class IfToStatementModifierFix(
info : CompilationInfo,
condition : Node,
statement : Node,
ifNode : Node,
kind : StatementModifierFix) extends Fix {

def isSafe = true
def isInteractive = false

...

}


As shown in this fragment the class that represents the fix requires a reference to each tree element involved in the problem. In this case the important parts are the complete if statement, the condition and the then statement.

Also a kind argument(specific for this quick fix) is used to determine if a if or a unless statement modifier needs to be created. The definition for StatementModifierFix, looks like this:

abstract class StatementModifierFix
case class IfStatementModifierFix extends StatementModifierFix
case class UnlessStatementModifierFix extends StatementModifierFix


The quick fix implementation

The IfToStatementModifierFix class needs to implement the implement method in order to do its work, here's the code:


def implement() = {
val doc = info.getDocument.asInstanceOf[BaseDocument]
val conditionRange = AstUtilities.getRange(condition)
val conditionText = getConditionText(doc,conditionRange)
val statementRange = AstUtilities.getRange(statement)
val statementText = doc.getText(statementRange.getStart,statementRange.getLength)
val ifRange = AstUtilities.getRange(ifNode)

val resultingStatement = statementText + getModifierText + conditionText
doc.atomicLock
doc.replace(ifRange.getStart,ifRange.getLength,resultingStatement,null)
doc.atomicUnlock
}


In order to generate the unless statement we need to change the operator from != to ==.
The getConditionText method does this job:


def getConditionText(doc : BaseDocument,range: OffsetRange) = {
val text = doc.getText(range.getStart,range.getLength)
kind match {
case IfStatementModifierFix() => text
case UnlessStatementModifierFix() => text.replace("!=","==")
}
}


The last thing we need to do is to connect the hint with this quick fix. In order to do this we need to modify the run method of the IfToStatementModifierHint class presented in part 1. We need to add a fix for each identified case.

The case when a method is used as a condition:


...
case ifStat@IfStatement(methodCall : CallNode,
NewlineNode(thenStat),
null) => {

val range = AstUtilities.getRange(node)

val fixes = new java.util.ArrayList
fixes.add(
new IfToStatementModifierFix(info,methodCall,thenStat,ifStat,IfStatementModifierFix))

val desc =
new Description(this,
recomendationText,
info.getFileObject,range,
fixes,
600)
result.add(desc)
}
...


The case when the condition is a not-equal comparison with nil:


case ifStat@IfStatement(condition@NotNode(eqExp@EqualComparison(x,_ : NilNode)),
NewlineNode(thenStat),
null) => {


val range = AstUtilities.getRange(node)

val fixes = new java.util.ArrayList
fixes.add(
new IfToStatementModifierFix(info,condition,thenStat,ifStat,IfStatementModifierFix))
fixes.add(
new IfToStatementModifierFix(info,eqExp,thenStat,ifStat,UnlessStatementModifierFix))

val desc =
new Description(this,
recomendationText,
info.getFileObject,range,
fixes,
600)
result.add(desc)
}


Note that for this case we also offer the quick fix for unless.

Example 1

Activating the hint on this code:



Now displays the following quick fix:



And changes the code as:



Example 2

Also for code like this:



The following options are presented:



By selecting option 2 the code is changed as:



Code for this experiment can be found here.