Skip to content

Commit

Permalink
Add better input error descriptions
Browse files Browse the repository at this point in the history
  • Loading branch information
MaciejG604 committed Jan 27, 2023
1 parent 7abec7d commit b67c287
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 17 deletions.
36 changes: 29 additions & 7 deletions modules/build/src/main/scala/scala/build/input/Inputs.scala
Expand Up @@ -263,7 +263,8 @@ object Inputs {
stdinOpt: => Option[Array[Byte]],
acceptFds: Boolean,
enableMarkdown: Boolean,
isRunWithShebang: Boolean
isRunWithShebang: Boolean,
isRunWithDefault: Boolean
): Seq[Either[String, Seq[Element]]] = args.zipWithIndex.map {
case (arg, idx) =>
lazy val path = os.Path(arg, cwd)
Expand Down Expand Up @@ -313,8 +314,17 @@ object Inputs {
if isShebangScript(String(content)) then
s"$arg scripts with no file extension should be run with 'scala-cli shebang'"
else
s"$arg: unrecognized source type (expected .scala or .sc extension, or a directory)"
else s"$arg: not found"
s"""$arg: unrecognized source type (expected .scala or .sc extension, or a directory),
|if it's meant to be a script add a '!#' pointing to 'scala-cli shebang' in the top line
|and run the source with 'scala-cli shebang $arg' or ${
if path.toString == arg then arg else s"./$arg"
}
|""".stripMargin
else if isRunWithDefault && idx == 0 && !arg.contains('.') then
s"""$arg is not a scala-cli sub-command and it is not a valid path to an input file or directory
|Try 'scala-cli --help' to see the list of available sub-commands and options
|""".stripMargin
else s"$arg: file not found"
Left(msg)
}
}
Expand All @@ -334,10 +344,20 @@ object Inputs {
enableMarkdown: Boolean,
allowRestrictedFeatures: Boolean,
extraClasspathWasPassed: Boolean,
isRunWithShebang: Boolean
isRunWithShebang: Boolean,
isRunWithDefault: Boolean
): Either[BuildException, Inputs] = {
val validatedArgs: Seq[Either[String, Seq[Element]]] =
validateArgs(args, cwd, download, stdinOpt, acceptFds, enableMarkdown, isRunWithShebang)
validateArgs(
args,
cwd,
download,
stdinOpt,
acceptFds,
enableMarkdown,
isRunWithShebang,
isRunWithDefault
)
val validatedSnippets: Seq[Either[String, Seq[Element]]] =
validateSnippets(scriptSnippetList, scalaSnippetList, javaSnippetList, markdownSnippetList)
val validatedArgsAndSnippets = validatedArgs ++ validatedSnippets
Expand Down Expand Up @@ -379,7 +399,8 @@ object Inputs {
enableMarkdown: Boolean = false,
allowRestrictedFeatures: Boolean,
extraClasspathWasPassed: Boolean,
isRunWithShebang: Boolean
isRunWithShebang: Boolean,
isRunWithDefault: Boolean
): Either[BuildException, Inputs] =
if (
args.isEmpty && scriptSnippetList.isEmpty && scalaSnippetList.isEmpty && javaSnippetList.isEmpty &&
Expand All @@ -404,7 +425,8 @@ object Inputs {
enableMarkdown,
allowRestrictedFeatures,
extraClasspathWasPassed,
isRunWithShebang
isRunWithShebang,
isRunWithDefault
)

def default(): Option[Inputs] = None
Expand Down
21 changes: 21 additions & 0 deletions modules/build/src/test/scala/scala/build/tests/InputsTests.scala
Expand Up @@ -108,4 +108,25 @@ class InputsTests extends munit.FunSuite {
assert(inputs.elements.projectSettingsFiles.length == 1)
}
}

test("passing script file") {
val projectFileName = Constants.projectFileName
val testInputs = TestInputs(
files = Seq(
os.rel / "foo.scala" ->
s"""object Foo {
| def main(args: Array[String]): Unit =
| println("Foo")
|}
|""".stripMargin,
os.rel / projectFileName -> ""
),
inputArgs = Seq(".", projectFileName)
)
testInputs.withBuild(buildOptions, buildThreads, bloopConfigOpt) {
(root, inputs, _) =>
assert(os.exists(root / Constants.workspaceDirName))
assert(inputs.elements.projectSettingsFiles.length == 1)
}
}
}
Expand Up @@ -45,7 +45,8 @@ final case class TestInputs(
forcedWorkspace = forcedWorkspaceOpt.map(_.resolveFrom(tmpDir)),
allowRestrictedFeatures = true,
extraClasspathWasPassed = false,
isRunWithShebang = false
isRunWithShebang = false,
isRunWithDefault = false
)
res match {
case Left(err) => throw new Exception(err)
Expand Down
Expand Up @@ -22,7 +22,8 @@ object Clean extends ScalaCommand[CleanOptions] {
forcedWorkspace = options.workspace.forcedWorkspaceOpt,
allowRestrictedFeatures = ScalaCli.allowRestrictedFeatures,
extraClasspathWasPassed = false,
isRunWithShebang = false
isRunWithShebang = false,
isRunWithDefault = false
) match {
case Left(message) =>
System.err.println(message)
Expand Down
Expand Up @@ -55,5 +55,6 @@ class Default(
}.parse(options.legacyScala.filterNonDeprecatedArgs(rawArgs, progName, logger)) match
case Left(e) => error(e)
case Right((replOptions: ReplOptions, _)) => Repl.runCommand(replOptions, args, logger)
case Right((runOptions: RunOptions, _)) => Run.runCommand(runOptions, args, logger)
case Right((runOptions: RunOptions, _)) =>
Run.runCommand(runOptions, args, logger, isRunWithDefault = true)
}
21 changes: 19 additions & 2 deletions modules/cli/src/main/scala/scala/cli/commands/run/Run.scala
Expand Up @@ -58,6 +58,21 @@ object Run extends ScalaCommand[RunOptions] with BuildCommandHelpers {
logger
)

def runCommand(
options: RunOptions,
args: RemainingArgs,
logger: Logger,
isRunWithDefault: Boolean
): Unit =
runCommand(
options,
args.remaining,
args.unparsed,
() => Inputs.default(),
logger,
isRunWithDefault = isRunWithDefault
)

override def buildOptions(options: RunOptions): Some[BuildOptions] = Some {
import options.*
import options.sharedRun.*
Expand Down Expand Up @@ -107,14 +122,16 @@ object Run extends ScalaCommand[RunOptions] with BuildCommandHelpers {
programArgs: Seq[String],
defaultInputs: () => Option[Inputs],
logger: Logger,
isRunWithShebang: Boolean = false
isRunWithShebang: Boolean = false,
isRunWithDefault: Boolean = false
): Unit = {
val initialBuildOptions = buildOptionsOrExit(options)

val inputs = options.shared.inputs(
inputArgs,
defaultInputs = defaultInputs,
isRunWithShebang
isRunWithShebang,
isRunWithDefault
).orExit(logger)
CurrentParams.workspaceOpt = Some(inputs.workspace)
val threads = BuildThreads.create()
Expand Down
Expand Up @@ -514,7 +514,8 @@ final case class SharedOptions(
def inputs(
args: Seq[String],
defaultInputs: () => Option[Inputs] = () => Inputs.default(),
isRunWithShebang: Boolean = false
isRunWithShebang: Boolean = false,
isRunWithDefault: Boolean = false
): Either[BuildException, Inputs] =
SharedOptions.inputs(
args,
Expand All @@ -532,7 +533,8 @@ final case class SharedOptions(
markdownSnippetList = allMarkdownSnippets,
enableMarkdown = markdown.enableMarkdown,
extraClasspathWasPassed = extraJarsAndClassPath.nonEmpty,
isRunWithShebang = isRunWithShebang
isRunWithShebang = isRunWithShebang,
isRunWithDefault = isRunWithDefault
)

def allScriptSnippets: List[String] = snippet.scriptSnippet ++ snippet.executeScript
Expand All @@ -548,7 +550,8 @@ final case class SharedOptions(
SharedOptions.readStdin(logger = logger),
!Properties.isWin,
enableMarkdown = true,
isRunWithShebang = false
isRunWithShebang = false,
isRunWithDefault = false
)

def strictBloopJsonCheckOrDefault: Boolean =
Expand Down Expand Up @@ -587,7 +590,8 @@ object SharedOptions {
markdownSnippetList: List[String],
enableMarkdown: Boolean = false,
extraClasspathWasPassed: Boolean = false,
isRunWithShebang: Boolean = false
isRunWithShebang: Boolean = false,
isRunWithDefault: Boolean = false
): Either[BuildException, Inputs] = {
val resourceInputs = resourceDirs
.map(os.Path(_, Os.pwd))
Expand All @@ -612,7 +616,8 @@ object SharedOptions {
enableMarkdown = enableMarkdown,
allowRestrictedFeatures = ScalaCli.allowRestrictedFeatures,
extraClasspathWasPassed = extraClasspathWasPassed,
isRunWithShebang
isRunWithShebang,
isRunWithDefault
)
maybeInputs.map { inputs =>
val forbiddenDirs =
Expand Down
Expand Up @@ -1127,4 +1127,61 @@ abstract class RunTestDefinitions(val scalaVersionOpt: Option[String])
expect(output == root.toString())
}
}

val commands = Seq("", "run", "compile")

for (command <- commands) {
test(
s"error output for unrecognized source type for ${if (command == "") "default" else command}"
) {
val inputs = TestInputs(
os.rel / "print.hehe" ->
s"""println("Foo")
|""".stripMargin
)
inputs.fromRoot { root =>
val proc = if (command == "") os.proc(TestUtil.cli, "print.hehe")
else os.proc(TestUtil.cli, command, "print.hehe")
val output = proc.call(cwd = root, check = false, stderr = os.Pipe)
.err.trim()

expect(output.contains("unrecognized source type"))
}
}

test(s"error output for nonexistent file for ${if (command == "") "default" else command}") {
val inputs = TestInputs(
os.rel / "print.hehe" ->
s"""println("Foo")
|""".stripMargin
)
inputs.fromRoot { root =>
val proc = if (command == "") os.proc(TestUtil.cli, "nonexisten.no")
else os.proc(TestUtil.cli, command, "nonexisten.no")
val output = proc.call(cwd = root, check = false, stderr = os.Pipe)
.err.trim()

expect(output.contains("file not found"))
}
}

test(s"error output for invalid sub-command for ${if (command == "") "default" else command}") {
val inputs = TestInputs(
os.rel / "print.hehe" ->
s"""println("Foo")
|""".stripMargin
)
inputs.fromRoot { root =>
val proc = if (command == "") os.proc(TestUtil.cli, "invalid")
else os.proc(TestUtil.cli, command, "invalid")
val output = proc.call(cwd = root, check = false, stderr = os.Pipe)
.err.trim()

if (command == "")
expect(output.contains("is not a scala-cli sub-command"))
else
expect(output.contains("file not found"))
}
}
}
}

0 comments on commit b67c287

Please sign in to comment.