Skip to content

Commit

Permalink
Format the copied directives in project.scala
Browse files Browse the repository at this point in the history
  • Loading branch information
MaciejG604 committed Aug 1, 2023
1 parent a6b2d6e commit 66f6078
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 80 deletions.
127 changes: 94 additions & 33 deletions modules/cli/src/main/scala/scala/cli/commands/fix/Fix.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import scala.build.preprocessing.{ExtractedDirectives, SheBang}
import scala.build.{CrossSources, Logger, Position, Sources}
import scala.cli.commands.shared.SharedOptions
import scala.cli.commands.{ScalaCommand, SpecificationLevel}
import scala.collection.immutable.HashMap
import scala.util.chaining.scalaUtilChainingOps

object Fix extends ScalaCommand[FixOptions] {
Expand All @@ -28,30 +29,9 @@ object Fix extends ScalaCommand[FixOptions] {
val newLine = System.lineSeparator()

override def runCommand(options: FixOptions, args: RemainingArgs, logger: Logger): Unit = {
val buildOptions = BuildOptions()
val inputs = options.shared.inputs(args.remaining, () => Inputs.default()).orExit(logger)
val inputs = options.shared.inputs(args.remaining, () => Inputs.default()).orExit(logger)

val (crossSources, _) = CrossSources.forInputs(
inputs,
preprocessors = Sources.defaultPreprocessors(
buildOptions.archiveCache,
buildOptions.internal.javaClassNameVersionOpt,
() => buildOptions.javaHome().value.javaCommand
),
logger = logger,
suppressWarningOptions = SuppressWarningOptions(
Some(true),
Some(true),
Some(true)
),
exclude = buildOptions.internal.exclude
).orExit(logger)

val sharedOptions = crossSources.sharedOptions(buildOptions)
val scopedSources = crossSources.scopedSources(sharedOptions).orExit(logger)

val mainSources = scopedSources.sources(Scope.Main, sharedOptions)
val testSources = scopedSources.sources(Scope.Test, sharedOptions)
val (mainSources, testSources) = getProjectSources(inputs)

// Only initial inputs are used, new inputs discovered during processing of
// CrossSources.forInput may be shared between projects
Expand Down Expand Up @@ -92,11 +72,7 @@ object Fix extends ScalaCommand[FixOptions] {
.append("// Main")
.append(newLine)

allDirectives
.flatMap(_.explodeToStrings)
.distinct
.sorted
.foreach(projectFileContents.append(_).append(newLine))
createFormattedLinesAndAppend(allDirectives, projectFileContents, isTest = false)
}

// Deal with directives from the Test scope
Expand Down Expand Up @@ -124,16 +100,41 @@ object Fix extends ScalaCommand[FixOptions] {
directive <- directivesWithTestPrefix
} yield directive

allDirectives
.flatMap(_.explodeToStrings)
.distinct
.sorted
.foreach(projectFileContents.append(_).append(newLine))
createFormattedLinesAndAppend(allDirectives, projectFileContents, isTest = true)
}
projectFileContents.append(newLine)

os.write.over(inputs.workspace / Constants.projectFileName, projectFileContents.toString)
}

def getProjectSources(inputs: Inputs): (Sources, Sources) = {
val buildOptions = BuildOptions()

val (crossSources, _) = CrossSources.forInputs(
inputs,
preprocessors = Sources.defaultPreprocessors(
buildOptions.archiveCache,
buildOptions.internal.javaClassNameVersionOpt,
() => buildOptions.javaHome().value.javaCommand
),
logger = logger,
suppressWarningOptions = SuppressWarningOptions(
Some(true),
Some(true),
Some(true)
),
exclude = buildOptions.internal.exclude
).orExit(logger)

val sharedOptions = crossSources.sharedOptions(buildOptions)
val scopedSources = crossSources.scopedSources(sharedOptions).orExit(logger)

val mainSources = scopedSources.sources(Scope.Main, sharedOptions)
val testSources = scopedSources.sources(Scope.Test, sharedOptions)

(mainSources, testSources)
}

def getExtractedDirectives(sources: Sources)(
using loggingUtilities: LoggingUtilities
): Seq[ExtractedDirectives] = {
Expand Down Expand Up @@ -244,6 +245,36 @@ object Fix extends ScalaCommand[FixOptions] {
}
}

def createFormattedLinesAndAppend(
strictDirectives: Seq[StrictDirective],
projectFileContents: StringBuilder,
isTest: Boolean
): Unit = {
strictDirectives
// group by key to merge values
.groupBy(_.key)
.map { (key, directives) =>
StrictDirective(key, directives.flatMap(_.values))
}
// group by key prefixes to create splits between groups
.groupBy(dir => (if (isTest) dir.key.stripPrefix("test.") else dir.key).takeWhile(_ != '.'))
.map { (_, directives) =>
directives.flatMap(_.explodeToStringsWithColLimit()).toSeq.sorted
}
.toSeq
.filter(_.nonEmpty)
.sortBy(_.head)(using directivesOrdering)
// append groups to the StringBuilder, add new lines between groups that are bigger than one line
.foldLeft(0) { (lastSize, directiveLines) =>
val newSize = directiveLines.size
if (lastSize > 1 || (lastSize != 0 && newSize > 1)) projectFileContents.append(newLine)

directiveLines.foreach(projectFileContents.append(_).append(newLine))

newSize
}
}

case class TransformedTestDirectives(
withTestPrefix: Seq[StrictDirective],
noTestPrefixAvailable: Seq[StrictDirective],
Expand All @@ -256,4 +287,34 @@ object Fix extends ScalaCommand[FixOptions] {
) {
def relativePath(path: os.Path) = path.relativeTo(workspacePath)
}

private val directivesOrdering: Ordering[String] = {
val directivesOrder: Map[String, Int] = HashMap(
"scala" -> 0,
"platforms" -> 1,
"jvm" -> 2,
"native" -> 3,
"js" -> 4,
"options" -> 5,
"java" -> 6,
"mainClass" -> 7,
"objectWrapper" -> 8,
"toolkit" -> 9,
"dependency" -> 10,
"publish" -> 20
)

Ordering.by { directiveLine =>
val key = directiveLine
.stripPrefix("//> using")
.stripLeading()
.stripPrefix("test.")
.takeWhile(!_.isWhitespace)
val orderFromMap = directivesOrder.get(key).orElse {
directivesOrder.find((keyPrefix, _) => key.startsWith(keyPrefix)).map(_._2)
}

orderFromMap.getOrElse(15)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,34 @@ case class StrictDirective(
s"//> using $key$suffix"
}

/** Explodes this directive into a sequence of directives, each with a single value. */
def explodeToStrings: Seq[String] =
values.map(v => StrictDirective(key, Seq(v)).toString)
/** Checks whether the directive with the sequence of values will fit into the given column limit,
* if it does then the function returns the single directive with all the values. If the
* directive does not fit then the function explodes it into a sequence of directives with
* distinct values, each with a single value.
*/
def explodeToStringsWithColLimit(colLimit: Int = 100): Seq[String] = {
val validValues = values.filter {
case _: EmptyValue => false
case _ => true
}

val usingKeyString = s"//> using $key"

if (validValues.isEmpty)
Seq(usingKeyString)
else {
val distinctValuesStrings = validValues
.map(v => s"\"${v.toString}\"")
.distinct
.sorted

if (distinctValuesStrings.map(_.length).sum + usingKeyString.length < colLimit)
Seq(s"$usingKeyString ${distinctValuesStrings.mkString(" ")}")
else
distinctValuesStrings.map(v => s"$usingKeyString $v")
}
}

def stringValuesCount: Int =
values.count {
case _: StringValue => true
Expand Down
Loading

0 comments on commit 66f6078

Please sign in to comment.