diff --git a/modules/build/src/main/scala/scala/build/bsp/BspClient.scala b/modules/build/src/main/scala/scala/build/bsp/BspClient.scala index 59346d8add..1302a774bd 100644 --- a/modules/build/src/main/scala/scala/build/bsp/BspClient.scala +++ b/modules/build/src/main/scala/scala/build/bsp/BspClient.scala @@ -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 @@ -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) diff --git a/modules/build/src/main/scala/scala/build/preprocessing/DirectivesPreprocessor.scala b/modules/build/src/main/scala/scala/build/preprocessing/DirectivesPreprocessor.scala index 1fc600d258..6a493ed232 100644 --- a/modules/build/src/main/scala/scala/build/preprocessing/DirectivesPreprocessor.scala +++ b/modules/build/src/main/scala/scala/build/preprocessing/DirectivesPreprocessor.scala @@ -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 diff --git a/modules/build/src/main/scala/scala/build/preprocessing/ExtractedDirectives.scala b/modules/build/src/main/scala/scala/build/preprocessing/ExtractedDirectives.scala index 4f4846ec0f..d9ce4ef21d 100644 --- a/modules/build/src/main/scala/scala/build/preprocessing/ExtractedDirectives.scala +++ b/modules/build/src/main/scala/scala/build/preprocessing/ExtractedDirectives.scala @@ -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 } diff --git a/modules/build/src/main/scala/scala/build/preprocessing/ScalaPreprocessor.scala b/modules/build/src/main/scala/scala/build/preprocessing/ScalaPreprocessor.scala index a46e12c212..3e4ced4fd1 100644 --- a/modules/build/src/main/scala/scala/build/preprocessing/ScalaPreprocessor.scala +++ b/modules/build/src/main/scala/scala/build/preprocessing/ScalaPreprocessor.scala @@ -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( diff --git a/modules/build/src/main/scala/scala/build/preprocessing/ScriptPreprocessor.scala b/modules/build/src/main/scala/scala/build/preprocessing/ScriptPreprocessor.scala index 3cb7bf3f59..89bddc4e72 100644 --- a/modules/build/src/main/scala/scala/build/preprocessing/ScriptPreprocessor.scala +++ b/modules/build/src/main/scala/scala/build/preprocessing/ScriptPreprocessor.scala @@ -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, @@ -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 diff --git a/modules/build/src/main/scala/scala/build/preprocessing/SheBang.scala b/modules/build/src/main/scala/scala/build/preprocessing/SheBang.scala index 3b1b29fb56..a8ea735faa 100644 --- a/modules/build/src/main/scala/scala/build/preprocessing/SheBang.scala +++ b/modules/build/src/main/scala/scala/build/preprocessing/SheBang.scala @@ -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) diff --git a/modules/build/src/main/scala/scala/build/preprocessing/UsingDirectivesOps.scala b/modules/build/src/main/scala/scala/build/preprocessing/UsingDirectivesOps.scala index c9481eb66b..c8d8b0e2de 100644 --- a/modules/build/src/main/scala/scala/build/preprocessing/UsingDirectivesOps.scala +++ b/modules/build/src/main/scala/scala/build/preprocessing/UsingDirectivesOps.scala @@ -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 { @@ -24,6 +49,7 @@ object UsingDirectivesOps { Nil } - def nonEmpty: Boolean = !ud.getFlattenedMap.isEmpty + def nonEmpty: Boolean = !isEmpty + def isEmpty: Boolean = ud.getFlattenedMap.isEmpty } } diff --git a/modules/build/src/test/scala/scala/build/tests/DirectiveTests.scala b/modules/build/src/test/scala/scala/build/tests/DirectiveTests.scala index e6b28e80b6..d69f92ba17 100644 --- a/modules/build/src/test/scala/scala/build/tests/DirectiveTests.scala +++ b/modules/build/src/test/scala/scala/build/tests/DirectiveTests.scala @@ -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)) diff --git a/modules/build/src/test/scala/scala/build/tests/ScalaPreprocessorTests.scala b/modules/build/src/test/scala/scala/build/tests/ScalaPreprocessorTests.scala index 8996117fae..87f5230799 100644 --- a/modules/build/src/test/scala/scala/build/tests/ScalaPreprocessorTests.scala +++ b/modules/build/src/test/scala/scala/build/tests/ScalaPreprocessorTests.scala @@ -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, @@ -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) + } + } } diff --git a/modules/cli/src/main/scala/scala/cli/ScalaCliCommands.scala b/modules/cli/src/main/scala/scala/cli/ScalaCliCommands.scala index c5c05f3802..f60a719d21 100644 --- a/modules/cli/src/main/scala/scala/cli/ScalaCliCommands.scala +++ b/modules/cli/src/main/scala/scala/cli/ScalaCliCommands.scala @@ -38,6 +38,7 @@ class ScalaCliCommands( directories.Directories, doc.Doc, export0.Export, + fix.Fix, fmt.Fmt, new HelpCmd(help), installcompletions.InstallCompletions, diff --git a/modules/cli/src/main/scala/scala/cli/commands/fix/Fix.scala b/modules/cli/src/main/scala/scala/cli/commands/fix/Fix.scala new file mode 100644 index 0000000000..3f42d6bb00 --- /dev/null +++ b/modules/cli/src/main/scala/scala/cli/commands/fix/Fix.scala @@ -0,0 +1,366 @@ +package scala.cli.commands.fix + +import caseapp.core.RemainingArgs + +import scala.build.Ops.EitherMap2 +import scala.build.errors.{BuildException, CompositeBuildException} +import scala.build.input.* +import scala.build.internal.Constants +import scala.build.options.{BuildOptions, Scope, SuppressWarningOptions} +import scala.build.preprocessing.directives.* +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] { + override def group = "Main" + override def scalaSpecificationLevel = SpecificationLevel.EXPERIMENTAL + override def sharedOptions(options: FixOptions): Option[SharedOptions] = Some(options.shared) + + lazy val targetDirectivesKeysSet = DirectivesPreprocessingUtils.requireDirectiveHandlers + .flatMap(_.keys.flatMap(_.nameAliases)).toSet + lazy val usingDirectivesKeysGrouped = DirectivesPreprocessingUtils.usingDirectiveHandlers + .flatMap(_.keys) + lazy val usingDirectivesWithTestPrefixKeysGrouped = + DirectivesPreprocessingUtils.usingDirectiveWithReqsHandlers + .flatMap(_.keys) + + val newLine = System.lineSeparator() + + override def runCommand(options: FixOptions, args: RemainingArgs, logger: Logger): Unit = { + val inputs = options.shared.inputs(args.remaining, () => Inputs.default()).orExit(logger) + + val (mainSources, testSources) = getProjectSources(inputs) + .left.map(CompositeBuildException(_)) + .orExit(logger) + + // Only initial inputs are used, new inputs discovered during processing of + // CrossSources.forInput may be shared between projects + val writableInputs: Seq[OnDisk] = inputs.flattened() + .collect { case onDisk: OnDisk => onDisk } + + def isExtractedFromWritableInput(position: Option[Position.File]): Boolean = { + val originOrPathOpt = position.map(_.path) + originOrPathOpt match { + case Some(Right(path)) => writableInputs.exists(_.path == path) + case _ => false + } + } + + val projectFileContents = new StringBuilder() + + given LoggingUtilities(logger, inputs.workspace) + + // Deal with directives from the Main scope + val (directivesFromWritableMainInputs, testDirectivesFromMain) = { + val originalMainDirectives = getExtractedDirectives(mainSources) + .filterNot(hasTargetDirectives) + + val transformedMainDirectives = unifyCorrespondingNameAliases(originalMainDirectives) + + val allDirectives = for { + transformedMainDirective <- transformedMainDirectives + directive <- transformedMainDirective.directives + } yield directive + + val (testScopeDirectives, allMainDirectives) = + allDirectives.partition(_.key.startsWith("test")) + + createFormattedLinesAndAppend(allMainDirectives, projectFileContents, isTest = false) + + ( + transformedMainDirectives.filter(d => isExtractedFromWritableInput(d.positions)), + testScopeDirectives + ) + } + + // Deal with directives from the Test scope + val directivesFromWritableTestInputs: Seq[TransformedTestDirectives] = + if ( + testSources.paths.nonEmpty || testSources.inMemory.nonEmpty || testDirectivesFromMain.nonEmpty + ) { + val originalTestDirectives = getExtractedDirectives(testSources) + .filterNot(hasTargetDirectives) + + val transformedTestDirectives = unifyCorrespondingNameAliases(originalTestDirectives) + .pipe(maybeTransformIntoTestEquivalent) + + val allDirectives = for { + directivesWithTestPrefix <- transformedTestDirectives.map(_.withTestPrefix) + directive <- directivesWithTestPrefix ++ testDirectivesFromMain + } yield directive + + createFormattedLinesAndAppend(allDirectives, projectFileContents, isTest = true) + + transformedTestDirectives + .filter(ttd => isExtractedFromWritableInput(ttd.positions)) + } + else Seq(TransformedTestDirectives(Nil, Nil, None)) + + projectFileContents.append(newLine) + + // Write extracted directives to project.scala + logger.message(s"Writing ${Constants.projectFileName}") + os.write.over(inputs.workspace / Constants.projectFileName, projectFileContents.toString) + + def isProjectFile(position: Option[Position.File]): Boolean = + position.exists(_.path.contains(inputs.workspace / Constants.projectFileName)) + + // Remove directives from their original files, skip the project.scala file + directivesFromWritableMainInputs + .filterNot(e => isProjectFile(e.positions)) + .foreach(d => removeDirectivesFrom(d.positions)) + directivesFromWritableTestInputs + .filterNot(ttd => isProjectFile(ttd.positions)) + .foreach(ttd => removeDirectivesFrom(ttd.positions, toKeep = ttd.noTestPrefixAvailable)) + } + + def getProjectSources(inputs: Inputs): Either[::[BuildException], (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.suppressAll, + 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, inputs.workspace) + val testSources = scopedSources.sources(Scope.Test, sharedOptions, inputs.workspace) + + (mainSources, testSources).traverseN + } + + def getExtractedDirectives(sources: Sources)( + using loggingUtilities: LoggingUtilities + ): Seq[ExtractedDirectives] = { + val logger = loggingUtilities.logger + + val fromPaths = sources.paths.map { (path, _) => + val (_, content) = SheBang.partitionOnShebangSection(os.read(path)) + logger.debug(s"Extracting directives from ${loggingUtilities.relativePath(path)}") + ExtractedDirectives.from(content.toCharArray, Right(path), logger, _ => None).orExit(logger) + } + + val fromInMemory = sources.inMemory.map { inMem => + val originOrPath = inMem.originalPath.map((_, path) => path) + val content = originOrPath match { + case Right(path) => + logger.debug(s"Extracting directives from ${loggingUtilities.relativePath(path)}") + os.read(path) + case Left(origin) => + logger.debug(s"Extracting directives from $origin") + inMem.wrapperParamsOpt match { + // In case of script snippets, we need to drop the top wrapper lines + case Some(wrapperParams) => String(inMem.content) + .linesWithSeparators + .drop(wrapperParams.topWrapperLineCount) + .mkString + case None => String(inMem.content) + } + } + + val (_, contentWithNoShebang) = SheBang.partitionOnShebangSection(content) + + ExtractedDirectives.from( + contentWithNoShebang.toCharArray, + originOrPath, + logger, + _ => None + ).orExit(logger) + } + + fromPaths ++ fromInMemory + } + + def hasTargetDirectives(extractedDirectives: ExtractedDirectives): Boolean = { + // Filter out all elements that contain using target directives + val directivesInElement = extractedDirectives.directives.map(_.key) + directivesInElement.exists(key => targetDirectivesKeysSet.contains(key)) + } + + def unifyCorrespondingNameAliases(extractedDirectives: Seq[ExtractedDirectives]) = + extractedDirectives.map { extracted => + // All keys that we migrate, not all in general + val allKeysGrouped = usingDirectivesKeysGrouped ++ usingDirectivesWithTestPrefixKeysGrouped + val strictDirectives = extracted.directives + + val strictDirectivesWithNewKeys = strictDirectives.flatMap { strictDir => + val newKeyOpt = allKeysGrouped.find(_.nameAliases.contains(strictDir.key)) + .flatMap(_.nameAliases.headOption) + .map { key => + if (key.startsWith("test")) + val withTestStripped = key.stripPrefix("test").stripPrefix(".") + "test." + withTestStripped.take(1).toLowerCase + withTestStripped.drop(1) + else + key + } + + newKeyOpt.map(newKey => strictDir.copy(key = newKey)) + } + + extracted.copy(directives = strictDirectivesWithNewKeys) + } + + /** Transforms directives into their 'test.' equivalent if it exists + * + * @param extractedDirectives + * @return + * an instance of TransformedTestDirectives containing transformed directives and those that + * could not be transformed since they have no 'test.' equivalent + */ + def maybeTransformIntoTestEquivalent(extractedDirectives: Seq[ExtractedDirectives]) + : Seq[TransformedTestDirectives] = + for { + extractedFromSingleElement <- extractedDirectives + directives = extractedFromSingleElement.directives + } yield { + val (withTestEquivalent, noTestEquivalent) = directives.partition { directive => + usingDirectivesWithTestPrefixKeysGrouped.exists( + _.nameAliases.contains("test." + directive.key) + ) + } + + val transformedToTestEquivalents = withTestEquivalent.map { + case StrictDirective(key, values) => StrictDirective("test." + key, values) + } + + TransformedTestDirectives( + withTestPrefix = transformedToTestEquivalents, + noTestPrefixAvailable = noTestEquivalent, + positions = extractedFromSingleElement.positions + ) + } + + def removeDirectivesFrom( + position: Option[Position.File], + toKeep: Seq[StrictDirective] = Nil + )( + using loggingUtilities: LoggingUtilities + ): Unit = { + position match { + case Some(Position.File(Right(path), _, _, offset)) => + val (shebangSection, strippedContent) = SheBang.partitionOnShebangSection(os.read(path)) + + def ignoreOrAddNewLine(str: String) = if str.isBlank then "" else str + newLine + + val keepLines = ignoreOrAddNewLine(shebangSection) + ignoreOrAddNewLine(toKeep.mkString( + "", + newLine, + newLine + )) + val newContents = keepLines + strippedContent.drop(offset).stripLeading() + val relativePath = loggingUtilities.relativePath(path) + + loggingUtilities.logger.message(s"Removing directives from $relativePath") + if (toKeep.nonEmpty) { + loggingUtilities.logger.message(" Keeping:") + toKeep.foreach(d => loggingUtilities.logger.message(s" $d")) + } + + os.write.over(path, newContents.stripLeading()) + case _ => () + } + } + + def createFormattedLinesAndAppend( + strictDirectives: Seq[StrictDirective], + projectFileContents: StringBuilder, + isTest: Boolean + ): Unit = { + if (strictDirectives.nonEmpty) { + projectFileContents + .append(if (projectFileContents.nonEmpty) newLine else "") + .append(if isTest then "// Test" else "// Main") + .append(newLine) + + 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], + positions: Option[Position.File] + ) + + case class LoggingUtilities( + logger: Logger, + workspacePath: os.Path + ) { + def relativePath(path: os.Path) = + if (path.startsWith(workspacePath)) path.relativeTo(workspacePath) + else path + } + + private val directivesOrdering: Ordering[String] = { + def directivesOrder(key: String): Int = { + val handlersOrder = Seq( + ScalaVersion.handler.keys, + Platform.handler.keys, + Jvm.handler.keys, + JavaHome.handler.keys, + ScalaNative.handler.keys, + ScalaJs.handler.keys, + ScalacOptions.handler.keys, + JavaOptions.handler.keys, + JavacOptions.handler.keys, + JavaProps.handler.keys, + MainClass.handler.keys, + scala.build.preprocessing.directives.Sources.handler.keys, + ObjectWrapper.handler.keys, + Toolkit.handler.keys, + Dependency.handler.keys + ) + + handlersOrder.zipWithIndex + .find(_._1.flatMap(_.nameAliases).contains(key)) + .map(_._2) + .getOrElse(if key.startsWith("publish") then 20 else 15) + } + + Ordering.by { directiveLine => + val key = directiveLine + .stripPrefix("//> using") + .stripLeading() + .stripPrefix("test.") + // separate key from value + .takeWhile(!_.isWhitespace) + + directivesOrder(key) + } + } +} diff --git a/modules/cli/src/main/scala/scala/cli/commands/fix/FixOptions.scala b/modules/cli/src/main/scala/scala/cli/commands/fix/FixOptions.scala new file mode 100644 index 0000000000..a1a2400abd --- /dev/null +++ b/modules/cli/src/main/scala/scala/cli/commands/fix/FixOptions.scala @@ -0,0 +1,33 @@ +package scala.cli.commands.fix + +import caseapp.* +import caseapp.core.help.Help + +import scala.cli.ScalaCli +import scala.cli.commands.shared.{HasSharedOptions, HelpMessages, SharedOptions} +import scala.cli.commands.tags + +// format: off +@HelpMessage(FixOptions.helpMessage, "", FixOptions.detailedHelpMessage) +final case class FixOptions( + @Recurse + shared: SharedOptions = SharedOptions() +) extends HasSharedOptions +// format: on + +object FixOptions { + implicit lazy val parser: Parser[FixOptions] = Parser.derive + implicit lazy val help: Help[FixOptions] = Help.derive + + val cmdName = "fix" + private val helpHeader = "Perform fixes on a Scala CLI project." + val helpMessage: String = HelpMessages.shortHelpMessage(cmdName, helpHeader) + val detailedHelpMessage: String = + s"""$helpHeader + | + |${HelpMessages.commandConfigurations(cmdName)} + | + |${HelpMessages.acceptedInputs} + | + |${HelpMessages.commandDocWebsiteReference(cmdName)}""".stripMargin +} diff --git a/modules/core/src/main/scala/scala/build/Position.scala b/modules/core/src/main/scala/scala/build/Position.scala index 250e20873e..e7a13984e8 100644 --- a/modules/core/src/main/scala/scala/build/Position.scala +++ b/modules/core/src/main/scala/scala/build/Position.scala @@ -15,7 +15,8 @@ object Position { final case class File( path: Either[String, os.Path], startPos: (Int, Int), - endPos: (Int, Int) + endPos: (Int, Int), + offset: Int = 0 ) extends Position { def render(cwd: os.Path, sep: String): String = { val p = path match { diff --git a/modules/directives/src/main/scala/scala/build/preprocessing/directives/CustomJar.scala b/modules/directives/src/main/scala/scala/build/preprocessing/directives/CustomJar.scala index bb73a84630..8be6806bae 100644 --- a/modules/directives/src/main/scala/scala/build/preprocessing/directives/CustomJar.scala +++ b/modules/directives/src/main/scala/scala/build/preprocessing/directives/CustomJar.scala @@ -49,6 +49,7 @@ final case class CustomJar( @DirectiveName("source.jars") sourcesJar: DirectiveValueParser.WithScopePath[List[Positioned[String]]] = DirectiveValueParser.WithScopePath.empty(Nil), + @DirectiveName("test.sourcesJar") @DirectiveName("test.sources.jar") @DirectiveName("test.sourcesJars") @DirectiveName("test.sources.jars") diff --git a/modules/directives/src/main/scala/scala/build/preprocessing/directives/Dependency.scala b/modules/directives/src/main/scala/scala/build/preprocessing/directives/Dependency.scala index a41486137e..dbce2a1509 100644 --- a/modules/directives/src/main/scala/scala/build/preprocessing/directives/Dependency.scala +++ b/modules/directives/src/main/scala/scala/build/preprocessing/directives/Dependency.scala @@ -36,6 +36,7 @@ final case class Dependency( @DirectiveName("deps") @DirectiveName("dependencies") dependency: List[Positioned[String]] = Nil, + @DirectiveName("test.dependency") @DirectiveName("test.dep") @DirectiveName("test.deps") @DirectiveName("test.dependencies") diff --git a/modules/directives/src/main/scala/scala/build/preprocessing/directives/DirectiveHandler.scala b/modules/directives/src/main/scala/scala/build/preprocessing/directives/DirectiveHandler.scala index e1ef71ab77..40d782cb3c 100644 --- a/modules/directives/src/main/scala/scala/build/preprocessing/directives/DirectiveHandler.scala +++ b/modules/directives/src/main/scala/scala/build/preprocessing/directives/DirectiveHandler.scala @@ -37,7 +37,7 @@ trait DirectiveHandler[+T] { self => final def isRestricted: Boolean = scalaSpecificationLevel == SpecificationLevel.RESTRICTED final def isExperimental: Boolean = scalaSpecificationLevel == SpecificationLevel.EXPERIMENTAL - def keys: Seq[String] + def keys: Seq[Key] def handleValues( scopedDirective: ScopedDirective, @@ -80,6 +80,9 @@ trait DirectiveHandler[+T] { self => } +/** Using directive key with all its aliases */ +case class Key(nameAliases: Seq[String]) + object DirectiveHandler { // from https://github.com/alexarchambault/case-app/blob/7ac9ae7cc6765df48eab27c4e35c66b00e4469a7/core/shared/src/main/scala/caseapp/core/util/CaseUtil.scala#L5-L22 @@ -315,9 +318,9 @@ object DirectiveHandler { } val keysValue = Expr.ofList { - fields0.flatMap { + fields0.map { case (sym, _) => - withPrefix(Expr(sym.name)) +: namesFromAnnotations(sym) + Expr.ofList(withPrefix(Expr(sym.name)) +: namesFromAnnotations(sym)) } } @@ -429,15 +432,17 @@ object DirectiveHandler { def scalaSpecificationLevel = $levelValue lazy val keys = $keysValue - .flatMap(key => - List( - key, - DirectiveHandler.pascalCaseSplit(key.toCharArray.toList) - .map(_.toLowerCase(Locale.ROOT)) - .mkString("-") - ) - ) - .distinct + .map { nameAliases => + val allAliases = nameAliases.flatMap(key => + List( + key, + DirectiveHandler.pascalCaseSplit(key.toCharArray.toList) + .map(_.toLowerCase(Locale.ROOT)) + .mkString("-") + ) + ).distinct + Key(allAliases) + } def handleValues(scopedDirective: ScopedDirective, logger: Logger) = ${ handleValuesImpl('{ scopedDirective }, '{ logger }) } diff --git a/modules/directives/src/main/scala/scala/build/preprocessing/directives/JavaOptions.scala b/modules/directives/src/main/scala/scala/build/preprocessing/directives/JavaOptions.scala index 70d7e381d7..ee71cf937a 100644 --- a/modules/directives/src/main/scala/scala/build/preprocessing/directives/JavaOptions.scala +++ b/modules/directives/src/main/scala/scala/build/preprocessing/directives/JavaOptions.scala @@ -19,6 +19,7 @@ import scala.cli.commands.SpecificationLevel final case class JavaOptions( @DirectiveName("javaOpt") javaOptions: List[Positioned[String]] = Nil, + @DirectiveName("test.javaOptions") @DirectiveName("test.javaOpt") testJavaOptions: List[Positioned[String]] = Nil ) extends HasBuildOptionsWithRequirements { diff --git a/modules/directives/src/main/scala/scala/build/preprocessing/directives/JavaProps.scala b/modules/directives/src/main/scala/scala/build/preprocessing/directives/JavaProps.scala index 73bc35d52d..a13bdf275d 100644 --- a/modules/directives/src/main/scala/scala/build/preprocessing/directives/JavaProps.scala +++ b/modules/directives/src/main/scala/scala/build/preprocessing/directives/JavaProps.scala @@ -23,6 +23,7 @@ import scala.cli.commands.SpecificationLevel final case class JavaProps( @DirectiveName("javaProp") javaProperty: List[Positioned[String]] = Nil, + @DirectiveName("test.javaProperty") @DirectiveName("test.javaProp") testJavaProperty: List[Positioned[String]] = Nil ) extends HasBuildOptionsWithRequirements { diff --git a/modules/directives/src/main/scala/scala/build/preprocessing/directives/JavacOptions.scala b/modules/directives/src/main/scala/scala/build/preprocessing/directives/JavacOptions.scala index d327a01a68..e04fd74ff7 100644 --- a/modules/directives/src/main/scala/scala/build/preprocessing/directives/JavacOptions.scala +++ b/modules/directives/src/main/scala/scala/build/preprocessing/directives/JavacOptions.scala @@ -19,6 +19,7 @@ import scala.cli.commands.SpecificationLevel final case class JavacOptions( @DirectiveName("javacOpt") javacOptions: List[Positioned[String]] = Nil, + @DirectiveName("test.javacOptions") @DirectiveName("test.javacOpt") testJavacOptions: List[Positioned[String]] = Nil ) extends HasBuildOptionsWithRequirements { diff --git a/modules/directives/src/main/scala/scala/build/preprocessing/directives/Resources.scala b/modules/directives/src/main/scala/scala/build/preprocessing/directives/Resources.scala index 8c33029027..2b8ce000bc 100644 --- a/modules/directives/src/main/scala/scala/build/preprocessing/directives/Resources.scala +++ b/modules/directives/src/main/scala/scala/build/preprocessing/directives/Resources.scala @@ -25,6 +25,7 @@ final case class Resources( resourceDirs: DirectiveValueParser.WithScopePath[List[Positioned[String]]] = DirectiveValueParser.WithScopePath.empty(Nil), @DirectiveName("test.resourceDir") + @DirectiveName("test.resourceDirs") testResourceDirs: DirectiveValueParser.WithScopePath[List[Positioned[String]]] = DirectiveValueParser.WithScopePath.empty(Nil) ) extends HasBuildOptionsWithRequirements { diff --git a/modules/directives/src/main/scala/scala/build/preprocessing/directives/StrictDirective.scala b/modules/directives/src/main/scala/scala/build/preprocessing/directives/StrictDirective.scala index be4798ad8f..78b532243d 100644 --- a/modules/directives/src/main/scala/scala/build/preprocessing/directives/StrictDirective.scala +++ b/modules/directives/src/main/scala/scala/build/preprocessing/directives/StrictDirective.scala @@ -14,6 +14,35 @@ case class StrictDirective( val suffix = if validValues.isEmpty then "" else s" \"${validValues.mkString("\", \"")}\"" s"//> using $key$suffix" } + + /** 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 diff --git a/modules/directives/src/main/scala/scala/build/preprocessing/directives/Tests.scala b/modules/directives/src/main/scala/scala/build/preprocessing/directives/Tests.scala index ba898915a5..8e5501df59 100644 --- a/modules/directives/src/main/scala/scala/build/preprocessing/directives/Tests.scala +++ b/modules/directives/src/main/scala/scala/build/preprocessing/directives/Tests.scala @@ -16,6 +16,7 @@ import scala.cli.commands.SpecificationLevel @DirectiveLevel(SpecificationLevel.SHOULD) // format: off final case class Tests( + @DirectiveName("test.framework") testFramework: Option[String] = None ) extends HasBuildOptions { // format: on diff --git a/modules/integration/src/test/scala/scala/cli/integration/CompileTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/CompileTestDefinitions.scala index feca6ba033..d0b532684a 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/CompileTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/CompileTestDefinitions.scala @@ -135,15 +135,17 @@ abstract class CompileTestDefinitions(val scalaVersionOpt: Option[String]) } test( - "target directives in files should not produce warnings about using directives in multiple files" + "having target + using directives in files should not produce warnings about using directives in multiple files" ) { val inputs = TestInputs( os.rel / "Bar.java" -> """//> using target.platform "jvm" + |//> using jvm "17" |public class Bar {} |""".stripMargin, os.rel / "Foo.test.scala" -> """//> using target.scala.>= "2.13" + |//> using dep "com.lihaoyi::os-lib::0.8.1" |class Foo {} |""".stripMargin ) @@ -162,12 +164,10 @@ abstract class CompileTestDefinitions(val scalaVersionOpt: Option[String]) val inputs = TestInputs( os.rel / "Bar.java" -> """//> using jvm "17" - |//> using target.scope "test" |public class Bar {} |""".stripMargin, os.rel / "Foo.scala" -> - """//> using target.scala.>= "2.13" - |//> using dep "com.lihaoyi::os-lib::0.8.1" + """//> using dep "com.lihaoyi::os-lib::0.8.1" |class Foo {} |""".stripMargin ) diff --git a/modules/integration/src/test/scala/scala/cli/integration/FixTests.scala b/modules/integration/src/test/scala/scala/cli/integration/FixTests.scala new file mode 100644 index 0000000000..0a342d8746 --- /dev/null +++ b/modules/integration/src/test/scala/scala/cli/integration/FixTests.scala @@ -0,0 +1,407 @@ +package scala.cli.integration + +import com.eed3si9n.expecty.Expecty.expect + +class FixTests extends ScalaCliSuite { + + override def group: ScalaCliSuite.TestGroup = ScalaCliSuite.TestGroup.First + + val projectFileName = "project.scala" + + val extraOptions = Seq("--suppress-experimental-feature-warning") + + test("fix basic") { + val mainFileName = "Main.scala" + val inputs = TestInputs( + os.rel / mainFileName -> + s"""//> using objectWrapper + |//> using dep com.lihaoyi::os-lib:0.9.1 com.lihaoyi::upickle:3.1.2 + | + |package com.foo.main + | + |object Main extends App { + | println(os.pwd) + |} + |""".stripMargin, + os.rel / projectFileName -> + s"""//> using lib "com.lihaoyi::pprint:0.6.6" + |""".stripMargin + ) + + inputs.fromRoot { root => + + val fixOutput = os.proc(TestUtil.cli, "--power", "fix", ".", "-v", "-v", extraOptions) + .call(cwd = root, mergeErrIntoOut = true).out.trim() + + assertNoDiff( + fixOutput, + """Extracting directives from Main.scala + |Extracting directives from project.scala + |Writing project.scala + |Removing directives from Main.scala""".stripMargin + ) + + val projectFileContents = os.read(root / projectFileName) + val mainFileContents = os.read(root / mainFileName) + + assertNoDiff( + projectFileContents, + """// Main + |//> using objectWrapper + | + |//> using dependency "com.lihaoyi::os-lib:0.9.1" + |//> using dependency "com.lihaoyi::pprint:0.6.6" + |//> using dependency "com.lihaoyi::upickle:3.1.2" + | + |""".stripMargin + ) + + assertNoDiff( + mainFileContents, + """package com.foo.main + | + |object Main extends App { + | println(os.pwd) + |} + |""".stripMargin + ) + + val runProc = os.proc(TestUtil.cli, "--power", "compile", ".", extraOptions) + .call(cwd = root, stderr = os.Pipe) + + expect(!runProc.err.trim.contains("Using directives detected in multiple files")) + } + } + + test("fix script with shebang") { + val mainFileName = "main.sc" + val inputs = TestInputs( + os.rel / mainFileName -> + s"""#!/usr/bin/env -S scala-cli shebang + | + |//> using objectWrapper + |//> using dep com.lihaoyi::os-lib:0.9.1 com.lihaoyi::upickle:3.1.2 + | + |println(os.pwd) + |""".stripMargin, + os.rel / projectFileName -> + s"""//> using lib "com.lihaoyi::pprint:0.6.6" + |""".stripMargin + ) + + inputs.fromRoot { root => + + val fixOutput = os.proc(TestUtil.cli, "--power", "fix", ".", "-v", "-v", extraOptions) + .call(cwd = root, mergeErrIntoOut = true).out.trim() + + assertNoDiff( + fixOutput, + """Extracting directives from project.scala + |Extracting directives from main.sc + |Writing project.scala + |Removing directives from main.sc""".stripMargin + ) + + val projectFileContents = os.read(root / projectFileName) + val mainFileContents = os.read(root / mainFileName) + + assertNoDiff( + projectFileContents, + """// Main + |//> using objectWrapper + | + |//> using dependency "com.lihaoyi::os-lib:0.9.1" + |//> using dependency "com.lihaoyi::pprint:0.6.6" + |//> using dependency "com.lihaoyi::upickle:3.1.2" + | + |""".stripMargin + ) + + assertNoDiff( + mainFileContents, + """#!/usr/bin/env -S scala-cli shebang + | + |println(os.pwd) + |""".stripMargin + ) + + val runProc = os.proc(TestUtil.cli, "--power", "compile", ".", extraOptions) + .call(cwd = root, stderr = os.Pipe) + + expect(!runProc.err.trim.contains("Using directives detected in multiple files")) + } + } + + test("fix with test scope") { + val mainSubPath = os.rel / "src" / "Main.scala" + val testSubPath = os.rel / "test" / "MyTests.scala" + val inputs = TestInputs( + mainSubPath -> + s"""//> using objectWrapper + |//> using dep "com.lihaoyi::os-lib:0.9.1" + | + |//> using test.dep "org.typelevel::cats-core:2.9.0" + | + |package com.foo.main + | + |object Main extends App { + | println(os.pwd) + |} + |""".stripMargin, + testSubPath -> + s"""//> using options -Xasync, -Xfatal-warnings + |//> using dep org.scalameta::munit::0.7.29 + | + |package com.foo.test.bar + | + |class MyTests extends munit.FunSuite { + | test("bar") { + | assert(2 + 2 == 4) + | println("Hello from " + "tests") + | } + |} + |""".stripMargin, + os.rel / projectFileName -> + s"""//> using lib com.lihaoyi::pprint:0.6.6 + |""".stripMargin + ) + + inputs.fromRoot { root => + + val fixOutput = os.proc(TestUtil.cli, "--power", "fix", ".", "-v", "-v", extraOptions) + .call(cwd = root, mergeErrIntoOut = true).out.trim() + + assertNoDiff( + fixOutput, + """Extracting directives from project.scala + |Extracting directives from src/Main.scala + |Extracting directives from test/MyTests.scala + |Writing project.scala + |Removing directives from src/Main.scala + |Removing directives from test/MyTests.scala""".stripMargin + ) + + val projectFileContents = os.read(root / projectFileName) + val mainFileContents = os.read(root / mainSubPath) + val testFileContents = os.read(root / testSubPath) + + assertNoDiff( + projectFileContents, + """// Main + |//> using objectWrapper + |//> using dependency "com.lihaoyi::os-lib:0.9.1" "com.lihaoyi::pprint:0.6.6" + | + |// Test + |//> using test.options "-Xasync" "-Xfatal-warnings" + |//> using test.dependency "org.scalameta::munit::0.7.29" "org.typelevel::cats-core:2.9.0" + |""".stripMargin + ) + + assertNoDiff( + mainFileContents, + """package com.foo.main + | + |object Main extends App { + | println(os.pwd) + |} + |""".stripMargin + ) + + assertNoDiff( + testFileContents, + """package com.foo.test.bar + | + |class MyTests extends munit.FunSuite { + | test("bar") { + | assert(2 + 2 == 4) + | println("Hello from " + "tests") + | } + |} + |""".stripMargin + ) + + val runProc = os.proc(TestUtil.cli, "--power", "compile", ".", extraOptions) + .call(cwd = root, stderr = os.Pipe) + + expect(!runProc.err.trim.contains("Using directives detected in multiple files")) + } + } + + test("fix complex inputs") { + val mainSubPath = os.rel / "src" / "Main.scala" + val testSubPath = os.rel / "test" / "MyTests.scala" + + val withUsedTargetSubPath = os.rel / "src" / "UsedTarget.scala" + val withUsedTargetContents = + s"""//> using target.scala 3.3.0 + |//> using dep com.lihaoyi::upickle:3.1.2 + |case class UsedTarget(x: Int) + |""".stripMargin + val withUnusedTargetSubPath = os.rel / "src" / "UnusedTarget.scala" + val withUnusedTargetContents = + s"""//> using target.scala 2.13 + |//> using dep com.lihaoyi::upickle:3.1.2 + |case class UnusedTarget(x: Int) + |""".stripMargin + + val includedInputs = TestInputs( + os.rel / "Included.scala" -> + """//> using options -Werror + | + |case class Included(x: Int) + |""".stripMargin + ) + + includedInputs.fromRoot { includeRoot => + val includePath = (includeRoot / "Included.scala").toString.replace("\\", "\\\\") + + val inputs = TestInputs( + mainSubPath -> + s"""//> using platforms "jvm" + |//> using scala "3.3.0" + |//> using jvm "17" + |//> using objectWrapper + |//> using dep com.lihaoyi::os-lib:0.9.1 + |//> using file $includePath + | + |//> using test.dep "org.typelevel::cats-core:2.9.0" + | + |package com.foo.main + | + |object Main extends App { + | println(os.pwd) + |} + |""".stripMargin, + withUsedTargetSubPath -> withUsedTargetContents, + withUnusedTargetSubPath -> withUnusedTargetContents, + testSubPath -> + s"""//> using options -Xasync, -Xfatal-warnings + |//> using dep "org.scalameta::munit::0.7.29" + |//> using scala 3.2.2 + | + |package com.foo.test.bar + | + |class MyTests extends munit.FunSuite { + | test("bar") { + | assert(2 + 2 == 4) + | println("Hello from " + "tests") + | } + |} + |""".stripMargin, + os.rel / projectFileName -> + s"""//> using lib com.lihaoyi::pprint:0.6.6 + | + |//> using publish.ci.password env:PUBLISH_PASSWORD + |//> using publish.ci.secretKey env:PUBLISH_SECRET_KEY + |//> using publish.ci.secretKeyPassword env:PUBLISH_SECRET_KEY_PASSWORD + |//> using publish.ci.user env:PUBLISH_USER + |""".stripMargin + ) + + inputs.fromRoot { root => + val res = os.proc( + TestUtil.cli, + "--power", + "fix", + ".", + "--script-snippet", + "//> using toolkit latest", + "-v", + "-v", + extraOptions + ).call(cwd = root, stderr = os.Pipe) + + assertNoDiff( + res.err.trim(), + s"""Extracting directives from project.scala + |Extracting directives from src/Main.scala + |Extracting directives from src/UsedTarget.scala + |Extracting directives from ${includeRoot / "Included.scala"} + |Extracting directives from snippet + |Extracting directives from test/MyTests.scala + |Writing project.scala + |Removing directives from src/Main.scala + |Removing directives from test/MyTests.scala + | Keeping: + | //> using scala "3.2.2"""".stripMargin + ) + + val projectFileContents = os.read(root / projectFileName) + val mainFileContents = os.read(root / mainSubPath) + val testFileContents = os.read(root / testSubPath) + val withUsedTargetContentsRead = os.read(root / withUsedTargetSubPath) + val withUnusedTargetContentsRead = os.read(root / withUnusedTargetSubPath) + + assertNoDiff( + projectFileContents, + s"""// Main + |//> using scala "3.3.0" + |//> using platforms "jvm" + |//> using jvm "17" + |//> using options "-Werror" + |//> using files "$includePath" + |//> using objectWrapper + |//> using toolkit "latest" + |//> using dependency "com.lihaoyi::os-lib:0.9.1" "com.lihaoyi::pprint:0.6.6" + | + |//> using publish.ci.password "env:PUBLISH_PASSWORD" + |//> using publish.ci.secretKey "env:PUBLISH_SECRET_KEY" + |//> using publish.ci.secretKeyPassword "env:PUBLISH_SECRET_KEY_PASSWORD" + |//> using publish.ci.user "env:PUBLISH_USER" + | + |// Test + |//> using test.options "-Xasync" "-Xfatal-warnings" + |//> using test.dependency "org.scalameta::munit::0.7.29" "org.typelevel::cats-core:2.9.0" + |""".stripMargin + ) + + assertNoDiff( + mainFileContents, + """package com.foo.main + | + |object Main extends App { + | println(os.pwd) + |} + |""".stripMargin + ) + + // Directives with no 'test.' equivalent are retained + assertNoDiff( + testFileContents, + """//> using scala "3.2.2" + | + |package com.foo.test.bar + | + |class MyTests extends munit.FunSuite { + | test("bar") { + | assert(2 + 2 == 4) + | println("Hello from " + "tests") + | } + |} + |""".stripMargin + ) + + assertNoDiff(withUsedTargetContents, withUsedTargetContentsRead) + assertNoDiff(withUnusedTargetContents, withUnusedTargetContentsRead) + + val runProc = os.proc(TestUtil.cli, "--power", "compile", ".") + .call(cwd = root, stderr = os.Pipe) + + val runErrOut = TestUtil.removeAnsiColors(runProc.err.trim) + expect(runErrOut.contains("Using directives detected in multiple files")) + expect(runErrOut.linesIterator.count(_.startsWith("[warn] //> using")) == 2) + expect(runErrOut.contains("[warn] //> using options -Werror")) + // TODO: Warning about using directives in multiple files for the test scope should not be displayed + expect(runErrOut.contains("[warn] //> using scala \"3.2.2\"")) + } + + assertNoDiff( + os.read(includeRoot / "Included.scala"), + """//> using options -Werror + | + |case class Included(x: Int) + |""".stripMargin + ) + } + } +} diff --git a/modules/options/src/main/scala/scala/build/options/SuppressWarningOptions.scala b/modules/options/src/main/scala/scala/build/options/SuppressWarningOptions.scala index dc7cba7690..7846debcf3 100644 --- a/modules/options/src/main/scala/scala/build/options/SuppressWarningOptions.scala +++ b/modules/options/src/main/scala/scala/build/options/SuppressWarningOptions.scala @@ -9,4 +9,10 @@ final case class SuppressWarningOptions( object SuppressWarningOptions { implicit val hasHashData: HasHashData[SuppressWarningOptions] = HasHashData.derive implicit val monoid: ConfigMonoid[SuppressWarningOptions] = ConfigMonoid.derive + + val suppressAll = SuppressWarningOptions( + suppressDirectivesInMultipleFilesWarning = Some(true), + suppressOutdatedDependencyWarning = Some(true), + suppressExperimentalFeatureWarning = Some(true) + ) } diff --git a/website/docs/reference/cli-options.md b/website/docs/reference/cli-options.md index 44927081dd..196819983e 100644 --- a/website/docs/reference/cli-options.md +++ b/website/docs/reference/cli-options.md @@ -56,7 +56,7 @@ Set JMH version Available in commands: -[`bloop`](./commands.md#bloop), [`bloop exit`](./commands.md#bloop-exit), [`bloop output`](./commands.md#bloop-output), [`bloop start`](./commands.md#bloop-start), [`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test), [`uninstall`](./commands.md#uninstall) +[`bloop`](./commands.md#bloop), [`bloop exit`](./commands.md#bloop-exit), [`bloop output`](./commands.md#bloop-output), [`bloop start`](./commands.md#bloop-start), [`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fix`](./commands.md#fix), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test), [`uninstall`](./commands.md#uninstall) @@ -229,7 +229,7 @@ Run given command against all provided Scala versions and/or platforms Available in commands: -[`bloop`](./commands.md#bloop), [`bloop start`](./commands.md#bloop-start), [`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`config`](./commands.md#config), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`pgp push`](./commands.md#pgp-push), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`publish setup`](./commands.md#publish-setup), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) +[`bloop`](./commands.md#bloop), [`bloop start`](./commands.md#bloop-start), [`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`config`](./commands.md#config), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fix`](./commands.md#fix), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`pgp push`](./commands.md#pgp-push), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`publish setup`](./commands.md#publish-setup), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) @@ -249,7 +249,7 @@ Debug mode (attach by default) Available in commands: -[`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) +[`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fix`](./commands.md#fix), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) @@ -424,7 +424,7 @@ Pass scalafmt version before running it (3.7.14 by default). If passed, this ove Available in commands: -[`add-path`](./commands.md#add-path), [`bloop`](./commands.md#bloop), [`bloop exit`](./commands.md#bloop-exit), [`bloop output`](./commands.md#bloop-output), [`bloop start`](./commands.md#bloop-start), [`bsp`](./commands.md#bsp), [`clean`](./commands.md#clean), [`compile`](./commands.md#compile), [`config`](./commands.md#config), [`default-file`](./commands.md#default-file), [`dependency-update`](./commands.md#dependency-update), [`directories`](./commands.md#directories), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`help`](./commands.md#help), [`install completions` , `install-completions`](./commands.md#install-completions), [`install-home`](./commands.md#install-home), [`new`](./commands.md#new), [`package`](./commands.md#package), [`pgp pull`](./commands.md#pgp-pull), [`pgp push`](./commands.md#pgp-push), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`publish setup`](./commands.md#publish-setup), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`github secret create` , `gh secret create`](./commands.md#github-secret-create), [`github secret list` , `gh secret list`](./commands.md#github-secret-list), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test), [`uninstall`](./commands.md#uninstall), [`uninstall completions` , `uninstall-completions`](./commands.md#uninstall-completions), [`update`](./commands.md#update), [`version`](./commands.md#version) +[`add-path`](./commands.md#add-path), [`bloop`](./commands.md#bloop), [`bloop exit`](./commands.md#bloop-exit), [`bloop output`](./commands.md#bloop-output), [`bloop start`](./commands.md#bloop-start), [`bsp`](./commands.md#bsp), [`clean`](./commands.md#clean), [`compile`](./commands.md#compile), [`config`](./commands.md#config), [`default-file`](./commands.md#default-file), [`dependency-update`](./commands.md#dependency-update), [`directories`](./commands.md#directories), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fix`](./commands.md#fix), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`help`](./commands.md#help), [`install completions` , `install-completions`](./commands.md#install-completions), [`install-home`](./commands.md#install-home), [`new`](./commands.md#new), [`package`](./commands.md#package), [`pgp pull`](./commands.md#pgp-pull), [`pgp push`](./commands.md#pgp-push), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`publish setup`](./commands.md#publish-setup), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`github secret create` , `gh secret create`](./commands.md#github-secret-create), [`github secret list` , `gh secret list`](./commands.md#github-secret-list), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test), [`uninstall`](./commands.md#uninstall), [`uninstall completions` , `uninstall-completions`](./commands.md#uninstall-completions), [`update`](./commands.md#update), [`version`](./commands.md#version) @@ -438,7 +438,7 @@ Suppress warnings about using experimental features Available in commands: -[`add-path`](./commands.md#add-path), [`bloop`](./commands.md#bloop), [`bloop exit`](./commands.md#bloop-exit), [`bloop output`](./commands.md#bloop-output), [`bloop start`](./commands.md#bloop-start), [`bsp`](./commands.md#bsp), [`clean`](./commands.md#clean), [`compile`](./commands.md#compile), [`config`](./commands.md#config), [`default-file`](./commands.md#default-file), [`dependency-update`](./commands.md#dependency-update), [`directories`](./commands.md#directories), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`help`](./commands.md#help), [`install completions` , `install-completions`](./commands.md#install-completions), [`install-home`](./commands.md#install-home), [`new`](./commands.md#new), [`package`](./commands.md#package), [`pgp create`](./commands.md#pgp-create), [`pgp key-id`](./commands.md#pgp-key-id), [`pgp pull`](./commands.md#pgp-pull), [`pgp push`](./commands.md#pgp-push), [`pgp sign`](./commands.md#pgp-sign), [`pgp verify`](./commands.md#pgp-verify), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`publish setup`](./commands.md#publish-setup), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`github secret create` , `gh secret create`](./commands.md#github-secret-create), [`github secret list` , `gh secret list`](./commands.md#github-secret-list), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test), [`uninstall`](./commands.md#uninstall), [`uninstall completions` , `uninstall-completions`](./commands.md#uninstall-completions), [`update`](./commands.md#update), [`version`](./commands.md#version) +[`add-path`](./commands.md#add-path), [`bloop`](./commands.md#bloop), [`bloop exit`](./commands.md#bloop-exit), [`bloop output`](./commands.md#bloop-output), [`bloop start`](./commands.md#bloop-start), [`bsp`](./commands.md#bsp), [`clean`](./commands.md#clean), [`compile`](./commands.md#compile), [`config`](./commands.md#config), [`default-file`](./commands.md#default-file), [`dependency-update`](./commands.md#dependency-update), [`directories`](./commands.md#directories), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fix`](./commands.md#fix), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`help`](./commands.md#help), [`install completions` , `install-completions`](./commands.md#install-completions), [`install-home`](./commands.md#install-home), [`new`](./commands.md#new), [`package`](./commands.md#package), [`pgp create`](./commands.md#pgp-create), [`pgp key-id`](./commands.md#pgp-key-id), [`pgp pull`](./commands.md#pgp-pull), [`pgp push`](./commands.md#pgp-push), [`pgp sign`](./commands.md#pgp-sign), [`pgp verify`](./commands.md#pgp-verify), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`publish setup`](./commands.md#publish-setup), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`github secret create` , `gh secret create`](./commands.md#github-secret-create), [`github secret list` , `gh secret list`](./commands.md#github-secret-list), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test), [`uninstall`](./commands.md#uninstall), [`uninstall completions` , `uninstall-completions`](./commands.md#uninstall-completions), [`update`](./commands.md#update), [`version`](./commands.md#version) @@ -462,7 +462,7 @@ Print help message, including hidden options, and exit Available in commands: -[`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) +[`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fix`](./commands.md#fix), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) @@ -562,7 +562,7 @@ Add java properties. Note that options equal `-Dproperty=value` are assumed to b Available in commands: -[`bloop`](./commands.md#bloop), [`bloop start`](./commands.md#bloop-start), [`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`config`](./commands.md#config), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`pgp push`](./commands.md#pgp-push), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`publish setup`](./commands.md#publish-setup), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) +[`bloop`](./commands.md#bloop), [`bloop start`](./commands.md#bloop-start), [`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`config`](./commands.md#config), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fix`](./commands.md#fix), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`pgp push`](./commands.md#pgp-push), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`publish setup`](./commands.md#publish-setup), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) @@ -612,7 +612,7 @@ Port for BSP debugging Available in commands: -[`add-path`](./commands.md#add-path), [`bloop`](./commands.md#bloop), [`bloop exit`](./commands.md#bloop-exit), [`bloop output`](./commands.md#bloop-output), [`bloop start`](./commands.md#bloop-start), [`bsp`](./commands.md#bsp), [`clean`](./commands.md#clean), [`compile`](./commands.md#compile), [`config`](./commands.md#config), [`default-file`](./commands.md#default-file), [`dependency-update`](./commands.md#dependency-update), [`directories`](./commands.md#directories), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`help`](./commands.md#help), [`install completions` , `install-completions`](./commands.md#install-completions), [`install-home`](./commands.md#install-home), [`new`](./commands.md#new), [`package`](./commands.md#package), [`pgp pull`](./commands.md#pgp-pull), [`pgp push`](./commands.md#pgp-push), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`publish setup`](./commands.md#publish-setup), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`github secret create` , `gh secret create`](./commands.md#github-secret-create), [`github secret list` , `gh secret list`](./commands.md#github-secret-list), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test), [`uninstall`](./commands.md#uninstall), [`uninstall completions` , `uninstall-completions`](./commands.md#uninstall-completions), [`update`](./commands.md#update), [`version`](./commands.md#version) +[`add-path`](./commands.md#add-path), [`bloop`](./commands.md#bloop), [`bloop exit`](./commands.md#bloop-exit), [`bloop output`](./commands.md#bloop-output), [`bloop start`](./commands.md#bloop-start), [`bsp`](./commands.md#bsp), [`clean`](./commands.md#clean), [`compile`](./commands.md#compile), [`config`](./commands.md#config), [`default-file`](./commands.md#default-file), [`dependency-update`](./commands.md#dependency-update), [`directories`](./commands.md#directories), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fix`](./commands.md#fix), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`help`](./commands.md#help), [`install completions` , `install-completions`](./commands.md#install-completions), [`install-home`](./commands.md#install-home), [`new`](./commands.md#new), [`package`](./commands.md#package), [`pgp pull`](./commands.md#pgp-pull), [`pgp push`](./commands.md#pgp-push), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`publish setup`](./commands.md#publish-setup), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`github secret create` , `gh secret create`](./commands.md#github-secret-create), [`github secret list` , `gh secret list`](./commands.md#github-secret-list), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test), [`uninstall`](./commands.md#uninstall), [`uninstall completions` , `uninstall-completions`](./commands.md#uninstall-completions), [`update`](./commands.md#update), [`version`](./commands.md#version) @@ -650,7 +650,7 @@ List main classes available in the current context Available in commands: -[`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) +[`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fix`](./commands.md#fix), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) @@ -1099,7 +1099,7 @@ Dummy mode - don't upload any secret to GitHub Available in commands: -[`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) +[`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fix`](./commands.md#fix), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) @@ -1202,7 +1202,7 @@ Run Java commands using a manifest-based class path (shortens command length) Available in commands: -[`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) +[`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fix`](./commands.md#fix), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) @@ -1288,7 +1288,7 @@ Whether to run the Scala.js CLI on the JVM or using a native executable Available in commands: -[`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) +[`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fix`](./commands.md#fix), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) @@ -1346,7 +1346,7 @@ Embed resources into the Scala Native binary (can be read with the Java resource Available in commands: -[`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) +[`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fix`](./commands.md#fix), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) @@ -1360,7 +1360,7 @@ Add a `scalac` option. Note that options starting with `-g`, `-language`, `-opt` Available in commands: -[`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) +[`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fix`](./commands.md#fix), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) @@ -1413,7 +1413,7 @@ Aliases: `-n` Available in commands: -[`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) +[`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fix`](./commands.md#fix), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) @@ -1514,7 +1514,7 @@ Force object wrapper for scripts Available in commands: -[`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) +[`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fix`](./commands.md#fix), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) @@ -1563,7 +1563,7 @@ A synonym to --markdown-snippet, which defaults the sub-command to `run` when no Available in commands: -[`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) +[`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fix`](./commands.md#fix), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) @@ -1577,7 +1577,7 @@ Generate BuildInfo for project Available in commands: -[`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) +[`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fix`](./commands.md#fix), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) @@ -1698,7 +1698,7 @@ A github token used to access GitHub. Not needed in most cases. Available in commands: -[`add-path`](./commands.md#add-path), [`bloop`](./commands.md#bloop), [`bloop exit`](./commands.md#bloop-exit), [`bloop output`](./commands.md#bloop-output), [`bloop start`](./commands.md#bloop-start), [`bsp`](./commands.md#bsp), [`clean`](./commands.md#clean), [`compile`](./commands.md#compile), [`config`](./commands.md#config), [`default-file`](./commands.md#default-file), [`dependency-update`](./commands.md#dependency-update), [`directories`](./commands.md#directories), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`help`](./commands.md#help), [`install completions` , `install-completions`](./commands.md#install-completions), [`install-home`](./commands.md#install-home), [`new`](./commands.md#new), [`package`](./commands.md#package), [`pgp pull`](./commands.md#pgp-pull), [`pgp push`](./commands.md#pgp-push), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`publish setup`](./commands.md#publish-setup), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`github secret create` , `gh secret create`](./commands.md#github-secret-create), [`github secret list` , `gh secret list`](./commands.md#github-secret-list), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test), [`uninstall`](./commands.md#uninstall), [`uninstall completions` , `uninstall-completions`](./commands.md#uninstall-completions), [`update`](./commands.md#update), [`version`](./commands.md#version) +[`add-path`](./commands.md#add-path), [`bloop`](./commands.md#bloop), [`bloop exit`](./commands.md#bloop-exit), [`bloop output`](./commands.md#bloop-output), [`bloop start`](./commands.md#bloop-start), [`bsp`](./commands.md#bsp), [`clean`](./commands.md#clean), [`compile`](./commands.md#compile), [`config`](./commands.md#config), [`default-file`](./commands.md#default-file), [`dependency-update`](./commands.md#dependency-update), [`directories`](./commands.md#directories), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fix`](./commands.md#fix), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`help`](./commands.md#help), [`install completions` , `install-completions`](./commands.md#install-completions), [`install-home`](./commands.md#install-home), [`new`](./commands.md#new), [`package`](./commands.md#package), [`pgp pull`](./commands.md#pgp-pull), [`pgp push`](./commands.md#pgp-push), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`publish setup`](./commands.md#publish-setup), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`github secret create` , `gh secret create`](./commands.md#github-secret-create), [`github secret list` , `gh secret list`](./commands.md#github-secret-list), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test), [`uninstall`](./commands.md#uninstall), [`uninstall completions` , `uninstall-completions`](./commands.md#uninstall-completions), [`update`](./commands.md#update), [`version`](./commands.md#version) @@ -1722,7 +1722,7 @@ Enable actionable diagnostics Available in commands: -[`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`publish setup`](./commands.md#publish-setup), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test), [`version`](./commands.md#version) +[`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fix`](./commands.md#fix), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`publish setup`](./commands.md#publish-setup), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test), [`version`](./commands.md#version) @@ -1852,7 +1852,7 @@ Name of BSP Available in commands: -[`bloop`](./commands.md#bloop), [`bloop exit`](./commands.md#bloop-exit), [`bloop start`](./commands.md#bloop-start), [`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`config`](./commands.md#config), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`pgp push`](./commands.md#pgp-push), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`publish setup`](./commands.md#publish-setup), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`github secret create` , `gh secret create`](./commands.md#github-secret-create), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test), [`uninstall`](./commands.md#uninstall) +[`bloop`](./commands.md#bloop), [`bloop exit`](./commands.md#bloop-exit), [`bloop start`](./commands.md#bloop-start), [`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`config`](./commands.md#config), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fix`](./commands.md#fix), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`pgp push`](./commands.md#pgp-push), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`publish setup`](./commands.md#publish-setup), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`github secret create` , `gh secret create`](./commands.md#github-secret-create), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test), [`uninstall`](./commands.md#uninstall) @@ -1905,7 +1905,7 @@ Force overwriting destination files Available in commands: -[`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`publish setup`](./commands.md#publish-setup), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) +[`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fix`](./commands.md#fix), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`publish setup`](./commands.md#publish-setup), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) @@ -2107,7 +2107,7 @@ Available in commands: Available in commands: -[`bsp`](./commands.md#bsp), [`clean`](./commands.md#clean), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`publish setup`](./commands.md#publish-setup), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) +[`bsp`](./commands.md#bsp), [`clean`](./commands.md#clean), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fix`](./commands.md#fix), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`publish setup`](./commands.md#publish-setup), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test) diff --git a/website/docs/reference/commands.md b/website/docs/reference/commands.md index aa2eac6151..4ea9c14c83 100644 --- a/website/docs/reference/commands.md +++ b/website/docs/reference/commands.md @@ -117,6 +117,28 @@ If you encounter any bugs or have feedback to share, make sure to reach out to t Accepts option groups: [compilation server](./cli-options.md#compilation-server-options), [coursier](./cli-options.md#coursier-options), [debug](./cli-options.md#debug-options), [dependency](./cli-options.md#dependency-options), [export](./cli-options.md#export-options), [global suppress warning](./cli-options.md#global-suppress-warning-options), [help group](./cli-options.md#help-group-options), [input](./cli-options.md#input-options), [jvm](./cli-options.md#jvm-options), [logging](./cli-options.md#logging-options), [main class](./cli-options.md#main-class-options), [markdown](./cli-options.md#markdown-options), [python](./cli-options.md#python-options), [Scala.js](./cli-options.md#scalajs-options), [Scala Native](./cli-options.md#scala-native-options), [scalac](./cli-options.md#scalac-options), [scalac extra](./cli-options.md#scalac-extra-options), [shared](./cli-options.md#shared-options), [snippet](./cli-options.md#snippet-options), [source generator](./cli-options.md#source-generator-options), [suppress warning](./cli-options.md#suppress-warning-options), [verbosity](./cli-options.md#verbosity-options), [version](./cli-options.md#version-options), [workspace](./cli-options.md#workspace-options) +## fix + +Perform fixes on a Scala CLI project. + +Specific fix configurations can be specified with both command line options and using directives defined in sources. +Command line options always take priority over using directives when a clash occurs, allowing to override configurations defined in sources. +Using directives can be defined in all supported input source file types. + +Multiple inputs can be passed at once. +Paths to directories, URLs and supported file types are accepted as inputs. +Accepted file extensions: .scala, .sc, .java, .jar, .md, .jar, .c, .h, .zip +For piped inputs use the corresponding alias: _.scala, _.java, _.sc, _.md +All supported types of inputs can be mixed with each other. + +For detailed documentation refer to our website: https://scala-cli.virtuslab.org/docs/commands/fix + +The `fix` sub-command is experimental. +Please bear in mind that non-ideal user experience should be expected. +If you encounter any bugs or have feedback to share, make sure to reach out to the maintenance team at https://github.com/VirtusLab/scala-cli + +Accepts option groups: [compilation server](./cli-options.md#compilation-server-options), [coursier](./cli-options.md#coursier-options), [debug](./cli-options.md#debug-options), [dependency](./cli-options.md#dependency-options), [global suppress warning](./cli-options.md#global-suppress-warning-options), [help group](./cli-options.md#help-group-options), [input](./cli-options.md#input-options), [jvm](./cli-options.md#jvm-options), [logging](./cli-options.md#logging-options), [markdown](./cli-options.md#markdown-options), [python](./cli-options.md#python-options), [Scala.js](./cli-options.md#scalajs-options), [Scala Native](./cli-options.md#scala-native-options), [scalac](./cli-options.md#scalac-options), [scalac extra](./cli-options.md#scalac-extra-options), [shared](./cli-options.md#shared-options), [snippet](./cli-options.md#snippet-options), [source generator](./cli-options.md#source-generator-options), [suppress warning](./cli-options.md#suppress-warning-options), [verbosity](./cli-options.md#verbosity-options), [version](./cli-options.md#version-options), [workspace](./cli-options.md#workspace-options) + ## fmt Aliases: `format`, `scalafmt`