Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fix command for extracting using directives into project.scala file #2309

Merged
merged 5 commits into from
Sep 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions modules/build/src/main/scala/scala/build/bsp/BspClient.scala
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,8 @@ class BspClient(
}
}
.groupBy(_.positions.headOption match
case Some(File(Right(path), _, _)) => Some(path)
case _ => None
case Some(File(Right(path), _, _, _)) => Some(path)
case _ => None
)
.filter(_._1.isDefined)
.values
Expand All @@ -222,7 +222,7 @@ class BspClient(
reset: Boolean = false
)(diag: Diagnostic): Seq[os.Path] =
diag.positions.flatMap {
case File(Right(path), (startLine, startC), (endL, endC)) =>
case File(Right(path), (startLine, startC), (endL, endC), _) =>
val id = new b.TextDocumentIdentifier(path.toNIO.toUri.toASCIIString)
val startPos = new b.Position(startLine, startC)
val endPos = new b.Position(endL, endC)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ case class DirectivesPreprocessor(

val handlersMap = handlers
.flatMap { handler =>
handler.keys.map(k => k -> handleValues(handler))
handler.keys.flatMap(_.nameAliases).map(k => k -> handleValues(handler))
}
.toMap

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ object ExtractedDirectives {

val directivesOpt = allDirectives.headOption
val directivesPositionOpt = directivesOpt match {
case Some(directives) if directives.containsTargetDirectivesOnly =>
None
case Some(directives)
if directives.containsTargetDirectives ||
directives.isEmpty => None
case Some(directives) => Some(directives.getPosition(path))
case None => None
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,9 @@ case object ScalaPreprocessor extends Preprocessor {
val summedOptions = allOptions.foldLeft(BuildOptions())(_ orElse _)
val lastContentOpt = preprocessedDirectives.strippedContent
.orElse(if (isSheBang) Some(content0) else None)
val directivesPositions = preprocessedDirectives.directivesPositions
val directivesPositions = preprocessedDirectives.directivesPositions.map { pos =>
if (isSheBang) pos.copy(endPos = pos.endPos._1 + 1 -> pos.endPos._2) else pos
}

val scopedRequirements = preprocessedDirectives.scopedReqs
Some(ProcessingOutput(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,11 @@ case object ScriptPreprocessor extends Preprocessor {
suppressWarningOptions: SuppressWarningOptions
)(using ScalaCliInvokeData): Either[BuildException, List[PreprocessedSource.UnwrappedScript]] =
either {

val (contentIgnoredSheBangLines, _) = SheBang.ignoreSheBangLines(content)

val (pkg, wrapper) = AmmUtil.pathToPackageWrapper(subPath)

val processingOutput: ProcessingOutput =
value(ScalaPreprocessor.process(
contentIgnoredSheBangLines,
content,
reportingPath,
scopePath / os.up,
logger,
Expand All @@ -102,7 +99,8 @@ case object ScriptPreprocessor extends Preprocessor {
))
.getOrElse(ProcessingOutput.empty)

val scriptCode = processingOutput.updatedContent.getOrElse(contentIgnoredSheBangLines)
val scriptCode =
processingOutput.updatedContent.getOrElse(SheBang.ignoreSheBangLines(content)._1)
// try to match in multiline mode, don't match comment lines starting with '//'
val containsMainAnnot = "(?m)^(?!//).*@main.*".r.findFirstIn(scriptCode).isDefined

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@ object SheBang {

def isShebangScript(content: String): Boolean = sheBangRegex.unanchored.matches(content)

/** Returns the shebang section and the content without the shebang section */
def partitionOnShebangSection(content: String): (String, String) =
if (content.startsWith("#!")) {
val regexMatch = sheBangRegex.findFirstMatchIn(content)
regexMatch match {
case Some(firstMatch) =>
(firstMatch.toString(), content.replaceFirst(firstMatch.toString(), ""))
case None => ("", content)
}
}
else
("", content)

def ignoreSheBangLines(content: String): (String, Boolean) =
if (content.startsWith("#!")) {
val regexMatch = sheBangRegex.findFirstMatchIn(content)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,45 @@
package scala.build.preprocessing

import com.virtuslab.using_directives.custom.model.UsingDirectives
import com.virtuslab.using_directives.custom.utils.ast.UsingDefs
import com.virtuslab.using_directives.custom.utils.ast.*

import scala.annotation.tailrec
import scala.build.Position
import scala.jdk.CollectionConverters.*

object UsingDirectivesOps {
extension (ud: UsingDirectives) {
def keySet: Set[String] = ud.getFlattenedMap.keySet().asScala.map(_.toString).toSet
def containsTargetDirectivesOnly: Boolean = ud.keySet.forall(_.toString.startsWith("target."))
def containsTargetDirectives: Boolean = ud.keySet.exists(_.startsWith("target."))

def getPosition(path: Either[String, os.Path]): Position.File =
val line = ud.getAst().getPosition().getLine()
val column = ud.getAst().getPosition().getColumn()
Position.File(path, (0, 0), (line, column))
extension (pos: Positioned) {
def getLine = pos.getPosition.getLine
def getColumn = pos.getPosition.getColumn
}

@tailrec
def getEndPostion(ast: UsingTree): (Int, Int) = ast match {
case uds: UsingDefs => uds.getUsingDefs.asScala match {
case _ :+ lastUsingDef => getEndPostion(lastUsingDef)
case _ => (uds.getLine, uds.getColumn)
}
case ud: UsingDef => getEndPostion(ud.getValue)
case uvs: UsingValues => uvs.getValues.asScala match {
case _ :+ lastUsingValue => getEndPostion(lastUsingValue)
case _ => (uvs.getLine, uvs.getColumn)
}
case sl: StringLiteral => (
sl.getLine,
sl.getColumn + sl.getValue.length + { if sl.getIsWrappedDoubleQuotes then 2 else 0 }
)
case bl: BooleanLiteral => (bl.getLine, bl.getColumn + bl.getValue.toString.length)
case el: EmptyLiteral => (el.getLine, el.getColumn)
}

val (line, column) = getEndPostion(ud.getAst)

Position.File(path, (0, 0), (line, column), ud.getCodeOffset)

def getDirectives =
ud.getAst match {
Expand All @@ -24,6 +49,7 @@ object UsingDirectivesOps {
Nil
}

def nonEmpty: Boolean = !ud.getFlattenedMap.isEmpty
def nonEmpty: Boolean = !isEmpty
def isEmpty: Boolean = ud.getFlattenedMap.isEmpty
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ class DirectiveTests extends munit.FunSuite {
assert(position.nonEmpty)

val (startPos, endPos) = position.get match {
case Position.File(_, startPos, endPos) => (startPos, endPos)
case _ => sys.error("cannot happen")
case Position.File(_, startPos, endPos, _) => (startPos, endPos)
case _ => sys.error("cannot happen")
}

expect(startPos == (0, 15))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,49 @@ package scala.build.tests

import com.eed3si9n.expecty.Expecty.expect

import scala.build.input.{ScalaCliInvokeData, SourceScalaFile}
import scala.build.input.{ScalaCliInvokeData, Script, SourceScalaFile}
import scala.build.options.SuppressWarningOptions
import scala.build.preprocessing.{PreprocessedSource, ScalaPreprocessor}
import scala.build.preprocessing.{PreprocessedSource, ScalaPreprocessor, ScriptPreprocessor}

class ScalaPreprocessorTests extends munit.FunSuite {

test("should respect using directives in a .scala file with the shebang line") {
val lastUsingLine =
"//> using dep \"com.lihaoyi::os-lib::0.8.1\" \"com.lihaoyi::os-lib::0.8.1\""
TestInputs(os.rel / "Main.scala" ->
"""#!/usr/bin/env -S scala-cli shebang
|//> using dep "com.lihaoyi::os-lib::0.8.1"
|
|object Main {
| def main(args: Array[String]): Unit = {
| println(os.pwd)
| }
|}""".stripMargin).fromRoot { root =>
s"""#!/usr/bin/env -S scala-cli shebang
|//> using jvm 11
|$lastUsingLine
|
|object Main {
| def main(args: Array[String]): Unit = {
| println(os.pwd)
| }
|}""".stripMargin).fromRoot { root =>
val scalaFile = SourceScalaFile(root, os.sub / "Main.scala")
val Some(Right(result)) = ScalaPreprocessor.preprocess(
scalaFile,
logger = TestLogger(),
allowRestrictedFeatures = false,
allowRestrictedFeatures = true,
suppressWarningOptions = SuppressWarningOptions()
)(using ScalaCliInvokeData.dummy)
expect(result.nonEmpty)
val Some(directivesPositions) = result.head.directivesPositions
expect(directivesPositions.startPos == 0 -> 0)
expect(directivesPositions.endPos == 3 -> 0)
expect(directivesPositions.endPos == 3 -> lastUsingLine.length)
}
}

test("should respect using directives in a .sc file with the shebang line") {
val depLine = "//> using dep com.lihaoyi::os-lib::0.8.1"

TestInputs(os.rel / "sample.sc" ->
"""#!/usr/bin/env -S scala-cli shebang
|//> using dep "com.lihaoyi::os-lib::0.8.1"
|println(os.pwd)
|""".stripMargin).fromRoot { root =>
val scalaFile = SourceScalaFile(root, os.sub / "sample.sc")
val Some(Right(result)) = ScalaPreprocessor.preprocess(
s"""#!/usr/bin/env -S scala-cli shebang
|$depLine
|println(os.pwd)
|""".stripMargin).fromRoot { root =>
val scalaFile = Script(root, os.sub / "sample.sc", None)
val Some(Right(result)) = ScriptPreprocessor.preprocess(
scalaFile,
logger = TestLogger(),
allowRestrictedFeatures = false,
Expand All @@ -48,7 +53,40 @@ class ScalaPreprocessorTests extends munit.FunSuite {
expect(result.nonEmpty)
val Some(directivesPositions) = result.head.directivesPositions
expect(directivesPositions.startPos == 0 -> 0)
expect(directivesPositions.endPos == 2 -> 0)
expect(directivesPositions.endPos == 2 -> depLine.length)
}
}

val lastUsingLines = Seq(
"//> using dep \"com.lihaoyi::os-lib::0.8.1\" \"com.lihaoyi::os-lib::0.8.1\"" -> "string literal",
"//> using scala 2.13.7" -> "numerical string",
"//> using objectWrapper true" -> "boolean literal",
"//> using objectWrapper" -> "empty value literal"
)

for ((lastUsingLine, typeName) <- lastUsingLines) do
test(s"correct directive positions with $typeName") {
TestInputs(os.rel / "Main.scala" ->
s"""#!/usr/bin/env -S scala-cli shebang
|//> using jvm 11
|$lastUsingLine
|
|object Main {
| def main(args: Array[String]): Unit = {
| println(os.pwd)
| }
|}""".stripMargin).fromRoot { root =>
val scalaFile = SourceScalaFile(root, os.sub / "Main.scala")
val Some(Right(result)) = ScalaPreprocessor.preprocess(
scalaFile,
logger = TestLogger(),
allowRestrictedFeatures = true,
suppressWarningOptions = SuppressWarningOptions()
)(using ScalaCliInvokeData.dummy)
expect(result.nonEmpty)
val Some(directivesPositions) = result.head.directivesPositions
expect(directivesPositions.startPos == 0 -> 0)
expect(directivesPositions.endPos == 3 -> lastUsingLine.length)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class ScalaCliCommands(
directories.Directories,
doc.Doc,
export0.Export,
fix.Fix,
fmt.Fmt,
new HelpCmd(help),
installcompletions.InstallCompletions,
Expand Down
Loading