Skip to content

Commit

Permalink
Highlight by-name parameters at their creation
Browse files Browse the repository at this point in the history
Minor refactoring to avoid code duplication

Fix #1002340
  • Loading branch information
mlangc committed Jan 15, 2015
1 parent 49d369d commit b1a9fb6
Show file tree
Hide file tree
Showing 15 changed files with 504 additions and 39 deletions.
Expand Up @@ -50,6 +50,7 @@ import org.scalaide.util.eclipse.RegionUtilsTest
import org.scalaide.core.pc.DeclarationPrinterTest
import org.scalaide.core.compiler.NamePrinterTest
import org.scalaide.core.ui.ScalaTemplateContextTest
import org.scalaide.ui.internal.editor.decorators.params.byname.CallByNameParamAtCreationPresenterTest

@RunWith(classOf[Suite])
@Suite.SuiteClasses(
Expand Down Expand Up @@ -103,6 +104,7 @@ import org.scalaide.core.ui.ScalaTemplateContextTest
classOf[RegionUtilsTest],
classOf[DeclarationPrinterTest],
classOf[NamePrinterTest],
classOf[ScalaTemplateContextTest]
classOf[ScalaTemplateContextTest],
classOf[CallByNameParamAtCreationPresenterTest]
))
class TestsSuite
Expand Up @@ -33,7 +33,7 @@ class AbstractSymbolClassifierTest {
}

