diff --git a/buildfile/src/main/scala/sbt/internal/EvaluateConfigurations.scala b/buildfile/src/main/scala/sbt/internal/EvaluateConfigurations.scala index a8e03feb2..84da1235e 100644 --- a/buildfile/src/main/scala/sbt/internal/EvaluateConfigurations.scala +++ b/buildfile/src/main/scala/sbt/internal/EvaluateConfigurations.scala @@ -22,6 +22,7 @@ import scala.jdk.CollectionConverters.* import xsbti.PathBasedFile import xsbti.VirtualFile import xsbti.VirtualFileRef +import dotty.tools.dotc.ast.untpd.{ Annotated, ValOrDefDef, Tree } /** * This file is responsible for compiling the .sbt files used to configure sbt builds. @@ -97,12 +98,13 @@ private[sbt] object EvaluateConfigurations { builtinImports: Seq[String], offset: Int ): ParsedFile = { + def loseTree(l: (String, Tree, LineRange)): (String, LineRange) = (l._1, l._3) val (importStatements, settingsAndDefinitions) = splitExpressions(file, lines) val allImports = builtinImports.map(s => (s, -1)) ++ addOffset(offset, importStatements) val (definitions, settings) = splitSettingsDefinitions( addOffsetToRange(offset, settingsAndDefinitions) ) - new ParsedFile(allImports, definitions, settings) + new ParsedFile(allImports, definitions.map(loseTree), settings.map(loseTree)) } /** @@ -194,11 +196,14 @@ private[sbt] object EvaluateConfigurations { private def resolveBase(f: File, p: Project) = p.copy(base = IO.resolve(f, p.base)) - def addOffset(offset: Int, lines: Seq[(String, Int)]): Seq[(String, Int)] = + private def addOffset(offset: Int, lines: Seq[(String, Int)]): Seq[(String, Int)] = lines.map { (s, i) => (s, i + offset) } - def addOffsetToRange(offset: Int, ranges: Seq[(String, LineRange)]): Seq[(String, LineRange)] = - ranges.map { (s, r) => (s, r.shift(offset)) } + private def addOffsetToRange( + offset: Int, + ranges: Seq[(String, Tree, LineRange)] + ): Seq[(String, Tree, LineRange)] = + ranges.map { (s, t, r) => (s, t, r.shift(offset)) } /** * The name of the class we cast DSL "setting" (vs. definition) lines to. @@ -286,20 +291,28 @@ private[sbt] object EvaluateConfigurations { private[sbt] def splitExpressions( file: VirtualFileRef, lines: Seq[String] - ): (Seq[(String, Int)], Seq[(String, LineRange)]) = + ): (Seq[(String, Int)], Seq[(String, Tree, LineRange)]) = val split = SbtParser(file, lines) // TODO - Look at pulling the parsed expression trees from the SbtParser and stitch them back into a different // scala compiler rather than re-parsing. - (split.imports, split.settings) + ( + split.imports, + split.settings.zip(split.settingsTrees).map { case ((s, r), (_, t)) => + (s, t, r) + } + ) private def splitSettingsDefinitions( - lines: Seq[(String, LineRange)] - ): (Seq[(String, LineRange)], Seq[(String, LineRange)]) = - lines partition { case (line, _) => isDefinition(line) } + lines: Seq[(String, Tree, LineRange)] + ): (Seq[(String, Tree, LineRange)], Seq[(String, Tree, LineRange)]) = + lines partition { case (_, tree, _) => isDefinition(tree) } - private def isDefinition(line: String): Boolean = { - val trimmed = line.trim - DefinitionKeywords.exists(trimmed.startsWith(_)) + private def isDefinition(tree: Tree): Boolean = { + tree match { + case Annotated(arg, annot) => isDefinition(arg) + case _: ValOrDefDef => true + case _ => false + } } private def extractedValTypes: Seq[String] = diff --git a/buildfile/src/test/scala/sbt/internal/parser/SplitExpressions.scala b/buildfile/src/test/scala/sbt/internal/parser/SplitExpressions.scala index 2399de076..a26268d7a 100644 --- a/buildfile/src/test/scala/sbt/internal/parser/SplitExpressions.scala +++ b/buildfile/src/test/scala/sbt/internal/parser/SplitExpressions.scala @@ -12,8 +12,9 @@ package parser import sbt.internal.util.LineRange import xsbti.VirtualFileRef +import dotty.tools.dotc.ast.untpd.Tree object SplitExpressions: type SplitExpression = - (VirtualFileRef, Seq[String]) => (Seq[(String, Int)], Seq[(String, LineRange)]) + (VirtualFileRef, Seq[String]) => (Seq[(String, Int)], Seq[(String, Tree, LineRange)]) end SplitExpressions diff --git a/buildfile/src/test/scala/sbt/internal/parser/SplitExpressionsBehavior.scala b/buildfile/src/test/scala/sbt/internal/parser/SplitExpressionsBehavior.scala index 3ba3e664d..d3260f998 100644 --- a/buildfile/src/test/scala/sbt/internal/parser/SplitExpressionsBehavior.scala +++ b/buildfile/src/test/scala/sbt/internal/parser/SplitExpressionsBehavior.scala @@ -12,10 +12,11 @@ package parser import sbt.internal.util.LineRange import xsbti.VirtualFileRef +import dotty.tools.dotc.ast.untpd.Tree trait SplitExpression { extension (splitter: SplitExpressions.SplitExpression) - def apply(s: String): (Seq[(String, Int)], Seq[(String, LineRange)]) = + def apply(s: String): (Seq[(String, Int)], Seq[(String, Tree, LineRange)]) = splitter(VirtualFileRef.of("noFile"), s.split('\n').toSeq) } @@ -53,6 +54,16 @@ trait SplitExpressionsBehavior extends SplitExpression { this: verify.BasicTestS assert(imports.size == 2) assert(settingsAndDefs.size == 1) } + + test("parse a config containing an annotated definition") { + val (imports, settingsAndDefs) = splitter( + """|import foo.Bar + |@foo + |lazy val root = (project in file(".")).enablePlugins(PlayScala)""".stripMargin + ) + assert(imports.size == 1) + assert(settingsAndDefs.size == 1) + } } } diff --git a/sbt-app/src/sbt-test/project-load/build-sbt-annotation/build.sbt b/sbt-app/src/sbt-test/project-load/build-sbt-annotation/build.sbt new file mode 100644 index 000000000..afd9f73ea --- /dev/null +++ b/sbt-app/src/sbt-test/project-load/build-sbt-annotation/build.sbt @@ -0,0 +1,23 @@ +import scala.annotation.{tailrec, nowarn} +import sbt.util.cacheLevel + +@tailrec +def even(x: Int): Boolean = Math.abs(x) match + case 0 => true + case 1 => false + case n => even(n - 2) + +@transient val foo = 4 + +@cacheLevel(include = Array.empty) +lazy val myTask = taskKey[Boolean]("...") + +@nowarn +lazy val myProject = project.settings( + myTask := { + assert(!file("ran").exists) + println("running") + IO.touch(file("ran")) + even(2) + } +) diff --git a/sbt-app/src/sbt-test/project-load/build-sbt-annotation/test b/sbt-app/src/sbt-test/project-load/build-sbt-annotation/test new file mode 100644 index 000000000..f9084ebbb --- /dev/null +++ b/sbt-app/src/sbt-test/project-load/build-sbt-annotation/test @@ -0,0 +1,6 @@ +$ absent ran +> myTask +$ exists ran + +# verify the task is not cached (i.e. annotation is not lost) +-> myTask