Skip to content

Commit d29bd9d

Browse files
authored
Refactor ScoverageOptions out to its own file. (#393)
This renames the plugin.scala to ScoveragePlugin.scala and also moves the parsing of the options out into it's own file. I also clean up some of the vars that weren't really necessary
1 parent fe2ea8b commit d29bd9d

File tree

4 files changed

+157
-98
lines changed

4 files changed

+157
-98
lines changed
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package scoverage
2+
3+
/** Base options that can be passed into scoverage
4+
*
5+
* @param excludedPackages packages to be excluded in coverage
6+
* @param excludedFiles files to be excluded in coverage
7+
* @param excludedSymbols symbols to be excluded in coverage
8+
* @param dataDir the directory that the coverage files should be written to
9+
* @param reportTestName whether or not the test names should be reported
10+
* @param sourceRoot the source root of your project
11+
*/
12+
case class ScoverageOptions(
13+
excludedPackages: Seq[String],
14+
excludedFiles: Seq[String],
15+
excludedSymbols: Seq[String],
16+
dataDir: String,
17+
reportTestName: Boolean,
18+
sourceRoot: String
19+
)
20+
21+
object ScoverageOptions {
22+
23+
private[scoverage] val help = Some(
24+
Seq(
25+
"-P:scoverage:dataDir:<pathtodatadir> where the coverage files should be written\n",
26+
"-P:scoverage:sourceRoot:<pathtosourceRoot> the root dir of your sources, used for path relativization\n",
27+
"-P:scoverage:excludedPackages:<regex>;<regex> semicolon separated list of regexs for packages to exclude",
28+
"-P:scoverage:excludedFiles:<regex>;<regex> semicolon separated list of regexs for paths to exclude",
29+
"-P:scoverage:excludedSymbols:<regex>;<regex> semicolon separated list of regexs for symbols to exclude",
30+
"-P:scoverage:extraAfterPhase:<phaseName> phase after which scoverage phase runs (must be after typer phase)",
31+
"-P:scoverage:extraBeforePhase:<phaseName> phase before which scoverage phase runs (must be before patmat phase)",
32+
" Any classes whose fully qualified name matches the regex will",
33+
" be excluded from coverage."
34+
).mkString("\n")
35+
)
36+
37+
private def parseExclusionOption(
38+
inOption: String
39+
): Seq[String] =
40+
inOption
41+
.split(";")
42+
.collect {
43+
case value if value.trim().nonEmpty => value.trim()
44+
}
45+
.toIndexedSeq
46+
47+
private val ExcludedPackages = "excludedPackages:(.*)".r
48+
private val ExcludedFiles = "excludedFiles:(.*)".r
49+
private val ExcludedSymbols = "excludedSymbols:(.*)".r
50+
private val DataDir = "dataDir:(.*)".r
51+
private val SourceRoot = "sourceRoot:(.*)".r
52+
private val ExtraAfterPhase = "extraAfterPhase:(.*)".r
53+
private val ExtraBeforePhase = "extraBeforePhase:(.*)".r
54+
55+
/** Default that is _only_ used for initializing purposes. dataDir and
56+
* sourceRoot are both just empty strings here, but we nevery actually
57+
* allow for this to be the case when the plugin runs, and this is checked
58+
* before it does.
59+
*/
60+
def default() = ScoverageOptions(
61+
excludedPackages = Seq.empty,
62+
excludedFiles = Seq.empty,
63+
excludedSymbols = Seq(
64+
"scala.reflect.api.Exprs.Expr",
65+
"scala.reflect.api.Trees.Tree",
66+
"scala.reflect.macros.Universe.Tree"
67+
),
68+
dataDir = "",
69+
reportTestName = false,
70+
sourceRoot = ""
71+
)
72+
73+
def processPhaseOptions(
74+
opts: List[String]
75+
): (Option[String], Option[String]) = {
76+
77+
val afterPhase: Option[String] =
78+
opts.collectFirst { case ExtraAfterPhase(phase) => phase }
79+
val beforePhase: Option[String] =
80+
opts.collectFirst { case ExtraBeforePhase(phase) => phase }
81+
82+
(afterPhase, beforePhase)
83+
}
84+
85+
def parse(
86+
scalacOptions: List[String],
87+
errFn: String => Unit,
88+
base: ScoverageOptions
89+
): ScoverageOptions = {
90+
91+
var options = base
92+
93+
scalacOptions.foreach {
94+
case ExcludedPackages(packages) =>
95+
options = options.copy(excludedFiles = parseExclusionOption(packages))
96+
case ExcludedFiles(files) =>
97+
options = options.copy(excludedFiles = parseExclusionOption(files))
98+
case ExcludedSymbols(symbols) =>
99+
options = options.copy(excludedSymbols = parseExclusionOption(symbols))
100+
case DataDir(dir) =>
101+
options = options.copy(dataDir = dir)
102+
case SourceRoot(root) =>
103+
options.copy(sourceRoot = root)
104+
// NOTE that both the extra phases are actually parsed out early on, so
105+
// we just ignore them here
106+
case ExtraAfterPhase(afterPhase) => ()
107+
case ExtraBeforePhase(beforePhase) => ()
108+
case "reportTestName" =>
109+
options.copy(reportTestName = true)
110+
case opt => errFn("Unknown option: " + opt)
111+
}
112+
113+
options
114+
}
115+
116+
}

scalac-scoverage-plugin/src/main/scala/scoverage/plugin.scala renamed to scalac-scoverage-plugin/src/main/scala/scoverage/ScoveragePlugin.scala

Lines changed: 34 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -14,126 +14,59 @@ import scala.tools.nsc.transform.TypingTransformers
1414
/** @author Stephen Samuel */
1515
class ScoveragePlugin(val global: Global) extends Plugin {
1616

17-
override val name: String = "scoverage"
18-
override val description: String = "scoverage code coverage compiler plugin"
19-
private val (extraAfterPhase, extraBeforePhase) = processPhaseOptions(
20-
pluginOptions
21-
)
17+
override val name: String = ScoveragePlugin.name
18+
override val description: String = ScoveragePlugin.description
19+
20+
// TODO I'm not 100% sure why, but historically these have been parsed out
21+
// first. One thing to play around with in the future would be to not do this
22+
// here and rather do it later when we utilize setOpts and instead just
23+
// initialize then in the instrumentationCompoent. This will save us
24+
// iterating over these options twice.
25+
private val (extraAfterPhase, extraBeforePhase) =
26+
ScoverageOptions.processPhaseOptions(
27+
pluginOptions
28+
)
29+
2230
val instrumentationComponent = new ScoverageInstrumentationComponent(
2331
global,
2432
extraAfterPhase,
2533
extraBeforePhase
2634
)
35+
2736
override val components: List[PluginComponent] = List(
2837
instrumentationComponent
2938
)
3039

31-
private def parseExclusionEntry(
32-
entryName: String,
33-
inOption: String
34-
): Seq[String] =
35-
inOption
36-
.substring(entryName.length)
37-
.split(";")
38-
.map(_.trim)
39-
.toIndexedSeq
40-
.filterNot(_.isEmpty)
41-
4240
override def init(opts: List[String], error: String => Unit): Boolean = {
43-
val options = new ScoverageOptions
44-
45-
for (opt <- opts) {
46-
if (opt.startsWith("excludedPackages:")) {
47-
options.excludedPackages = parseExclusionEntry("excludedPackages:", opt)
48-
} else if (opt.startsWith("excludedFiles:")) {
49-
options.excludedFiles = parseExclusionEntry("excludedFiles:", opt)
50-
} else if (opt.startsWith("excludedSymbols:")) {
51-
options.excludedSymbols = parseExclusionEntry("excludedSymbols:", opt)
52-
} else if (opt.startsWith("dataDir:")) {
53-
options.dataDir = opt.substring("dataDir:".length)
54-
} else if (opt.startsWith("sourceRoot:")) {
55-
options.sourceRoot = opt.substring("sourceRoot:".length())
56-
} else if (
57-
opt
58-
.startsWith("extraAfterPhase:") || opt.startsWith("extraBeforePhase:")
59-
) {
60-
// skip here, these flags are processed elsewhere
61-
} else if (opt == "reportTestName") {
62-
options.reportTestName = true
63-
} else {
64-
error("Unknown option: " + opt)
65-
}
66-
}
67-
if (!opts.exists(_.startsWith("dataDir:")))
41+
42+
val options =
43+
ScoverageOptions.parse(opts, error, ScoverageOptions.default())
44+
45+
if (options.dataDir.isEmpty())
6846
throw new RuntimeException(
6947
"Cannot invoke plugin without specifying <dataDir>"
7048
)
71-
if (!opts.exists(_.startsWith("sourceRoot:")))
49+
50+
if (options.sourceRoot.isEmpty())
7251
throw new RuntimeException(
7352
"Cannot invoke plugin without specifying <sourceRoot>"
7453
)
54+
7555
instrumentationComponent.setOptions(options)
7656
true
7757
}
7858

79-
override val optionsHelp: Option[String] = Some(
80-
Seq(
81-
"-P:scoverage:dataDir:<pathtodatadir> where the coverage files should be written\n",
82-
"-P:scoverage:sourceRoot:<pathtosourceRoot> the root dir of your sources, used for path relativization\n",
83-
"-P:scoverage:excludedPackages:<regex>;<regex> semicolon separated list of regexs for packages to exclude",
84-
"-P:scoverage:excludedFiles:<regex>;<regex> semicolon separated list of regexs for paths to exclude",
85-
"-P:scoverage:excludedSymbols:<regex>;<regex> semicolon separated list of regexs for symbols to exclude",
86-
"-P:scoverage:extraAfterPhase:<phaseName> phase after which scoverage phase runs (must be after typer phase)",
87-
"-P:scoverage:extraBeforePhase:<phaseName> phase before which scoverage phase runs (must be before patmat phase)",
88-
" Any classes whose fully qualified name matches the regex will",
89-
" be excluded from coverage."
90-
).mkString("\n")
91-
)
59+
override val optionsHelp: Option[String] = ScoverageOptions.help
9260

93-
// copied from scala 2.11
9461
private def pluginOptions: List[String] = {
9562
// Process plugin options of form plugin:option
9663
def namec = name + ":"
97-
global.settings.pluginOptions.value filter (_ startsWith namec) map (_ stripPrefix namec)
98-
}
99-
100-
private def processPhaseOptions(
101-
opts: List[String]
102-
): (Option[String], Option[String]) = {
103-
var afterPhase: Option[String] = None
104-
var beforePhase: Option[String] = None
105-
for (opt <- opts) {
106-
if (opt.startsWith("extraAfterPhase:")) {
107-
afterPhase = Some(opt.substring("extraAfterPhase:".length))
108-
}
109-
if (opt.startsWith("extraBeforePhase:")) {
110-
beforePhase = Some(opt.substring("extraBeforePhase:".length))
111-
}
112-
}
113-
(afterPhase, beforePhase)
64+
global.settings.pluginOptions.value
65+
.filter(_.startsWith(namec))
66+
.map(_.stripPrefix(namec))
11467
}
11568
}
11669

117-
// TODO refactor this into a case class. We'll also refactor how we parse the
118-
// options to get rid of all these vars
119-
class ScoverageOptions {
120-
var excludedPackages: Seq[String] = Nil
121-
var excludedFiles: Seq[String] = Nil
122-
var excludedSymbols: Seq[String] = Seq(
123-
"scala.reflect.api.Exprs.Expr",
124-
"scala.reflect.api.Trees.Tree",
125-
"scala.reflect.macros.Universe.Tree"
126-
)
127-
var dataDir: String = IOUtils.getTempPath
128-
var reportTestName: Boolean = false
129-
// TODO again, we'll refactor this later so this won't have a default here.
130-
// However for tests we'll have to create this. However, make sure you create
131-
// either both in temp or neither in temp, since on windows your temp dir
132-
// will be in another drive, so the relativize functinality won't work if
133-
// correctly.
134-
var sourceRoot: String = IOUtils.getTempPath
135-
}
136-
13770
class ScoverageInstrumentationComponent(
13871
val global: Global,
13972
extraAfterPhase: Option[String],
@@ -147,7 +80,7 @@ class ScoverageInstrumentationComponent(
14780
val statementIds = new AtomicInteger(0)
14881
val coverage = new Coverage
14982

150-
override val phaseName: String = "scoverage-instrumentation"
83+
override val phaseName: String = ScoveragePlugin.phaseName
15184
override val runsAfter: List[String] =
15285
List("typer") ::: extraAfterPhase.toList
15386
override val runsBefore: List[String] =
@@ -158,7 +91,7 @@ class ScoverageInstrumentationComponent(
15891
* You must call "setOptions" before running any commands that rely on
15992
* the options.
16093
*/
161-
private var options: ScoverageOptions = new ScoverageOptions()
94+
private var options: ScoverageOptions = ScoverageOptions.default()
16295
private var coverageFilter: CoverageFilter = AllCoverageFilter
16396

16497
private val isScalaJsEnabled: Boolean = {
@@ -194,7 +127,6 @@ class ScoverageInstrumentationComponent(
194127
s"Instrumentation completed [${coverage.statements.size} statements]"
195128
)
196129

197-
// TODO do we need to verify this sourceRoot exists? How does semanticdb do this?
198130
Serializer.serialize(
199131
coverage,
200132
Serializer.coverageFile(options.dataDir),
@@ -920,3 +852,9 @@ class ScoverageInstrumentationComponent(
920852
}
921853
}
922854
}
855+
856+
object ScoveragePlugin {
857+
val name: String = "scoverage"
858+
val description: String = "scoverage code coverage compiler plugin"
859+
val phaseName: String = "scoverage-instrumentation"
860+
}

scalac-scoverage-plugin/src/test/scala/scoverage/RegexCoverageFilterTest.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ class RegexCoverageFilterTest extends FunSuite {
8383
)
8484
}
8585

86-
val options = new ScoverageOptions()
86+
val options = ScoverageOptions.default()
8787

8888
test("isSymbolIncluded should return true for empty excludes") {
8989
assert(new RegexCoverageFilter(Nil, Nil, Nil).isSymbolIncluded("x"))

scalac-scoverage-plugin/src/test/scala/scoverage/ScoverageCompiler.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,12 @@ class ScoverageCompiler(
121121
val instrumentationComponent =
122122
new ScoverageInstrumentationComponent(this, None, None)
123123

124-
instrumentationComponent.setOptions(new ScoverageOptions())
124+
val coverageOptions = ScoverageOptions
125+
.default()
126+
.copy(dataDir = IOUtils.getTempPath)
127+
.copy(sourceRoot = IOUtils.getTempPath)
128+
129+
instrumentationComponent.setOptions(coverageOptions)
125130
val testStore = new ScoverageTestStoreComponent(this)
126131
val validator = new PositionValidator(this)
127132

0 commit comments

Comments
 (0)