@@ -5,6 +5,7 @@ import java.io.File
55
66import ast .tpd .*
77import collection .mutable
8+ import core .Comments .Comment
89import core .Flags .*
910import core .Contexts .{Context , ctx , inContext }
1011import 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.
0 commit comments