@@ -10,26 +10,30 @@ import java.util.concurrent.atomic.AtomicInteger
10
10
/** @author Stephen Samuel */
11
11
class ScoveragePlugin (val global : Global ) extends Plugin {
12
12
13
- val name : String = " scoverage"
14
- val description : String = " scoverage code coverage compiler plugin"
15
- val opts = new ScoverageOptions
16
- val components : List [PluginComponent ] = List (new ScoverageComponent (global, opts))
17
-
18
- override def processOptions (options : List [String ], error : String => Unit ) {
19
- for ( opt <- options ) {
13
+ override val name : String = " scoverage"
14
+ override val description : String = " scoverage code coverage compiler plugin"
15
+ val component = new ScoverageComponent (global)
16
+ override val components : List [PluginComponent ] = List (component)
17
+
18
+ override def processOptions (opts : List [String ], error : String => Unit ) {
19
+ val options = new ScoverageOptions
20
+ for ( opt <- opts ) {
20
21
if (opt.startsWith(" excludedPackages:" )) {
21
- opts .excludedPackages = opt.substring(" excludedPackages:" .length).split(" ;" ).map(_.trim).filterNot(_.isEmpty)
22
+ options .excludedPackages = opt.substring(" excludedPackages:" .length).split(" ;" ).map(_.trim).filterNot(_.isEmpty)
22
23
} else if (opt.startsWith(" dataDir:" )) {
23
- opts .dataDir = opt.substring(" dataDir:" .length)
24
+ options .dataDir = opt.substring(" dataDir:" .length)
24
25
} else {
25
26
error(" Unknown option: " + opt)
26
27
}
27
28
}
29
+ component.setOptions(options)
28
30
}
29
31
30
32
override val optionsHelp : Option [String ] = Some (Seq (
31
33
" -P:scoverage:dataDir:<pathtodatadir> where the coverage files should be written\n " ,
32
- " -P:scoverage:excludedPackages:<regex>;<regex> semicolon separated list of regexs for packages to exclude\n "
34
+ " -P:scoverage:excludedPackages:<regex>;<regex> semicolon separated list of regexs for packages to exclude" ,
35
+ " Any classes whose fully qualified name matches the regex will" ,
36
+ " be excluded from coverage."
33
37
).mkString(" \n " ))
34
38
}
35
39
@@ -38,16 +42,38 @@ class ScoverageOptions {
38
42
var dataDir : String = _
39
43
}
40
44
41
- class ScoverageComponent (val global : Global , options : ScoverageOptions )
42
- extends PluginComponent with TypingTransformers with Transform with TreeDSL {
45
+ class ScoverageComponent (
46
+ val global : Global )
47
+ extends PluginComponent
48
+ with TypingTransformers
49
+ with Transform
50
+ with TreeDSL {
43
51
44
52
import global ._
45
53
46
54
val statementIds = new AtomicInteger (0 )
47
55
val coverage = new Coverage
48
- val phaseName : String = " scoverage"
49
- val runsAfter : List [String ] = List (" typer" )
56
+ override val phaseName : String = " scoverage"
57
+ override val runsAfter : List [String ] = List (" typer" )
50
58
override val runsBefore = List [String ](" patmat" )
59
+ /**
60
+ * Our options are not provided at construction time, but shortly after,
61
+ * so they start as None.
62
+ * You must call "setOptions" before running any commands that rely on
63
+ * the options.
64
+ */
65
+ private var _options : Option [ScoverageOptions ] = None
66
+ private var coverageFilter : Option [CoverageFilter ] = None
67
+
68
+ private def options : ScoverageOptions = {
69
+ require(_options.nonEmpty, " You must first call \" setOptions\" " )
70
+ _options.get
71
+ }
72
+
73
+ def setOptions (options : ScoverageOptions ): Unit = {
74
+ _options = Some (options)
75
+ coverageFilter = Some (new CoverageFilter (options.excludedPackages))
76
+ }
51
77
52
78
override def newPhase (prev : scala.tools.nsc.Phase ): Phase = new Phase (prev) {
53
79
@@ -70,8 +96,14 @@ class ScoverageComponent(val global: Global, options: ScoverageOptions)
70
96
71
97
var location : Location = null
72
98
73
- def safeStart (tree : Tree ): Int = if (tree.pos.isDefined) tree.pos.start else - 1
74
- def safeEnd (tree : Tree ): Int = if (tree.pos.isDefined) tree.pos.end else - 1
99
+ /**
100
+ * The 'start' of the position, if it is available, else -1
101
+ * We cannot use 'isDefined' to test whether pos.start will work, as some
102
+ * classes (e.g. [[scala.reflect.internal.util.OffsetPosition ]] have
103
+ * isDefined true, but throw on `start`
104
+ */
105
+ def safeStart (tree : Tree ): Int = scala.util.Try (tree.pos.start).getOrElse(- 1 )
106
+ def safeEnd (tree : Tree ): Int = scala.util.Try (tree.pos.end).getOrElse(- 1 )
75
107
def safeLine (tree : Tree ): Int = if (tree.pos.isDefined) tree.pos.line else - 1
76
108
def safeSource (tree : Tree ): Option [SourceFile ] = if (tree.pos.isDefined) Some (tree.pos.source) else None
77
109
@@ -117,25 +149,28 @@ class ScoverageComponent(val global: Global, options: ScoverageOptions)
117
149
println(s " [warn] Could not instrument [ ${tree.getClass.getSimpleName}/ ${tree.symbol}]. No position. " )
118
150
tree
119
151
case Some (source) =>
120
-
121
- val id = statementIds.incrementAndGet
122
- val statement = MeasuredStatement (
123
- source.path,
124
- location,
125
- id,
126
- safeStart(tree),
127
- safeEnd(tree),
128
- safeLine(tree),
129
- tree.toString(),
130
- Option (tree.symbol).map(_.fullNameString).getOrElse(" <nosymbol>" ),
131
- tree.getClass.getSimpleName,
132
- branch
133
- )
134
- coverage.add(statement)
135
-
136
- val apply = invokeCall(id)
137
- val block = Block (List (apply), tree)
138
- localTyper.typed(atPos(tree.pos)(block))
152
+ if (tree.pos.isDefined && ! isStatementIncluded(tree.pos)) {
153
+ tree
154
+ } else {
155
+ val id = statementIds.incrementAndGet
156
+ val statement = MeasuredStatement (
157
+ source.path,
158
+ location,
159
+ id,
160
+ safeStart(tree),
161
+ safeEnd(tree),
162
+ safeLine(tree),
163
+ tree.toString(),
164
+ Option (tree.symbol).map(_.fullNameString).getOrElse(" <nosymbol>" ),
165
+ tree.getClass.getSimpleName,
166
+ branch
167
+ )
168
+ coverage.add(statement)
169
+
170
+ val apply = invokeCall(id)
171
+ val block = Block (List (apply), tree)
172
+ localTyper.typed(atPos(tree.pos)(block))
173
+ }
139
174
}
140
175
}
141
176
@@ -153,8 +188,12 @@ class ScoverageComponent(val global: Global, options: ScoverageOptions)
153
188
dir.getPath
154
189
}
155
190
156
- def isIncluded (t : Tree ): Boolean = {
157
- new CoverageFilter (options.excludedPackages).isIncluded(t.symbol.fullNameString)
191
+ def isClassIncluded (symbol : Symbol ): Boolean = {
192
+ coverageFilter.get.isClassIncluded(symbol.fullNameString)
193
+ }
194
+
195
+ def isStatementIncluded (pos : Position ): Boolean = {
196
+ coverageFilter.get.isLineIncluded(pos)
158
197
}
159
198
160
199
def className (s : Symbol ): String = {
@@ -275,21 +314,21 @@ class ScoverageComponent(val global: Global, options: ScoverageOptions)
275
314
// special support to handle partial functions
276
315
case c : ClassDef if c.symbol.isAnonymousFunction &&
277
316
c.symbol.enclClass.superClass.nameString.contains(" AbstractPartialFunction" ) =>
278
- if (isIncluded(c ))
317
+ if (isClassIncluded(c.symbol ))
279
318
transformPartial(c)
280
319
else
281
320
c
282
321
283
322
// scalac generated classes, we just instrument the enclosed methods/statments
284
323
// the location would stay as the source class
285
324
case c : ClassDef if c.symbol.isAnonymousClass || c.symbol.isAnonymousFunction =>
286
- if (isIncluded(c ))
325
+ if (isClassIncluded(c.symbol ))
287
326
super .transform(tree)
288
327
else
289
328
c
290
329
291
330
case c : ClassDef =>
292
- if (isIncluded(c )) {
331
+ if (isClassIncluded(c.symbol )) {
293
332
updateLocation(c.symbol)
294
333
super .transform(tree)
295
334
}
@@ -386,7 +425,7 @@ class ScoverageComponent(val global: Global, options: ScoverageOptions)
386
425
387
426
// user defined objects
388
427
case m : ModuleDef =>
389
- if (isIncluded(m )) {
428
+ if (isClassIncluded(m.symbol )) {
390
429
updateLocation(m.symbol)
391
430
super .transform(tree)
392
431
}
@@ -422,7 +461,7 @@ class ScoverageComponent(val global: Global, options: ScoverageOptions)
422
461
case n : New => super .transform(n)
423
462
424
463
case p : PackageDef =>
425
- if (isIncluded(p )) treeCopy.PackageDef (p, p.pid, transformStatements(p.stats))
464
+ if (isClassIncluded(p.symbol )) treeCopy.PackageDef (p, p.pid, transformStatements(p.stats))
426
465
else p
427
466
428
467
// This AST node corresponds to the following Scala code: `return` expr
0 commit comments