diff --git a/compiler/src/dotty/tools/dotc/Driver.scala b/compiler/src/dotty/tools/dotc/Driver.scala index cb672da1e70b..03b946c74d9f 100644 --- a/compiler/src/dotty/tools/dotc/Driver.scala +++ b/compiler/src/dotty/tools/dotc/Driver.scala @@ -64,9 +64,18 @@ class Driver { protected def sourcesRequired: Boolean = true - def setup(args: Array[String], rootCtx: Context): (List[AbstractFile], Context) = { + protected def command: CompilerCommand = ScalacCommand + + /** Setup context with initialized settings from CLI arguments, then check if there are any settings that + * would change the default behaviour of the compiler. + * + * @return If there is no setting like `-help` preventing us from continuing compilation, + * this method returns a list of files to compile and an updated Context. + * If compilation should be interrupted, this method returns None. + */ + def setup(args: Array[String], rootCtx: Context): Option[(List[AbstractFile], Context)] = { val ictx = rootCtx.fresh - val summary = CompilerCommand.distill(args)(using ictx) + val summary = command.distill(args, ictx.settings)(ictx.settingsState)(using ictx) ictx.setSettings(summary.sstate) MacroClassLoader.init(ictx) Positioned.init(using ictx) @@ -74,9 +83,11 @@ class Driver { inContext(ictx) { if !ctx.settings.YdropComments.value || ctx.mode.is(Mode.ReadComments) then ictx.setProperty(ContextDoc, new ContextDocstrings) - val fileNames = CompilerCommand.checkUsage(summary, sourcesRequired) - val files = fileNames.map(ctx.getFile) - (files, fromTastySetup(files)) + val fileNamesOrNone = command.checkUsage(summary, sourcesRequired)(using ctx.settings)(using ctx.settingsState) + fileNamesOrNone.map { fileNames => + val files = fileNames.map(ctx.getFile) + (files, fromTastySetup(files)) + } } } @@ -182,8 +193,11 @@ class Driver { * if compilation succeeded. */ def process(args: Array[String], rootCtx: Context): Reporter = { - val (files, compileCtx) = setup(args, rootCtx) - doCompile(newCompiler(using compileCtx), files)(using compileCtx) + setup(args, rootCtx) match + case Some((files, compileCtx)) => + doCompile(newCompiler(using compileCtx), files)(using compileCtx) + case None => + rootCtx.reporter } def main(args: Array[String]): Unit = { diff --git a/compiler/src/dotty/tools/dotc/Resident.scala b/compiler/src/dotty/tools/dotc/Resident.scala index d186bf5ec01e..810369ec0f29 100644 --- a/compiler/src/dotty/tools/dotc/Resident.scala +++ b/compiler/src/dotty/tools/dotc/Resident.scala @@ -40,16 +40,21 @@ class Resident extends Driver { final override def process(args: Array[String], rootCtx: Context): Reporter = { @tailrec def loop(args: Array[String], prevCtx: Context): Reporter = { - var (files, ctx) = setup(args, prevCtx) - inContext(ctx) { doCompile(residentCompiler, files) } - var nextCtx = ctx - var line = getLine() - while (line == reset) { - nextCtx = rootCtx - line = getLine() - } - if (line.startsWith(quit)) ctx.reporter - else loop(line split "\\s+", nextCtx) + setup(args, prevCtx) match + case Some((files, ctx)) => + inContext(ctx) { + doCompile(residentCompiler, files) + } + var nextCtx = ctx + var line = getLine() + while (line == reset) { + nextCtx = rootCtx + line = getLine() + } + if (line.startsWith(quit)) ctx.reporter + else loop(line split "\\s+", nextCtx) + case None => + prevCtx.reporter } loop(args, rootCtx) } diff --git a/compiler/src/dotty/tools/dotc/ScalacCommand.scala b/compiler/src/dotty/tools/dotc/ScalacCommand.scala new file mode 100644 index 000000000000..2e0d9a08f25d --- /dev/null +++ b/compiler/src/dotty/tools/dotc/ScalacCommand.scala @@ -0,0 +1,9 @@ +package dotty.tools.dotc + +import config.Properties._ +import config.CompilerCommand + +object ScalacCommand extends CompilerCommand: + override def cmdName: String = "scalac" + override def versionMsg: String = s"Scala compiler $versionString -- $copyrightString" + override def ifErrorsMsg: String = " scalac -help gives more information" diff --git a/compiler/src/dotty/tools/dotc/config/CliCommand.scala b/compiler/src/dotty/tools/dotc/config/CliCommand.scala new file mode 100644 index 000000000000..1a48fbfdf131 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/config/CliCommand.scala @@ -0,0 +1,147 @@ +package dotty.tools.dotc +package config + +import java.nio.file.{Files, Paths} + +import Settings._ +import core.Contexts._ +import Properties._ + +import scala.collection.JavaConverters._ + +trait CliCommand: + + type ConcreteSettings <: CommonScalaSettings with Settings.SettingGroup + + def versionMsg: String + + def ifErrorsMsg: String + + /** The name of the command */ + def cmdName: String + + def isHelpFlag(using settings: ConcreteSettings)(using SettingsState): Boolean + + def helpMsg(using settings: ConcreteSettings)(using SettingsState, Context): String + + private def explainAdvanced = """ + |-- Notes on option parsing -- + |Boolean settings are always false unless set. + |Where multiple values are accepted, they should be comma-separated. + | example: -Xplugin:plugin1,plugin2 + | means one or a comma-separated list of: + | - (partial) phase names with an optional "+" suffix to include the next phase + | - the string "all" + | example: -Xprint:all prints all phases. + | example: -Xprint:typer,mixin prints the typer and mixin phases. + | example: -Ylog:erasure+ logs the erasure phase and the phase after the erasure phase. + | This is useful because during the tree transform of phase X, we often + | already are in phase X + 1. + """ + + /** Distill arguments into summary detailing settings, errors and files to main */ + def distill(args: Array[String], sg: Settings.SettingGroup)(ss: SettingsState = sg.defaultState)(using Context): ArgsSummary = + /** + * Expands all arguments starting with @ to the contents of the + * file named like each argument. + */ + def expandArg(arg: String): List[String] = + def stripComment(s: String) = s takeWhile (_ != '#') + val path = Paths.get(arg stripPrefix "@") + if (!Files.exists(path)) + report.error(s"Argument file ${path.getFileName} could not be found") + Nil + else + val lines = Files.readAllLines(path) // default to UTF-8 encoding + val params = lines.asScala map stripComment mkString " " + CommandLineParser.tokenize(params) + + // expand out @filename to the contents of that filename + def expandedArguments = args.toList flatMap { + case x if x startsWith "@" => expandArg(x) + case x => List(x) + } + + sg.processArguments(expandedArguments, processAll = true, settingsState = ss) + + /** Creates a help message for a subset of options based on cond */ + protected def availableOptionsMsg(cond: Setting[?] => Boolean)(using settings: ConcreteSettings)(using SettingsState): String = + val ss = (settings.allSettings filter cond).toList sortBy (_.name) + val width = (ss map (_.name.length)).max + def format(s: String) = ("%-" + width + "s") format s + def helpStr(s: Setting[?]) = + def defaultValue = s.default match + case _: Int | _: String => s.default.toString + case _ => + // For now, skip the default values that do not make sense for the end user. + // For example 'false' for the version command. + "" + + def formatSetting(name: String, value: String) = + if (value.nonEmpty) + // the format here is helping to make empty padding and put the additional information exactly under the description. + s"\n${format("")} $name: $value." + else + "" + s"${format(s.name)} ${s.description}${formatSetting("Default", defaultValue)}${formatSetting("Choices", s.legalChoices)}" + + ss.map(helpStr).mkString("", "\n", s"\n${format("@")} A text file containing compiler arguments (options and source files).\n") + + protected def shortUsage: String = s"Usage: $cmdName " + + protected def createUsageMsg(label: String, shouldExplain: Boolean, cond: Setting[?] => Boolean)(using settings: ConcreteSettings)(using SettingsState): String = + val prefix = List( + Some(shortUsage), + Some(explainAdvanced) filter (_ => shouldExplain), + Some(label + " options include:") + ).flatten mkString "\n" + + prefix + "\n" + availableOptionsMsg(cond) + + protected def isStandard(s: Setting[?])(using settings: ConcreteSettings)(using SettingsState): Boolean = + !isAdvanced(s) && !isPrivate(s) + protected def isAdvanced(s: Setting[?])(using settings: ConcreteSettings)(using SettingsState): Boolean = + s.name.startsWith("-X") && s.name != "-X" + protected def isPrivate(s: Setting[?])(using settings: ConcreteSettings)(using SettingsState): Boolean = + s.name.startsWith("-Y") && s.name != "-Y" + + /** Messages explaining usage and options */ + protected def usageMessage(using settings: ConcreteSettings)(using SettingsState) = + createUsageMsg("where possible standard", shouldExplain = false, isStandard) + protected def xusageMessage(using settings: ConcreteSettings)(using SettingsState) = + createUsageMsg("Possible advanced", shouldExplain = true, isAdvanced) + protected def yusageMessage(using settings: ConcreteSettings)(using SettingsState) = + createUsageMsg("Possible private", shouldExplain = true, isPrivate) + + protected def phasesMessage: String = + (new Compiler()).phases.map { + case List(single) => single.phaseName + case more => more.map(_.phaseName).mkString("{", ", ", "}") + }.mkString("\n") + + /** Provide usage feedback on argument summary, assuming that all settings + * are already applied in context. + * @return Either Some list of files passed as arguments or None if further processing should be interrupted. + */ + def checkUsage(summary: ArgsSummary, sourcesRequired: Boolean)(using settings: ConcreteSettings)(using SettingsState, Context): Option[List[String]] = + // Print all warnings encountered during arguments parsing + summary.warnings.foreach(report.warning(_)) + + if summary.errors.nonEmpty then + summary.errors foreach (report.error(_)) + report.echo(ifErrorsMsg) + None + else if settings.version.value then + report.echo(versionMsg) + None + else if isHelpFlag then + report.echo(helpMsg) + None + else if (sourcesRequired && summary.arguments.isEmpty) + report.echo(usageMessage) + None + else + Some(summary.arguments) + + extension [T](setting: Setting[T]) + protected def value(using ss: SettingsState): T = setting.valueIn(ss) diff --git a/compiler/src/dotty/tools/dotc/config/CompilerCommand.scala b/compiler/src/dotty/tools/dotc/config/CompilerCommand.scala index 25f41c6b0955..049972365642 100644 --- a/compiler/src/dotty/tools/dotc/config/CompilerCommand.scala +++ b/compiler/src/dotty/tools/dotc/config/CompilerCommand.scala @@ -9,149 +9,16 @@ import Properties._ import scala.collection.JavaConverters._ -object CompilerCommand { - - /** The name of the command */ - def cmdName: String = "scalac" - - private def explainAdvanced = """ - |-- Notes on option parsing -- - |Boolean settings are always false unless set. - |Where multiple values are accepted, they should be comma-separated. - | example: -Xplugin:plugin1,plugin2 - | means one or a comma-separated list of: - | - (partial) phase names with an optional "+" suffix to include the next phase - | - the string "all" - | example: -Xprint:all prints all phases. - | example: -Xprint:typer,mixin prints the typer and mixin phases. - | example: -Ylog:erasure+ logs the erasure phase and the phase after the erasure phase. - | This is useful because during the tree transform of phase X, we often - | already are in phase X + 1. - """ - - def shortUsage: String = s"Usage: $cmdName " - - def versionMsg: String = s"Scala compiler $versionString -- $copyrightString" - - def shouldStopWithInfo(using Context): Boolean = { - val settings = ctx.settings - import settings._ - Set(help, Xhelp, Yhelp, showPlugins, XshowPhases) exists (_.value) - } - - /** Distill arguments into summary detailing settings, errors and files to compiler */ - def distill(args: Array[String])(using Context): ArgsSummary = { - /** - * Expands all arguments starting with @ to the contents of the - * file named like each argument. - */ - def expandArg(arg: String): List[String] = { - def stripComment(s: String) = s takeWhile (_ != '#') - val path = Paths.get(arg stripPrefix "@") - if (!Files.exists(path)) - throw new java.io.FileNotFoundException("argument file %s could not be found" format path.getFileName) - - val lines = Files.readAllLines(path) // default to UTF-8 encoding - - val params = lines.asScala map stripComment mkString " " - CommandLineParser.tokenize(params) - } - - // expand out @filename to the contents of that filename - def expandedArguments = args.toList flatMap { - case x if x startsWith "@" => expandArg(x) - case x => List(x) - } - - ctx.settings.processArguments(expandedArguments, processAll = true) - } - - /** Provide usage feedback on argument summary, assuming that all settings - * are already applied in context. - * @return The list of files to compile. - */ - def checkUsage(summary: ArgsSummary, sourcesRequired: Boolean)(using Context): List[String] = { - val settings = ctx.settings - - /** Creates a help message for a subset of options based on cond */ - def availableOptionsMsg(cond: Setting[?] => Boolean): String = { - val ss = (ctx.settings.allSettings filter cond).toList sortBy (_.name) - val width = (ss map (_.name.length)).max - def format(s: String) = ("%-" + width + "s") format s - def helpStr(s: Setting[?]) = { - def defaultValue = s.default match { - case _: Int | _: String => s.default.toString - case _ => - // For now, skip the default values that do not make sense for the end user. - // For example 'false' for the version command. - "" - } - def formatSetting(name: String, value: String) = - if (value.nonEmpty) - // the format here is helping to make empty padding and put the additional information exactly under the description. - s"\n${format("")} $name: $value." - else - "" - s"${format(s.name)} ${s.description}${formatSetting("Default", defaultValue)}${formatSetting("Choices", s.legalChoices)}" - } - ss map helpStr mkString "\n" - } - - def createUsageMsg(label: String, shouldExplain: Boolean, cond: Setting[?] => Boolean): String = { - val prefix = List( - Some(shortUsage), - Some(explainAdvanced) filter (_ => shouldExplain), - Some(label + " options include:") - ).flatten mkString "\n" - - prefix + "\n" + availableOptionsMsg(cond) - } - - def isStandard(s: Setting[?]): Boolean = !isAdvanced(s) && !isPrivate(s) - def isAdvanced(s: Setting[?]): Boolean = s.name.startsWith("-X") && s.name != "-X" - def isPrivate(s: Setting[?]) : Boolean = s.name.startsWith("-Y") && s.name != "-Y" - - /** Messages explaining usage and options */ - def usageMessage = createUsageMsg("where possible standard", shouldExplain = false, isStandard) - def xusageMessage = createUsageMsg("Possible advanced", shouldExplain = true, isAdvanced) - def yusageMessage = createUsageMsg("Possible private", shouldExplain = true, isPrivate) - - def phasesMessage: String = { - (new Compiler()).phases.map { - case List(single) => single.phaseName - case more => more.map(_.phaseName).mkString("{", ", ", "}") - }.mkString("\n") - } - - def infoMessage: String = { - import settings._ - if (help.value) usageMessage - else if (Xhelp.value) xusageMessage - else if (Yhelp.value) yusageMessage - else if (showPlugins.value) ctx.base.pluginDescriptions - else if (XshowPhases.value) phasesMessage - else "" - } - - // Print all warnings encountered during arguments parsing - summary.warnings.foreach(report.warning(_)) - - if (summary.errors.nonEmpty) { - summary.errors foreach (report.error(_)) - report.echo(" scalac -help gives more information") - Nil - } - else if (settings.version.value) { - report.echo(versionMsg) - Nil - } - else if (shouldStopWithInfo) { - report.echo(infoMessage) - Nil - } - else { - if (sourcesRequired && summary.arguments.isEmpty) report.echo(usageMessage) - summary.arguments - } - } -} +abstract class CompilerCommand extends CliCommand: + type ConcreteSettings = ScalaSettings + + final def helpMsg(using settings: ScalaSettings)(using SettingsState, Context): String = + if (settings.help.value) usageMessage + else if (settings.Xhelp.value) xusageMessage + else if (settings.Yhelp.value) yusageMessage + else if (settings.showPlugins.value) ctx.base.pluginDescriptions + else if (settings.XshowPhases.value) phasesMessage + else "" + + final def isHelpFlag(using settings: ScalaSettings)(using SettingsState): Boolean = + Set(settings.help, settings.Xhelp, settings.Yhelp, settings.showPlugins, settings.XshowPhases) exists (_.value) diff --git a/compiler/src/dotty/tools/dotc/config/PathResolver.scala b/compiler/src/dotty/tools/dotc/config/PathResolver.scala index 5cbb11739d37..e98552a9df28 100644 --- a/compiler/src/dotty/tools/dotc/config/PathResolver.scala +++ b/compiler/src/dotty/tools/dotc/config/PathResolver.scala @@ -144,7 +144,7 @@ object PathResolver { } else inContext(ContextBase().initialCtx) { val ArgsSummary(sstate, rest, errors, warnings) = - ctx.settings.processArguments(args.toList, true) + ctx.settings.processArguments(args.toList, true, ctx.settingsState) errors.foreach(println) val pr = new PathResolver()(using ctx.fresh.setSettings(sstate)) println(" COMMAND: 'scala %s'".format(args.mkString(" "))) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 540eb80b1658..7506976fa212 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -25,6 +25,7 @@ trait CommonScalaSettings { self: Settings.SettingGroup => val color: Setting[String] = ChoiceSetting("-color", "mode", "Colored output", List("always", "never"/*, "auto"*/), "always"/* "auto"*/, aliases = List("--color")) val verbose: Setting[Boolean] = BooleanSetting("-verbose", "Output messages about what the compiler is doing.", aliases = List("--verbose")) val version: Setting[Boolean] = BooleanSetting("-version", "Print product version and exit.", aliases = List("--version")) + val help: Setting[Boolean] = BooleanSetting("-help", "Print a synopsis of standard options.", aliases = List("--help")) val pageWidth: Setting[Int] = IntSetting("-pagewidth", "Set page width", 80, aliases = List("--page-width")) val silentWarnings: Setting[Boolean] = BooleanSetting("-nowarn", "Silence all warnings.", aliases = List("--no-warnings")) @@ -93,7 +94,6 @@ class ScalaSettings extends Settings.SettingGroup with CommonScalaSettings { val explainTypes: Setting[Boolean] = BooleanSetting("-explain-types", "Explain type errors in more detail.", aliases = List("--explain-types")) val explain: Setting[Boolean] = BooleanSetting("-explain", "Explain errors in more detail.", aliases = List("--explain")) val feature: Setting[Boolean] = BooleanSetting("-feature", "Emit warning and location for usages of features that should be imported explicitly.", aliases = List("--feature")) - val help: Setting[Boolean] = BooleanSetting("-help", "Print a synopsis of standard options.", aliases = List("--help")) val release: Setting[String] = ChoiceSetting("-release", "release", "Compile code with classes specific to the given version of the Java platform available on the classpath and emit bytecode for this version.", supportedReleaseVersions, "", aliases = List("--release")) val source: Setting[String] = ChoiceSetting("-source", "source version", "source version", List("3.0", "future", "3.0-migration", "future-migration"), "3.0", aliases = List("--source")) val scalajs: Setting[Boolean] = BooleanSetting("-scalajs", "Compile in Scala.js mode (requires scalajs-library.jar on the classpath).", aliases = List("--scalajs")) diff --git a/compiler/src/dotty/tools/dotc/config/Settings.scala b/compiler/src/dotty/tools/dotc/config/Settings.scala index c5eee24ba6ae..7a0f18ab9e24 100644 --- a/compiler/src/dotty/tools/dotc/config/Settings.scala +++ b/compiler/src/dotty/tools/dotc/config/Settings.scala @@ -244,8 +244,8 @@ object Settings { } } - def processArguments(arguments: List[String], processAll: Boolean)(using Context): ArgsSummary = - processArguments(ArgsSummary(ctx.settingsState, arguments, Nil, Nil), processAll, Nil) + def processArguments(arguments: List[String], processAll: Boolean, settingsState: SettingsState = defaultState): ArgsSummary = + processArguments(ArgsSummary(settingsState, arguments, Nil, Nil), processAll, Nil) def publish[T](settingf: Int => Setting[T]): Setting[T] = { val setting = settingf(_allSettings.length) diff --git a/compiler/src/dotty/tools/dotc/decompiler/IDEDecompilerDriver.scala b/compiler/src/dotty/tools/dotc/decompiler/IDEDecompilerDriver.scala index 630884656c10..3e8e0f5fb86d 100644 --- a/compiler/src/dotty/tools/dotc/decompiler/IDEDecompilerDriver.scala +++ b/compiler/src/dotty/tools/dotc/decompiler/IDEDecompilerDriver.scala @@ -19,7 +19,7 @@ class IDEDecompilerDriver(val settings: List[String]) extends dotc.Driver { val rootCtx = initCtx.fresh.addMode(Mode.Interactive | Mode.ReadPositions | Mode.ReadComments) rootCtx.setSetting(rootCtx.settings.YretainTrees, true) rootCtx.setSetting(rootCtx.settings.fromTasty, true) - val ctx = setup(settings.toArray :+ "dummy.scala", rootCtx)._2 + val ctx = setup(settings.toArray :+ "dummy.scala", rootCtx).get._2 ctx.initialize()(using ctx) ctx } diff --git a/compiler/src/dotty/tools/dotc/decompiler/Main.scala b/compiler/src/dotty/tools/dotc/decompiler/Main.scala index e386abfa7bf8..3cc94f782793 100644 --- a/compiler/src/dotty/tools/dotc/decompiler/Main.scala +++ b/compiler/src/dotty/tools/dotc/decompiler/Main.scala @@ -18,7 +18,7 @@ object Main extends dotc.Driver { new TASTYDecompiler } - override def setup(args0: Array[String], rootCtx: Context): (List[AbstractFile], Context) = { + override def setup(args0: Array[String], rootCtx: Context): Option[(List[AbstractFile], Context)] = { var args = args0.filter(a => a != "-decompile") if (!args.contains("-from-tasty")) args = "-from-tasty" +: args if (args.contains("-d")) args = "-color:never" +: args diff --git a/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala b/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala index cb2ada52616e..aa45b2080ee9 100644 --- a/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala +++ b/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala @@ -32,7 +32,9 @@ class InteractiveDriver(val settings: List[String]) extends Driver { val rootCtx = initCtx.fresh.addMode(Mode.ReadPositions).addMode(Mode.Interactive).addMode(Mode.ReadComments) rootCtx.setSetting(rootCtx.settings.YretainTrees, true) rootCtx.setSetting(rootCtx.settings.YcookComments, true) - val ctx = setup(settings.toArray, rootCtx)._2 + val ctx = setup(settings.toArray, rootCtx) match + case Some((_, ctx)) => ctx + case None => rootCtx ctx.initialize()(using ctx) ctx } diff --git a/compiler/src/dotty/tools/repl/Main.scala b/compiler/src/dotty/tools/repl/Main.scala index 725395dcdb3c..127ccd10b467 100644 --- a/compiler/src/dotty/tools/repl/Main.scala +++ b/compiler/src/dotty/tools/repl/Main.scala @@ -3,5 +3,5 @@ package dotty.tools.repl /** Main entry point to the REPL */ object Main { def main(args: Array[String]): Unit = - new ReplDriver(args).runUntilQuit() + new ReplDriver(args).tryRunning } diff --git a/compiler/src/dotty/tools/repl/ReplCommand.scala b/compiler/src/dotty/tools/repl/ReplCommand.scala new file mode 100644 index 000000000000..3e46106acc2c --- /dev/null +++ b/compiler/src/dotty/tools/repl/ReplCommand.scala @@ -0,0 +1,9 @@ +package dotty.tools.repl + +import dotty.tools.dotc.config.Properties._ +import dotty.tools.dotc.config.CompilerCommand + +object ReplCommand extends CompilerCommand: + override def cmdName: String = "scala" + override def versionMsg: String = s"Scala code runner $versionString -- $copyrightString" + override def ifErrorsMsg: String = " scala -help gives more information" diff --git a/compiler/src/dotty/tools/repl/ReplDriver.scala b/compiler/src/dotty/tools/repl/ReplDriver.scala index 966eb2107fb5..7ad47f6a8cfd 100644 --- a/compiler/src/dotty/tools/repl/ReplDriver.scala +++ b/compiler/src/dotty/tools/repl/ReplDriver.scala @@ -22,6 +22,7 @@ import dotty.tools.dotc.reporting.{Message, Diagnostic} import dotty.tools.dotc.util.Spans.Span import dotty.tools.dotc.util.{SourceFile, SourcePosition} import dotty.tools.dotc.{CompilationUnit, Driver} +import dotty.tools.dotc.config.CompilerCommand import dotty.tools.io._ import org.jline.reader._ @@ -67,9 +68,14 @@ class ReplDriver(settings: Array[String], private def initialCtx = { val rootCtx = initCtx.fresh.addMode(Mode.ReadPositions | Mode.Interactive | Mode.ReadComments) rootCtx.setSetting(rootCtx.settings.YcookComments, true) - val ictx = setup(settings, rootCtx)._2 - ictx.base.initialize()(using ictx) - ictx + setup(settings, rootCtx) match + case Some((files, ictx)) => + shouldStart = true + ictx.base.initialize()(using ictx) + ictx + case None => + shouldStart = false + rootCtx } /** the initial, empty state of the REPL session */ @@ -91,6 +97,7 @@ class ReplDriver(settings: Array[String], } private var rootCtx: Context = _ + private var shouldStart: Boolean = _ private var compiler: ReplCompiler = _ private var rendering: Rendering = _ @@ -98,6 +105,14 @@ class ReplDriver(settings: Array[String], // is called, we're in business resetToInitial() + override protected def command: CompilerCommand = ReplCommand + + /** Try to run REPL if there is nothing that prevents us doing so. + * + * Possible reason for unsuccessful run are raised flags in CLI like --help or --version + */ + final def tryRunning = if shouldStart then runUntilQuit() + /** Run REPL with `state` until `:quit` command found * * This method is the main entry point into the REPL. Its effects are not diff --git a/compiler/src/dotty/tools/scripting/ScriptingDriver.scala b/compiler/src/dotty/tools/scripting/ScriptingDriver.scala index a23840b9f8e0..af0858acf658 100644 --- a/compiler/src/dotty/tools/scripting/ScriptingDriver.scala +++ b/compiler/src/dotty/tools/scripting/ScriptingDriver.scala @@ -19,28 +19,30 @@ import sys.process._ class ScriptingDriver(compilerArgs: Array[String], scriptFile: File, scriptArgs: Array[String]) extends Driver: def compileAndRun(pack:(Path, String, String) => Boolean = null): Unit = val outDir = Files.createTempDirectory("scala3-scripting") - val (toCompile, rootCtx) = setup(compilerArgs :+ scriptFile.getAbsolutePath, initCtx.fresh) - given Context = rootCtx.fresh.setSetting(rootCtx.settings.outputDir, - new PlainDirectory(Directory(outDir))) - - if doCompile(newCompiler, toCompile).hasErrors then - throw ScriptingException("Errors encountered during compilation") - - try - val (mainClass, mainMethod) = detectMainClassAndMethod(outDir, ctx.settings.classpath.value, scriptFile) - val invokeMain: Boolean = - Option(pack) match - case Some(func) => - func(outDir, ctx.settings.classpath.value, mainClass) - case None => - true - end match - if invokeMain then mainMethod.invoke(null, scriptArgs) - catch - case e: java.lang.reflect.InvocationTargetException => - throw e.getCause - finally - deleteFile(outDir.toFile) + setup(compilerArgs :+ scriptFile.getAbsolutePath, initCtx.fresh) match + case Some((toCompile, rootCtx)) => + given Context = rootCtx.fresh.setSetting(rootCtx.settings.outputDir, + new PlainDirectory(Directory(outDir))) + + if doCompile(newCompiler, toCompile).hasErrors then + throw ScriptingException("Errors encountered during compilation") + + try + val (mainClass, mainMethod) = detectMainClassAndMethod(outDir, ctx.settings.classpath.value, scriptFile) + val invokeMain: Boolean = + Option(pack) match + case Some(func) => + func(outDir, ctx.settings.classpath.value, mainClass) + case None => + true + end match + if invokeMain then mainMethod.invoke(null, scriptArgs) + catch + case e: java.lang.reflect.InvocationTargetException => + throw e.getCause + finally + deleteFile(outDir.toFile) + case None => end compileAndRun private def deleteFile(target: File): Unit = diff --git a/compiler/test/dotty/tools/dotc/ScalaCommandTest.scala b/compiler/test/dotty/tools/dotc/ScalaCommandTest.scala new file mode 100644 index 000000000000..f3d12d7b7bba --- /dev/null +++ b/compiler/test/dotty/tools/dotc/ScalaCommandTest.scala @@ -0,0 +1,44 @@ +package dotty.tools.dotc + +import org.junit.Test +import org.junit.Assert._ +import org.junit.Rule +import org.junit.rules.TemporaryFolder +import dotty.tools.dotc.config.Settings._ +import core.Contexts.{Context, ContextBase} + +class ScalaCommandTest: + + private val _temporaryFolder = new TemporaryFolder + + @Rule + def temporaryFolder = _temporaryFolder + + @Test def `Simple one parameter`: Unit = inContext { + val settings = config.ScalaSettings() + val args = "-cp path/to/classes1:other/path/to/classes2 files".split(" ") + val summary = ScalacCommand.distill(args, settings)() + given SettingsState = summary.sstate + assertEquals("path/to/classes1:other/path/to/classes2", settings.classpath.value) + assertEquals("files" :: Nil, summary.arguments) + } + + @Test def `Unfold @file`: Unit = inContext { + val settings = config.ScalaSettings() + val file = temporaryFolder.newFile("config") + val writer = java.io.FileWriter(file); + writer.write("-sourceroot myNewRoot someMoreFiles"); + writer.close(); + val args = s"-cp path/to/classes1:other/path/to/classes2 @${file} someFiles".split(" ") + val summary = ScalacCommand.distill(args, settings)() + + given SettingsState = summary.sstate + assertEquals("path/to/classes1:other/path/to/classes2", settings.classpath.value) + assertEquals("myNewRoot", settings.sourceroot.value) + assertEquals("someMoreFiles" :: "someFiles" :: Nil, summary.arguments) + } + + private def inContext(f: Context ?=> Unit) = f(using (new ContextBase).initialCtx.fresh) + + extension [T](setting: Setting[T]) + private def value(using ss: SettingsState): T = setting.valueIn(ss) diff --git a/compiler/test/dotty/tools/dotc/SettingsTests.scala b/compiler/test/dotty/tools/dotc/SettingsTests.scala index dc1308ce6cb4..5303fb070c7a 100644 --- a/compiler/test/dotty/tools/dotc/SettingsTests.scala +++ b/compiler/test/dotty/tools/dotc/SettingsTests.scala @@ -49,6 +49,7 @@ class SettingsTests { val args = List("-foo", "b", "-bar", "1") val summary = Settings.processArguments(args, true) assertTrue(summary.errors.isEmpty) + given SettingsState = summary.sstate assertEquals("b", Settings.foo.value) assertEquals(1, Settings.bar.value) } @@ -66,6 +67,7 @@ class SettingsTests { val args = List("-foo", "b", "-bar", "1", "-baz", "5") val summary = Settings.processArguments(args, true) assertTrue(summary.errors.isEmpty) + given SettingsState = summary.sstate assertEquals("b", Settings.foo.value) assertEquals(1, Settings.bar.value) assertEquals(5, Settings.baz.value) @@ -75,6 +77,7 @@ class SettingsTests { val args = List("-foo:b") val summary = Settings.processArguments(args, true) assertTrue(summary.errors.isEmpty) + given SettingsState = summary.sstate assertEquals("b", Settings.foo.value) } @@ -107,4 +110,7 @@ class SettingsTests { } private def inContext(f: Context ?=> Unit) = f(using (new ContextBase).initialCtx.fresh) + + extension [T](setting: Setting[T]) + private def value(using ss: SettingsState): T = setting.valueIn(ss) } diff --git a/compiler/test/dotty/tools/dotc/core/tasty/CommentPicklingTest.scala b/compiler/test/dotty/tools/dotc/core/tasty/CommentPicklingTest.scala index 28779fe9b697..361d5a00b880 100644 --- a/compiler/test/dotty/tools/dotc/core/tasty/CommentPicklingTest.scala +++ b/compiler/test/dotty/tools/dotc/core/tasty/CommentPicklingTest.scala @@ -107,7 +107,7 @@ class CommentPicklingTest { private class UnpicklingDriver extends Driver { override def initCtx = super.initCtx.addMode(Mode.ReadComments) def unpickle[T](args: Array[String], files: List[File])(fn: (List[tpd.Tree], Context) => T): T = { - implicit val (_, ctx: Context) = setup(args, initCtx) + implicit val ctx: Context = setup(args, initCtx).map(_._2).getOrElse(initCtx) ctx.initialize() val trees = files.flatMap { f => val unpickler = new DottyUnpickler(f.toByteArray()) diff --git a/dist/bin/scala b/dist/bin/scala index baab654f3012..bfcd30c14cd0 100755 --- a/dist/bin/scala +++ b/dist/bin/scala @@ -143,7 +143,6 @@ elif [ $execute_repl == true ] || ([ $execute_run == false ] && [ $options_indic if [ "$CLASS_PATH" ]; then cp_arg="-classpath \"$CLASS_PATH\"" fi - echo "Starting scala3 REPL..." eval "\"$PROG_HOME/bin/scalac\" ${cp_arg-} ${java_options[@]} -repl ${residual_args[@]}" scala_exit_status=$? elif [ $execute_repl == true ] || [ ${#residual_args[@]} -ne 0 ]; then diff --git a/dist/bin/scalac b/dist/bin/scalac index 6373478eb7c9..f81c20853bd2 100755 --- a/dist/bin/scalac +++ b/dist/bin/scalac @@ -83,7 +83,6 @@ in_scripting_args=false while [[ $# -gt 0 ]]; do case "$1" in --) shift; for arg; do addResidual "$arg"; done; set -- ;; - -h|-help) help=true && shift ;; -v|-verbose) verbose=true && addScala "-verbose" && shift ;; -debug) DEBUG="$DEBUG_STR" && shift ;; -q|-quiet) quiet=true && shift ;; diff --git a/dist/bin/scaladoc b/dist/bin/scaladoc index 792de1fe2201..b536dbf99e4e 100755 --- a/dist/bin/scaladoc +++ b/dist/bin/scaladoc @@ -102,7 +102,6 @@ in_scripting_args=false while [[ $# -gt 0 ]]; do case "$1" in --) shift; for arg; do addResidual "$arg"; done; set -- ;; - -h|-help) help=true && shift ;; -v|-verbose) verbose=true && addScala "-verbose" && shift ;; -debug) DEBUG="$DEBUG_STR" && shift ;; -q|-quiet) quiet=true && shift ;; diff --git a/sbt-bridge/src/dotty/tools/xsbt/CompilerBridgeDriver.java b/sbt-bridge/src/dotty/tools/xsbt/CompilerBridgeDriver.java index f73256a7ddde..8a7f3ae2d22e 100644 --- a/sbt-bridge/src/dotty/tools/xsbt/CompilerBridgeDriver.java +++ b/sbt-bridge/src/dotty/tools/xsbt/CompilerBridgeDriver.java @@ -7,7 +7,7 @@ import dotty.tools.dotc.Compiler; import dotty.tools.dotc.Driver; -import dotty.tools.dotc.config.CompilerCommand; +import dotty.tools.dotc.ScalacCommand; import dotty.tools.dotc.config.Properties; import dotty.tools.dotc.core.Contexts; import dotty.tools.io.AbstractFile; @@ -60,9 +60,9 @@ synchronized public void run(VirtualFile[] sources, AnalysisCallback callback, L .setReporter(reporter) .setSbtCallback(callback); - Contexts.Context context = setup(args, initialCtx)._2; + Contexts.Context context = setup(args, initialCtx).map(t -> t._2).getOrElse(() -> initialCtx); - if (CompilerCommand.shouldStopWithInfo(context)) { + if (ScalacCommand.isHelpFlag(context.settings(), context.settingsState())) { throw new InterfaceCompileFailed(args, new Problem[0], StopInfoError); } diff --git a/scaladoc-testcases/src/example/Inheritance.scala b/scaladoc-testcases/src/example/Inheritance.scala index 95053b9ce752..cfe2c2281900 100644 --- a/scaladoc-testcases/src/example/Inheritance.scala +++ b/scaladoc-testcases/src/example/Inheritance.scala @@ -19,3 +19,5 @@ class A: class D extends C class C extends A + +class D extends ``.ToplevelClass diff --git a/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala b/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala index 8f483f777848..1e900902c24f 100644 --- a/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala +++ b/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala @@ -11,6 +11,7 @@ import java.nio.file.Files import dotty.tools.dotc.config.Settings._ import dotty.tools.dotc.config.CommonScalaSettings import dotty.tools.dotc.reporting.Reporter +import dotty.tools.dotc.core.Contexts._ object Scaladoc: enum CommentSyntax: @@ -45,27 +46,136 @@ object Scaladoc: ) def run(args: Array[String], rootContext: CompilerContext): Reporter = - val (parsedArgs, ctx) = ScaladocArgs.extract(args.toList, rootContext) - given CompilerContext = ctx + val (parsedArgsOrNone, ctx) = extract(args, rootContext) - def listTastyFiles(f: File): Seq[File] = - val (files, dirs) = Option(f.listFiles()).toArray.flatten.partition(_.isFile) - ArraySeq.unsafeWrapArray( - files.filter(_.getName.endsWith(".tasty")) ++ dirs.flatMap(listTastyFiles) - ) - val tastyFiles = parsedArgs.tastyFiles ++ parsedArgs.tastyDirs.flatMap(listTastyFiles) + parsedArgsOrNone.map { parsedArgs => + given CompilerContext = ctx + + def listTastyFiles(f: File): Seq[File] = + val (files, dirs) = Option(f.listFiles()).toArray.flatten.partition(_.isFile) + ArraySeq.unsafeWrapArray( + files.filter(_.getName.endsWith(".tasty")) ++ dirs.flatMap(listTastyFiles) + ) + val tastyFiles = parsedArgs.tastyFiles ++ parsedArgs.tastyDirs.flatMap(listTastyFiles) + + if !ctx.reporter.hasErrors then + val updatedArgs = parsedArgs.copy(tastyDirs = Nil, tastyFiles = tastyFiles) - if !ctx.reporter.hasErrors then - val updatedArgs = parsedArgs.copy(tastyDirs = Nil, tastyFiles = tastyFiles) + if (parsedArgs.output.exists()) util.IO.delete(parsedArgs.output) - if (parsedArgs.output.exists()) util.IO.delete(parsedArgs.output) + run(updatedArgs) + report.inform("Done") + else report.error("Failure") - run(updatedArgs) - report.inform("Done") - else report.error("Failure") + } ctx.reporter + def extract(args: Array[String], rootCtx: CompilerContext): (Option[Scaladoc.Args], CompilerContext) = + val newContext = rootCtx.fresh + given CompilerContext = newContext + val ss = ScaladocSettings() + import ss._ + val summary = ScaladocCommand.distill(args, ss)() + val argumentFilesOrNone = ScaladocCommand.checkUsage(summary, true)(using ss)(using summary.sstate) + + extension[T](arg: Setting[T]) + def get = arg.valueIn(summary.sstate) + def withDefault(default: => T) = + if arg.get == arg.default then default else arg.get + def nonDefault = + if arg.get == arg.default then None else Some(arg.get) + + def setInGlobal[T](s: Setting[T]) = + s.nonDefault.foreach { newValue => + newContext.settings.allSettings.find(_ == s).fold( + report.warning(s"Unable to set ${s.name} in global context") + )(s => newContext.setSetting(s.asInstanceOf[Setting[T]], newValue)) + } + + allSettings.filterNot(scaladocSpecificSettings.contains).foreach(setInGlobal) + + summary.warnings.foreach(report.warning(_)) + summary.errors.foreach(report.error(_)) + + def parseTastyRoots(roots: String) = + roots.split(File.pathSeparatorChar).toList.map(new File(_)) + + argumentFilesOrNone.fold((None, newContext)) { argumentFiles => + val inFiles = argumentFiles.map(File(_)).filter(_.getName != "___fake___.scala") + val (existing, nonExisting) = inFiles.partition(_.exists) + + if nonExisting.nonEmpty then report.warning( + s"scaladoc will ignore following non-existent paths: ${nonExisting.mkString(", ")}" + ) + + val (dirs, files) = existing.partition(_.isDirectory) + val (validFiles, other) = files.partition(f => + f.getName.endsWith(".tasty") || f.getName.endsWith(".jar") + ) + + if other.nonEmpty then report.warning( + s"scaladoc suports only .tasty and .jar files, following files will be ignored: ${other.mkString(", ")}" + ) + + def defaultDest(): File = + report.warning("Destination is not provided, please provide '-d' parameter pointing to directory where docs should be created") + File("output") + + val parseSyntax: CommentSyntax = syntax.nonDefault.fold(CommentSyntax.default){ str => + CommentSyntax.parse(str).getOrElse{ + report.error(s"unrecognized value for -syntax option: $str") + CommentSyntax.default + } + } + val externalMappings = + externalDocumentationMappings.get.flatMap( s => + ExternalDocLink.parse(s).fold(left => { + report.warning(left) + None + }, right => Some(right) + ) + ) + + val socialLinksParsed = + socialLinks.get.flatMap { s => + SocialLinks.parse(s).fold(left => { + report.warning(left) + None + },right => Some(right)) + } + + unsupportedSettings.filter(s => s.get != s.default).foreach { s => + report.warning(s"Setting ${s.name} is currently not supported.") + } + val destFile = outputDir.nonDefault.fold(defaultDest())(_.file) + val printableProjectName = projectName.nonDefault.fold("")("for " + _ ) + report.inform( + s"Generating documenation $printableProjectName in $destFile") + + if deprecatedSkipPackages.get.nonEmpty then report.warning(deprecatedSkipPackages.description) + + val docArgs = Args( + projectName.withDefault("root"), + dirs, + validFiles, + classpath.get, + destFile, + siteRoot.nonDefault, + projectVersion.nonDefault, + projectLogo.nonDefault, + parseSyntax, + sourceLinks.get, + revision.nonDefault, + externalMappings, + socialLinksParsed, + skipById.get ++ deprecatedSkipPackages.get, + skipByRegex.get, + docRootContent.nonDefault + ) + (Some(docArgs), newContext) + } + private [scaladoc] def run(args: Args)(using ctx: CompilerContext): DocContext = given docContext: DocContext = new DocContext(args, ctx) val module = ScalaModuleProvider.mkModule() @@ -73,4 +183,3 @@ object Scaladoc: new dotty.tools.scaladoc.renderers.HtmlRenderer(module.rootPackage, module.members).render() report.inform("generation completed successfully") docContext - diff --git a/scaladoc/src/dotty/tools/scaladoc/ScaladocArgs.scala b/scaladoc/src/dotty/tools/scaladoc/ScaladocArgs.scala deleted file mode 100644 index 9472173e2f88..000000000000 --- a/scaladoc/src/dotty/tools/scaladoc/ScaladocArgs.scala +++ /dev/null @@ -1,163 +0,0 @@ -package dotty.tools.scaladoc - -import java.util.ServiceLoader -import java.io.File -import java.util.jar._ -import collection.JavaConverters._ -import collection.immutable.ArraySeq - -import java.nio.file.Files - -import dotty.tools.dotc.config.Settings._ -import dotty.tools.dotc.config.CommonScalaSettings -import dotty.tools.scaladoc.Scaladoc._ - -class ScaladocArgs extends SettingGroup with CommonScalaSettings: - val unsupportedSettings = Seq( - // Options that we like to support - bootclasspath, extdirs, javabootclasspath, encoding, usejavacp, - // Needed for plugin architecture - plugin,disable,require, pluginsDir, pluginOptions, - // we need support for sourcepath and sourceroot - sourcepath, sourceroot - ) - - val sourceLinks: Setting[List[String]] = - MultiStringSetting("-source-links", "sources", SourceLinks.usage) - - val syntax: Setting[String] = - StringSetting("-comment-syntax", "syntax", "Syntax of the comment used", "") - - val revision: Setting[String] = - StringSetting("-revision", "revision", "Revision (branch or ref) used to build project project", "") - - val externalDocumentationMappings: Setting[List[String]] = - MultiStringSetting("-external-mappings", "external-mappings", - "Mapping between regexes matching classpath entries and external documentation. " + - "'regex::[scaladoc|scaladoc|javadoc]::path' syntax is used") - - val socialLinks: Setting[List[String]] = - MultiStringSetting("-social-links", "social-links", - "Links to social sites. '[github|twitter|gitter|discord]::link' syntax is used. " + - "'custom::link::white_icon_name::black_icon_name' is also allowed, in this case icons must be present in 'images/'' directory.") - - val deprecatedSkipPackages: Setting[List[String]] = - MultiStringSetting("-skip-packages", "packages", "Deprecated, please use `-skip-by-id` or `-skip-by-regex`") - - val skipById: Setting[List[String]] = - MultiStringSetting("-skip-by-id", "package or class identifier", "Identifiers of packages or top-level classes to skip when generating documentation") - - val skipByRegex: Setting[List[String]] = - MultiStringSetting("-skip-by-regex", "regex", "Regexes that match fully qualified names of packages or top-level classes to skip when generating documentation") - - val docRootContent: Setting[String] = - StringSetting("-doc-root-content", "path", "The file from which the root package documentation should be imported.", "") - - def scaladocSpecificSettings: Set[Setting[_]] = - Set(sourceLinks, syntax, revision, externalDocumentationMappings, socialLinks, skipById, skipByRegex, deprecatedSkipPackages, docRootContent) - -object ScaladocArgs: - def extract(args: List[String], rootCtx: CompilerContext):(Scaladoc.Args, CompilerContext) = - val inst = new ScaladocArgs - import inst._ - val initialSummary = - ArgsSummary(defaultState, args, errors = Nil, warnings = Nil) - val summary = - processArguments(initialSummary, processAll = true, skipped = Nil) - val newContext = rootCtx.fresh - - extension[T](arg: Setting[T]) - def get = arg.valueIn(summary.sstate) - def withDefault(default: => T) = - if arg.get == arg.default then default else arg.get - def nonDefault = - if arg.get == arg.default then None else Some(arg.get) - - def setInGlobal[T](s: Setting[T]) = - s.nonDefault.foreach { newValue => - newContext.settings.allSettings.find(_ == s).fold( - report.warning(s"Unable to set ${s.name} in global context") - )(s => newContext.setSetting(s.asInstanceOf[Setting[T]], newValue)) - } - - allSettings.filterNot(scaladocSpecificSettings.contains).foreach(setInGlobal) - - given CompilerContext = newContext - summary.warnings.foreach(report.warning(_)) - summary.errors.foreach(report.error(_)) - - def parseTastyRoots(roots: String) = - roots.split(File.pathSeparatorChar).toList.map(new File(_)) - - val inFiles = summary.arguments.map(File(_)).filter(_.getName != "___fake___.scala") - val (existing, nonExisting) = inFiles.partition(_.exists) - - if nonExisting.nonEmpty then report.warning( - s"scaladoc will ignore following non-existent paths: ${nonExisting.mkString(", ")}" - ) - - val (dirs, files) = existing.partition(_.isDirectory) - val (validFiles, other) = files.partition(f => - f.getName.endsWith(".tasty") || f.getName.endsWith(".jar") - ) - - if other.nonEmpty then report.warning( - s"scaladoc suports only .tasty and .jar files, following files will be ignored: ${other.mkString(", ")}" - ) - - def defaultDest(): File = - report.error("Destination is not provided, please provide '-d' parameter pointing to directory where docs should be created") - File("output") - - val parseSyntax = syntax.nonDefault.fold(CommentSyntax.default){ str => - CommentSyntax.parse(str).getOrElse{ - report.error(s"unrecognized value for -syntax option: $str") - CommentSyntax.default - } - } - val externalMappings = - externalDocumentationMappings.get.flatMap( s => - ExternalDocLink.parse(s).fold(left => { - report.warning(left) - None - }, right => Some(right) - ) - ) - - val socialLinksParsed = - socialLinks.get.flatMap { s => - SocialLinks.parse(s).fold(left => { - report.warning(left) - None - },right => Some(right)) - } - - unsupportedSettings.filter(s => s.get != s.default).foreach { s => - report.warning(s"Setting ${s.name} is currently not supported.") - } - val destFile = outputDir.nonDefault.fold(defaultDest())(_.file) - val printableProjectName = projectName.nonDefault.fold("")("for " + _ ) - report.inform( - s"Generating documenation $printableProjectName in $destFile") - - if deprecatedSkipPackages.get.nonEmpty then report.warning(deprecatedSkipPackages.description) - - val docArgs = Args( - projectName.withDefault("root"), - dirs, - validFiles, - classpath.get, - destFile, - siteRoot.nonDefault, - projectVersion.nonDefault, - projectLogo.nonDefault, - parseSyntax, - sourceLinks.get, - revision.nonDefault, - externalMappings, - socialLinksParsed, - skipById.get ++ deprecatedSkipPackages.get, - skipByRegex.get, - docRootContent.nonDefault - ) - (docArgs, newContext) diff --git a/scaladoc/src/dotty/tools/scaladoc/ScaladocCommand.scala b/scaladoc/src/dotty/tools/scaladoc/ScaladocCommand.scala new file mode 100644 index 000000000000..de2d95dc457a --- /dev/null +++ b/scaladoc/src/dotty/tools/scaladoc/ScaladocCommand.scala @@ -0,0 +1,30 @@ +package dotty.tools.scaladoc + +import java.util.ServiceLoader +import java.io.File +import java.util.jar._ +import collection.JavaConverters._ +import collection.immutable.ArraySeq + +import java.nio.file.Files + +import dotty.tools.dotc.config.Settings._ +import dotty.tools.dotc.config.CommonScalaSettings +import dotty.tools.scaladoc.Scaladoc._ +import dotty.tools.dotc.config.Settings.Setting.value +import dotty.tools.dotc.config.Properties._ +import dotty.tools.dotc.config.CliCommand +import dotty.tools.dotc.core.Contexts._ + +object ScaladocCommand extends CliCommand: + type ConcreteSettings = ScaladocSettings + override def cmdName: String = "scaladoc" + override def versionMsg: String = s"Scaladoc $versionString -- $copyrightString" + override def ifErrorsMsg: String = " scaladoc -help gives more information" + + def helpMsg(using settings: ScaladocSettings)(using SettingsState, Context): String = + if (settings.help.value) usageMessage + else "" + + def isHelpFlag(using settings: ScaladocSettings)(using SettingsState): Boolean = + Set(settings.help) exists (_.value) diff --git a/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala b/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala new file mode 100644 index 000000000000..701e5f551e8e --- /dev/null +++ b/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala @@ -0,0 +1,61 @@ +package dotty.tools.scaladoc + +import java.util.ServiceLoader +import java.io.File +import java.util.jar._ +import collection.JavaConverters._ +import collection.immutable.ArraySeq + +import java.nio.file.Files + +import dotty.tools.dotc.config.Settings._ +import dotty.tools.dotc.config.CommonScalaSettings +import dotty.tools.scaladoc.Scaladoc._ +import dotty.tools.dotc.config.Settings.Setting.value +import dotty.tools.dotc.config.Properties._ +import dotty.tools.dotc.config.CliCommand +import dotty.tools.dotc.core.Contexts._ + +class ScaladocSettings extends SettingGroup with CommonScalaSettings: + val unsupportedSettings = Seq( + // Options that we like to support + bootclasspath, extdirs, javabootclasspath, encoding, usejavacp, + // Needed for plugin architecture + plugin,disable,require, pluginsDir, pluginOptions, + // we need support for sourcepath and sourceroot + sourcepath, sourceroot + ) + + val sourceLinks: Setting[List[String]] = + MultiStringSetting("-source-links", "sources", SourceLinks.usage) + + val syntax: Setting[String] = + StringSetting("-comment-syntax", "syntax", "Syntax of the comment used", "") + + val revision: Setting[String] = + StringSetting("-revision", "revision", "Revision (branch or ref) used to build project project", "") + + val externalDocumentationMappings: Setting[List[String]] = + MultiStringSetting("-external-mappings", "external-mappings", + "Mapping between regexes matching classpath entries and external documentation. " + + "'regex::[scaladoc|scaladoc|javadoc]::path' syntax is used") + + val socialLinks: Setting[List[String]] = + MultiStringSetting("-social-links", "social-links", + "Links to social sites. '[github|twitter|gitter|discord]::link' syntax is used. " + + "'custom::link::white_icon_name::black_icon_name' is also allowed, in this case icons must be present in 'images/'' directory.") + + val deprecatedSkipPackages: Setting[List[String]] = + MultiStringSetting("-skip-packages", "packages", "Deprecated, please use `-skip-by-id` or `-skip-by-regex`") + + val skipById: Setting[List[String]] = + MultiStringSetting("-skip-by-id", "package or class identifier", "Identifiers of packages or top-level classes to skip when generating documentation") + + val skipByRegex: Setting[List[String]] = + MultiStringSetting("-skip-by-regex", "regex", "Regexes that match fully qualified names of packages or top-level classes to skip when generating documentation") + + val docRootContent: Setting[String] = + StringSetting("-doc-root-content", "path", "The file from which the root package documentation should be imported.", "") + + def scaladocSpecificSettings: Set[Setting[_]] = + Set(sourceLinks, syntax, revision, externalDocumentationMappings, socialLinks, skipById, skipByRegex, deprecatedSkipPackages, docRootContent) diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/Locations.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/Locations.scala index 05ba195f8e02..f3d67e2f8466 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/Locations.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/Locations.scala @@ -34,7 +34,8 @@ trait Locations(using ctx: DocContext): case dri => val loc = dri.location val fqn = loc.split(Array('.')).toList match - case List("") => List("index") + case "" :: Nil => "index" :: Nil + case "" :: tail => "_empty_" :: tail case other => other Seq("api") ++ fqn diff --git a/staging/src/scala/quoted/staging/QuoteDriver.scala b/staging/src/scala/quoted/staging/QuoteDriver.scala index 5d12d6ab5c26..cc3ecfe1ceec 100644 --- a/staging/src/scala/quoted/staging/QuoteDriver.scala +++ b/staging/src/scala/quoted/staging/QuoteDriver.scala @@ -33,7 +33,7 @@ private class QuoteDriver(appClassloader: ClassLoader) extends Driver: new VirtualDirectory("") end outDir - val (_, ctx0: Context) = setup(settings.compilerArgs.toArray :+ "dummy.scala", initCtx.fresh) + val ctx0 = setup(settings.compilerArgs.toArray :+ "dummy.scala", initCtx.fresh).get._2 val ctx = setCompilerSettings(ctx0.fresh.setSetting(ctx0.settings.outputDir, outDir), settings) new QuoteCompiler().newRun(ctx).compileExpr(exprBuilder) match