Skip to content

Commit d95becd

Browse files
committed
Add support for local coverage on and off
1 parent 01e92e0 commit d95becd

File tree

3 files changed

+570
-5
lines changed

3 files changed

+570
-5
lines changed

compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import java.io.File
55

66
import ast.tpd.*
77
import collection.mutable
8+
import core.Comments.Comment
89
import core.Flags.*
910
import core.Contexts.{Context, ctx, inContext}
1011
import core.DenotTransformers.IdentityDenotTransformer
@@ -40,6 +41,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
4041

4142
private var coverageExcludeClasslikePatterns: List[Pattern] = Nil
4243
private var coverageExcludeFilePatterns: List[Pattern] = Nil
44+
private val coverageLocalExclusions: mutable.ListBuffer[Span] = mutable.ListBuffer.empty
4345

4446
override def run(using ctx: Context): Unit =
4547
val outputPath = ctx.settings.coverageOutputDir.value
@@ -62,6 +64,22 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
6264
coverageExcludeClasslikePatterns = ctx.settings.coverageExcludeClasslikes.value.map(_.r.pattern)
6365
coverageExcludeFilePatterns = ctx.settings.coverageExcludeFiles.value.map(_.r.pattern)
6466

67+
var currentStartingComment: Option[Comment] = None
68+
ctx.compilationUnit.comments.foreach {
69+
case comment if InstrumentCoverage.scoverageLocalOff.matches(comment.raw) && currentStartingComment.isEmpty =>
70+
currentStartingComment = Some(comment)
71+
case comment if InstrumentCoverage.scoverageLocalOn.matches(comment.raw) =>
72+
currentStartingComment.foreach { start =>
73+
currentStartingComment = None
74+
coverageLocalExclusions += start.span.withEnd(comment.span.end)
75+
}
76+
case _ =>
77+
}
78+
79+
currentStartingComment.headOption.foreach { start =>
80+
coverageLocalExclusions += start.span.withEnd(ctx.source.length - 1)
81+
}
82+
6583
ctx.base.coverage.nn.removeStatementsFromFile(ctx.compilationUnit.source.file.absolute.jpath)
6684
super.run
6785

@@ -79,6 +97,10 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
7997
_.matcher(normalizedPath).matches
8098
)
8199

100+
private def isTreeExcluded(tree: Tree)(using Context): Boolean =
101+
coverageLocalExclusions.exists: excludedSpans =>
102+
excludedSpans.contains(tree.span)
103+
82104
override protected def newTransformer(using Context) =
83105
CoverageTransformer(ctx.settings.coverageOutputDir.value)
84106

@@ -179,7 +201,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
179201
* @return instrumentation result, with the preparation statement, coverage call and tree separated
180202
*/
181203
private def tryInstrument(tree: Apply)(using Context): InstrumentedParts =
182-
if canInstrumentApply(tree) then
204+
if !isTreeExcluded(tree) && canInstrumentApply(tree) then
183205
// Create a call to Invoker.invoked(coverageDirectory, newStatementId)
184206
val coverageCall = createInvokeCall(tree, tree.sourcePos)
185207

@@ -206,22 +228,26 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
206228

207229
private def tryInstrument(tree: Ident)(using Context): InstrumentedParts =
208230
val sym = tree.symbol
209-
if canInstrumentParameterless(sym) then
231+
if !isTreeExcluded(tree) && canInstrumentParameterless(sym) then
210232
// call to a local parameterless method f
211233
val coverageCall = createInvokeCall(tree, tree.sourcePos)
212234
InstrumentedParts.singleExpr(coverageCall, tree)
213235
else
214236
InstrumentedParts.notCovered(tree)
215237

216238
private def tryInstrument(tree: Literal)(using Context): InstrumentedParts =
217-
val coverageCall = createInvokeCall(tree, tree.sourcePos)
218-
InstrumentedParts.singleExpr(coverageCall, tree)
239+
if !isTreeExcluded(tree) then
240+
val coverageCall = createInvokeCall(tree, tree.sourcePos)
241+
InstrumentedParts.singleExpr(coverageCall, tree)
242+
else
243+
InstrumentedParts.notCovered(tree)
244+
219245

220246
private def tryInstrument(tree: Select)(using Context): InstrumentedParts =
221247
val sym = tree.symbol
222248
val qual = transform(tree.qualifier).ensureConforms(tree.qualifier.tpe)
223249
val transformed = cpy.Select(tree)(qual, tree.name)
224-
if canInstrumentParameterless(sym) then
250+
if !isTreeExcluded(tree) && canInstrumentParameterless(sym) then
225251
// call to a parameterless method
226252
val coverageCall = createInvokeCall(tree, tree.sourcePos)
227253
InstrumentedParts.singleExpr(coverageCall, transformed)
@@ -606,6 +632,8 @@ object InstrumentCoverage:
606632
val name: String = "instrumentCoverage"
607633
val description: String = "instrument code for coverage checking"
608634
val ExcludeMethodFlags: FlagSet = Artifact | Erased
635+
val scoverageLocalOn: Regex = """^\s*//\s*\$COVERAGE-ON\$""".r
636+
val scoverageLocalOff: Regex = """^\s*//\s*\$COVERAGE-OFF\$""".r
609637

610638
/**
611639
* An instrumented Tree, in 3 parts.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package covtest
2+
3+
class C:
4+
def a: C = this
5+
def b: Unit = return
6+
def c: Unit = ()
7+
def d: Int = 12
8+
def e: Null = null
9+
10+
// $COVERAGE-OFF$
11+
def block: Int =
12+
"literal"
13+
0
14+
15+
def cond: Boolean =
16+
if false then true
17+
else false
18+
19+
def partialCond: Unit =
20+
if false then ()
21+
22+
// $COVERAGE-ON$
23+
24+
def new1: C = new {}
25+
26+
def tryCatch: Unit =
27+
try ()
28+
catch
29+
case e: Exception => 1

0 commit comments

Comments
 (0)