protected def checkSymbolInfoClassification(source: String, locationTemplate: String, regionTagToSymbolInfo: Map[String, SymbolInfo], delimiter: Char = '$') {
val expectedRegionToSymbolNameMap: Map[IRegion, String] = RegionParser.getRegions(locationTemplate, delimiter)
val expectedRegionToSymbolNameMap: Map[IRegion, String] = RegionParser.delimitedRegions(locationTemplate, delimiter)
val expectedRegionsAndSymbols: List[(IRegion, SymbolInfo)] =
expectedRegionToSymbolNameMap.mapValues(regionTagToSymbolInfo).toList sortBy regionOffset
val actualRegionsAndSymbols: List[(IRegion, SymbolInfo)] =
Expand Down
Expand Up @@ -73,4 +73,4 @@ class CallByNameParameterTest extends AbstractSymbolClassifierTest {
}
""", Map("BNP" -> CallByNameParameter, "LV" -> LocalVal, "P" -> Param))
}
}
}
Expand Up @@ -2,9 +2,43 @@ package org.scalaide.core.semantichighlighting.classifier

import org.eclipse.jface.text.IRegion
import org.eclipse.jface.text.Region
import scala.annotation.tailrec

object RegionParser {

/**
* This class represents a substring with an optional prefix and suffix.
*/
case class EmbeddedSubstr(str: String, prefix: String = "", suffix: String = "") {
private[RegionParser] val searchString = prefix + str + suffix
}

object EmbeddedSubstr {
implicit def wrapAsEmbeddedSubstring(str: String) = EmbeddedSubstr(str)
}


/**
* Extracts the regions marked by the given substrings.
*/
def substrRegions(test: String, substrs: EmbeddedSubstr*): Map[IRegion, EmbeddedSubstr] = {
@tailrec
def regions(substr: EmbeddedSubstr, fromIndex: Int = 0, acc: Seq[IRegion] = Seq()): Seq[IRegion] = {
val index = test.indexOf(substr.searchString, fromIndex)
if (index < 0) {
acc
} else {
val newAcc = acc :+ new Region(index + substr.prefix.length, substr.str.length)
regions(substr, index + substr.searchString.length, newAcc)
}

}

substrs.foldLeft(Map[IRegion, EmbeddedSubstr]()) { (acc, substr) =>
acc ++ regions(substr).map(_ -> substr)
}
}

/**
* Search for regions delimited with a sign. In the default case the
* delimited sign is a '$'.
Expand All @@ -26,7 +60,7 @@ object RegionParser {
* are handled as there were no escape sign. This means that the String `$a\$b$`
* is treated as `$a$b$`.
*/
def getRegions(text: String, delimiter: Char = '$'): Map[IRegion, String] = {
def delimitedRegions(text: String, delimiter: Char = '$'): Map[IRegion, String] = {
val sb = new StringBuilder
var curPos = 0
var offset = 0
Expand Down Expand Up @@ -57,4 +91,4 @@ object RegionParser {
require(sb.isEmpty, "odd number of '"+delimiter+"' signs in text")
regions
}
}
}
Expand Up @@ -4,23 +4,53 @@ import org.eclipse.jface.text.IRegion
import org.eclipse.jface.text.Region
import org.junit.Test
import org.junit.Assert._

private case class GetRegionsTestCase(input: String, expected: Map[IRegion, String])
import org.scalaide.core.semantichighlighting.classifier.RegionParser.EmbeddedSubstr
import org.scalaide.core.semantichighlighting.classifier.RegionParser.EmbeddedSubstr.wrapAsEmbeddedSubstring

class RegionParserTest {
@Test
def testDelimitedRegions() {
case class TestCase(input: String, expected: Map[IRegion, String])

private val getRegionsTestCases = Seq(
GetRegionsTestCase("", Map()),
GetRegionsTestCase("""$abc$ def $ghi$""", Map(new Region(0, 5) -> "abc", new Region(10, 5) -> "ghi")),
GetRegionsTestCase("""$a\$bc$ de\$f $ghi$""", Map(new Region(0, 6) -> "a$bc", new Region(12, 5) -> "ghi")),
GetRegionsTestCase("""\""", Map()))

val testCases = Seq(
TestCase("", Map()),
TestCase("""$abc$ def $ghi$""", Map(new Region(0, 5) -> "abc", new Region(10, 5) -> "ghi")),
TestCase("""$a\$bc$ de\$f $ghi$""", Map(new Region(0, 6) -> "a$bc", new Region(12, 5) -> "ghi")),
TestCase("""\""", Map()))

@Test
def getRegions() {
for(testCase <- getRegionsTestCases) {
val actual = RegionParser.getRegions(testCase.input)
for(testCase <- testCases) {
val actual = RegionParser.delimitedRegions(testCase.input)
assertEquals(testCase.expected, actual)
}
}
}

@Test
def testSubstrRegions() {
case class TestCase(expected: Map[IRegion, EmbeddedSubstr], input: String, substrs: EmbeddedSubstr*)
def toRegion(offset: Int, len: Int) = new Region(offset, len)

val xStrY = EmbeddedSubstr("str", "x", "y")
val xxxStr = EmbeddedSubstr("str", "xxx")
val strYyy = EmbeddedSubstr("str", "", "yyy")

val testCases = Seq(
TestCase(Map(), ""),
TestCase(Map(), "asdf", "xyz"),
TestCase(Map(toRegion(0, 1) -> "a"), "a", "a"),
TestCase(Map(toRegion(0, 1) -> "a", toRegion(1, 1) -> "a"), "aa", "a"),
TestCase(Map(
toRegion(1, 3) -> xStrY,
toRegion(11, 3) -> xxxStr,
toRegion(16, 3) -> strYyy,
toRegion(5, 1) -> "5",
toRegion(15, 1) -> "5"),
"xstry56xxxxstr45stryyy",
xStrY, xxxStr, strYyy, "5"))

for (testCase <- testCases) {
val actual = RegionParser.substrRegions(testCase.input, testCase.substrs :_*)
assertEquals(testCase.expected, actual)
}

}
}
@@ -0,0 +1,175 @@
package org.scalaide.ui.internal.editor.decorators.params.byname

import scala.annotation.migration

import org.eclipse.jface.text.Region
import org.junit.Assert.assertEquals
import org.junit.Assert._
import org.junit.Test
import org.scalaide.CompilerSupportTests
import org.scalaide.core.semantichighlighting.classifier.RegionParser
import org.scalaide.core.semantichighlighting.classifier.RegionParser.EmbeddedSubstr
import org.scalaide.core.semantichighlighting.classifier.RegionParser.EmbeddedSubstr.wrapAsEmbeddedSubstring

import CallByNameParamAtCreationPresenterTest.mkScalaCompilationUnit

object CallByNameParamAtCreationPresenterTest extends CompilerSupportTests

class CallByNameParamAtCreationPresenterTest {
import CallByNameParamAtCreationPresenterTest._

import RegionParser.EmbeddedSubstr
import RegionParser.EmbeddedSubstr.wrapAsEmbeddedSubstring
import CallByNameParamAtCreationPresenter.Cfg

@Test
def testWithStringArgument() {
testWithSingleLineCfg("""
object O {
def method(s: => String) = Unit
method("Hanelore Hostasch")
}""",
"\"Hanelore Hostasch\"")
}

@Test
def testWithValArgument() {
testWithSingleLineCfg("""
object O {
val wert = 43
def method(i: => Int) = Unit
method(wert)
}""",
EmbeddedSubstr("wert", "(", ")"))
}

@Test
def testWithMultipleMethods() {
testWithSingleLineCfg("""
object O {
val wert = 43
def method1(i: => Int) = Unit
def method2(i: Int) = Unit
method1(wert)
method2(wert)
}""",
EmbeddedSubstr("wert", "1(", ")"))
}

@Test
def testWithMathExpression() {
testWithSingleLineCfg("""
object O {
def method(i: => Int) = Unit
method(1 + 2 + 3 + 4)
}""",
"1 + 2 + 3 + 4")
}

@Test
def testWithStringExpression() {
testWithSingleLineCfg("""
object O {
def method(s: => String) = Unit
method("" + "asdf".substring(1))
}""",
""""" + "asdf".substring(1)""")
}

@Test
def testWithRecursion() {
testWithSingleLineCfg("""
object O {
def method(s: => String) = s
method(method(method("recursive")))
}""",
"\"recursive\"", """method("recursive")""", """method(method("recursive"))""")
}

@Test
def testWithMultipleArgs() {
testWithSingleLineCfg("""
object O {
def method(s1: => String, s2: String) = s1 + s2
method("hallo", "welt")
}""",
"\"hallo\"")
}

@Test
def testWithMultipleArgLists() {
testWithSingleLineCfg("""
object O {
def method(s1: => String)(s2: String)(i1: Int, i2: => Int, i3: Int) = Unit
method("hallo")("welt")(1, 2, 3)
}""",
"\"hallo\"", EmbeddedSubstr("2", " ", ","))
}

@Test
def testWithCompilationErrorAlreadyDefined() {
testWithSingleLineCfg("""
object O {
def alreadyDefined(s: => String) = Unit
def alreadyDefined(s: => String) = Unit
alreadyDefined("")
}""")
}

@Test
def testWithCompilationErrorInArg() {
testWithSingleLineCfg("""
object O {
def method(s: => String) = Unit
alreadyDefined("" "ups")
}""")
}

@Test
def testWithMuliLineCfgWithMultipleLines() {
testWithMultiLineCfg("""
object O {
def method(s: => String) = Unit
method("a" +
"b + c" +
"d")
}""", """"a" +
"b + c" +
"d"""")
}

@Test
def testWithSingleLineCfgWithMultipleLines() {
testWithSingleLineCfg("""
object O {
def method(s: => String) = Unit
method("a" +
"b + c" +
"d")
}""", """"a" +""")
}

private def testWith(source: String, cfg: Cfg, paramCreations: EmbeddedSubstr*) {
val cu = mkScalaCompilationUnit(source)
cu.withSourceFile { (sourceFile, compiler) =>
val res = CallByNameParamAtCreationPresenter.annotations(compiler, cu, sourceFile, cfg)
val regions = res.values.map(pos => new Region(pos.offset, pos.length)).toSet
val expectedRegions = RegionParser.substrRegions(source, paramCreations: _*).keySet
assertEquals(expectedRegions, regions)

assertEquals(Set(), res.keys.filterNot(_.isInstanceOf[CallByNameParamAtCreationAnnotation]))
val annotationMsgs = res.keySet.map(_.getText)
for (substr <- paramCreations) {
assertTrue(annotationMsgs.toString(), annotationMsgs.exists(_.contains(substr.str)))
}
}
}

private def testWithSingleLineCfg(source: String, paramCreations: EmbeddedSubstr*) {
testWith(source, Cfg(firstLineOnly = true), paramCreations: _*)
}

private def testWithMultiLineCfg(source: String, paramCreations: EmbeddedSubstr*) {
testWith(source, Cfg(firstLineOnly = false), paramCreations: _*)
}
}
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit b1a9fb6

Please sign in to comment.