Skip to content

Commit

Permalink
Fixes #25 Add support for external rules
Browse files Browse the repository at this point in the history
If the command line has -jar specified, then use a specially created classloader to load the class and the reference.conf
The sbt & maven plugins should just work
  • Loading branch information
matthewfarwell committed Jan 28, 2015
1 parent fbb5de0 commit 9a576c3
Show file tree
Hide file tree
Showing 7 changed files with 48 additions and 31 deletions.
38 changes: 22 additions & 16 deletions src/main/scala/org/scalastyle/Checker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import java.nio.charset.MalformedInputException
import scala.io.Codec
import scala.collection.JavaConversions.seqAsJavaList
import scala.collection.JavaConversions.collectionAsScalaIterable
import scala.annotation.tailrec

case class Line(text: String, start: Int, end: Int)

Expand Down Expand Up @@ -57,10 +58,9 @@ case class Lines(lines: Array[Line], lastChar: Char) {

}

class ScalastyleChecker[T <: FileSpec] {
class ScalastyleChecker[T <: FileSpec](classLoader: Option[ClassLoader] = None) {
def checkFiles(configuration: ScalastyleConfiguration, files: Seq[T]): List[Message[T]] = {
val checks = configuration.checks.filter(_.enabled)
StartWork() :: files.flatMap(file => StartFile(file) :: Checker.verifyFile(configuration, checks, file) ::: List(EndFile(file))).toList ::: List(EndWork())
privateCheckFiles(configuration, files).toList
}

def checkFilesAsJava(configuration: ScalastyleConfiguration, files: java.util.List[T]): java.util.List[Message[T]] = {
Expand All @@ -69,16 +69,22 @@ class ScalastyleChecker[T <: FileSpec] {

private[this] def privateCheckFiles(configuration: ScalastyleConfiguration, files: Iterable[T]): Seq[Message[T]] = {
val checks = configuration.checks.filter(_.enabled)
StartWork() :: files.flatMap(file => StartFile(file) :: Checker.verifyFile(configuration, checks, file) ::: List(EndFile(file))).toList ::: List(EndWork())
val checkerUtils = new CheckerUtils(classLoader)
StartWork() :: files.flatMap(file => StartFile(file) :: checkerUtils.verifyFile(configuration, checks, file) :::
List(EndFile(file))).toList ::: List(EndWork())
}

}

case class ScalariformAst(ast: CompilationUnit, comments: List[Comment])

object Checker {
type CheckerClass = Class[_ <: Checker[_]]
def parseLines(source: String): Lines = Lines(source.split("\n").scanLeft(Line("", 0, 0)) {
case (pl, t) => Line(t, pl.end, pl.end + t.length + 1)
}.tail, source.charAt(source.length()-1))
}

class CheckerUtils(classLoader: Option[ClassLoader] = None) {
private def comments(tokens: List[Token]): List[Comment] = tokens.map(t => {
if (t.associatedWhitespaceAndComments == null) Nil else t.associatedWhitespaceAndComments.comments // scalastyle:ignore null
}).flatten
Expand All @@ -89,10 +95,6 @@ object Checker {
Some(ScalariformAst(new ScalaParser(tokens.toArray).compilationUnitOrScript(), comments(tokens)))
}

def parseLines(source: String): Lines = Lines(source.split("\n").scanLeft(Line("", 0, 0)) {
case (pl, t) => Line(t, pl.end, pl.end + t.length + 1)
}.tail, source.charAt(source.length()-1))

def verifySource[T <: FileSpec](configuration: ScalastyleConfiguration, classes: List[ConfigurationChecker], file: T, source: String): List[Message[T]] = {
if (source.isEmpty()) {
Nil
Expand All @@ -103,7 +105,7 @@ object Checker {

private def verifySource0[T <: FileSpec](configuration: ScalastyleConfiguration, classes: List[ConfigurationChecker],
file: T, source: String): List[Message[T]] = {
val lines = parseLines(source)
val lines = Checker.parseLines(source)
val scalariformAst = parseScalariform(source)

val commentFilters = scalariformAst match {
Expand Down Expand Up @@ -142,6 +144,7 @@ object Checker {
* If there is no encoding passed, we try the default, then UTF-8, then UTF-16, then ISO-8859-1
*/
def readFile(file: String, encoding: Option[String])(implicit codec: Codec): String = {
@tailrec
def readFileWithEncoding(file: String, encodings: List[String]): Option[String] = {
if (encodings.size == 0) {
None
Expand Down Expand Up @@ -172,9 +175,12 @@ object Checker {
}
}

def newInstance(name: String, level: Level, parameters: Map[String, String], customMessage: Option[String], customId: Option[String]): Option[Checker[_]] = {
private def newInstance(name: String, level: Level, parameters: Map[String, String], customMessage: Option[String],
customId: Option[String]): Option[Checker[_]] = {
try {
val clazz = Class.forName(name).asInstanceOf[Class[Checker[_]]]
val cl: ClassLoader = classLoader.getOrElse(this.getClass().getClassLoader())

val clazz = Class.forName(name, true, cl)
val c: Checker[_] = clazz.getConstructor().newInstance().asInstanceOf[Checker[_]]
c.setParameters(parameters)
c.setLevel(level)
Expand All @@ -197,10 +203,10 @@ trait Checker[A] {
var customMessage: Option[String] = None
var customErrorKey: Option[String] = None

protected def setParameters(parameters: Map[String, String]) = this.parameters = parameters;
protected def setLevel(level: Level) = this.level = level;
protected def setCustomErrorKey(customErrorKey: Option[String]) = this.customErrorKey = customErrorKey
protected def setCustomMessage(customMessage: Option[String]) = this.customMessage = customMessage
def setParameters(parameters: Map[String, String]): Unit = this.parameters = parameters;
def setLevel(level: Level): Unit = this.level = level;
def setCustomErrorKey(customErrorKey: Option[String]): Unit = this.customErrorKey = customErrorKey
def setCustomMessage(customMessage: Option[String]): Unit = this.customMessage = customMessage
protected def getInt(parameter: String, defaultValue: Int): Int = Integer.parseInt(parameters.getOrElse(parameter, "" + defaultValue))
protected def getString(parameter: String, defaultValue: String): String = parameters.getOrElse(parameter, defaultValue)
protected def getBoolean(parameter: String, defaultValue: Boolean): Boolean = parameters.getOrElse(parameter, "" + defaultValue) == "true"
Expand Down
12 changes: 9 additions & 3 deletions src/main/scala/org/scalastyle/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import java.io.File
import java.util.Date
import scala.io.Codec
import com.typesafe.config.ConfigFactory
import java.net.URLClassLoader
import java.net.URL

case class MainConfig(error: Boolean,
config: Option[String] = None,
Expand All @@ -29,7 +31,8 @@ case class MainConfig(error: Boolean,
warningsaserrors: Boolean = false,
xmlFile: Option[String] = None,
xmlEncoding: Option[String] = None,
inputEncoding: Option[String] = None)
inputEncoding: Option[String] = None,
externalJar: Option[String] = None)

object Main {
// scalastyle:off regex
Expand All @@ -43,6 +46,7 @@ object Main {
println(" --xmlEncoding STRING encoding to use for the xml file")
println(" --inputEncoding STRING encoding for the source files")
println(" -w, --warnings true|false fail if there are warnings")
println(" -e, --externalJar FILE jar containing custom rules")
System.exit(1)
}
// scalastyle:on regex
Expand All @@ -62,6 +66,7 @@ object Main {
case ("--xmlOutput") => config = config.copy(xmlFile = Some(args(i + 1)))
case ("--xmlEncoding") => config = config.copy(xmlEncoding = Some(args(i + 1)))
case ("--inputEncoding") => config = config.copy(inputEncoding = Some(args(i + 1)))
case ("-e" | "--externalJar") => config = config.copy(externalJar = Some(args(i + 1)))
case _ => config = config.copy(error = true)
}
i = i + 2
Expand Down Expand Up @@ -99,10 +104,11 @@ object Main {
private[this] def execute(mc: MainConfig)(implicit codec: Codec): Boolean = {
val start = now()
val configuration = ScalastyleConfiguration.readFromXml(mc.config.get)
val messages = new ScalastyleChecker().checkFiles(configuration, Directory.getFiles(mc.inputEncoding, mc.directories.map(new File(_)).toSeq))
val cl = mc.externalJar.flatMap(j => Some(new URLClassLoader(Array(new java.io.File(j).toURI().toURL()))))
val messages = new ScalastyleChecker(cl).checkFiles(configuration, Directory.getFiles(mc.inputEncoding, mc.directories.map(new File(_)).toSeq))

// scalastyle:off regex
val config = ConfigFactory.load()
val config = ConfigFactory.load(cl.getOrElse(this.getClass().getClassLoader()))
val outputResult = new TextOutput(config).output(messages)
mc.xmlFile match {
case Some(x) => {
Expand Down
4 changes: 3 additions & 1 deletion src/main/scala/org/scalastyle/PrintAst.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,10 @@ class Foobar {
}

def printAst(source: String): Unit = {
val cu = new CheckerUtils()

val lines = Checker.parseLines(source)
val scalariformAst = Checker.parseScalariform(source)
val scalariformAst = cu.parseScalariform(source)
scalariformAst match {
case None => println("Parse error")
case Some(ast) => printAst(lines, ast.ast)
Expand Down
4 changes: 2 additions & 2 deletions src/test/scala/org/scalastyle/CommentFilterTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class CommentFilterTest extends AssertionsForJUnit {
// scalastyle:off
// another comment
// scalastyle:on"""
val comments = Checker.parseScalariform(text).get.comments
val comments = new CheckerUtils().parseScalariform(text).get.comments

val tokens = CommentFilter.findScalastyleComments(comments)

Expand Down Expand Up @@ -143,7 +143,7 @@ class foobar {
}

private[this] def assertCommentFilter(expected: List[CommentFilter], text: String) = {
val hiddenTokenInfo = Checker.parseScalariform(text).get.comments
val hiddenTokenInfo = new CheckerUtils().parseScalariform(text).get.comments
val lines = Checker.parseLines(text)
assertEquals(expected.mkString("\n"), CommentFilter.findCommentFilters(hiddenTokenInfo, lines).mkString("\n"))
}
Expand Down
16 changes: 9 additions & 7 deletions src/test/scala/org/scalastyle/FileEncodingTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@ class FileEncodingTest extends AssertionsForJUnit {
// so this test MUST BE RUN with a -Dfile.encoding=UTF-8.
// so we can't test that the strings are returned correctly
@Test def testFileEncodings(): Unit = {
assertEquals(TestString, Checker.readFile(createFile("UTF16"), None))
assertEquals(TestString, Checker.readFile(createFile("UTF8"), None))
Checker.readFile(createFile("ISO-8859-1"), None) // can't tell difference between UTF-8 & ISO-8859-1
Checker.readFile(createFile("windows-1252"), None) // can't tell difference between UTF-8 & windows-1252
assertEquals(TestString, Checker.readFile(createFile("UTF-16BE"), None))
Checker.readFile(createFile("UTF-16LE"), None) // can't tell difference between LE & BE
Checker.readFile(createFile("GBK"), None) // gets read by ISO-8859-1
val cu = new CheckerUtils()

assertEquals(TestString, cu.readFile(createFile("UTF16"), None))
assertEquals(TestString, cu.readFile(createFile("UTF8"), None))
cu.readFile(createFile("ISO-8859-1"), None) // can't tell difference between UTF-8 & ISO-8859-1
cu.readFile(createFile("windows-1252"), None) // can't tell difference between UTF-8 & windows-1252
assertEquals(TestString, cu.readFile(createFile("UTF-16BE"), None))
cu.readFile(createFile("UTF-16LE"), None) // can't tell difference between LE & BE
cu.readFile(createFile("GBK"), None) // gets read by ISO-8859-1
}

private def createFile(encoding: String) = {
Expand Down
2 changes: 1 addition & 1 deletion src/test/scala/org/scalastyle/SourceFileParserTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class SourceFileParserTest extends AssertionsForJUnit {
val checks = config.checks.filter(_.enabled)
val sourcePath = new File("src/test/resources/testfiles/EmptyClass.scala")
val sourceFile = new DirectoryFileSpec(sourcePath.getAbsolutePath(), encoding = None, sourcePath.getAbsoluteFile())
val msgs = Checker.verifyFile(config, checks, sourceFile)
val msgs = new CheckerUtils().verifyFile(config, checks, sourceFile)
assertEquals(Nil, msgs)
}
}
3 changes: 2 additions & 1 deletion src/test/scala/org/scalastyle/file/CheckerTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import org.scalastyle.WarningLevel
import org.scalastyle.ConfigurationChecker
import org.scalastyle.FileSpec
import org.scalastyle.ScalastyleConfiguration
import org.scalastyle.CheckerUtils

// scalastyle:off multiple.string.literals

Expand All @@ -41,7 +42,7 @@ trait CheckerTest {
customMessage: Option[String] = None, commentFilter: Boolean = true, customId: Option[String] = None) = {
val classes = List(ConfigurationChecker(classUnderTest.getName(), WarningLevel, true, params, customMessage, customId))
val configuration = ScalastyleConfiguration("", commentFilter, classes)
assertEquals(expected.mkString("\n"), Checker.verifySource(configuration, classes, NullFileSpec, source).mkString("\n"))
assertEquals(expected.mkString("\n"), new CheckerUtils().verifySource(configuration, classes, NullFileSpec, source).mkString("\n"))
}

protected def fileError(args: List[String] = List(), customMessage: Option[String] = None) =
Expand Down

0 comments on commit 9a576c3

Please sign in to comment.