From 1d9b44d5d794d9414a232c015e2faeffa00596ad Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Sun, 24 Nov 2013 15:19:24 -0500 Subject: [PATCH] Expand aliases instead of evaluating directly. This avoids an additional cause of recursion via the semicolon/multiple command, which fixes #933. It also provides error messages on the expanded command. This fixes #598. --- .../command/src/main/scala/sbt/BasicCommands.scala | 14 +++++++++++--- sbt/src/sbt-test/actions/aliasrc/.sbtrc | 1 + sbt/src/sbt-test/actions/aliasrc/test | 2 ++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/main/command/src/main/scala/sbt/BasicCommands.scala b/main/command/src/main/scala/sbt/BasicCommands.scala index 2c0f629f3..14c2dfded 100644 --- a/main/command/src/main/scala/sbt/BasicCommands.scala +++ b/main/command/src/main/scala/sbt/BasicCommands.scala @@ -234,11 +234,13 @@ object BasicCommands def addAlias(s: State, name: String, value: String): State = if(Command validID name) { val removed = removeAlias(s, name) - if(value.isEmpty) removed else removed.copy(definedCommands = newAlias(name, value) +: removed.definedCommands) + if(value.isEmpty) removed else addAlias0(removed, name, value) } else { System.err.println("Invalid alias name '" + name + "'.") s.fail } + private[this] def addAlias0(s: State, name: String, value: String): State = + s.copy(definedCommands = newAlias(name, value) +: s.definedCommands) def removeAliases(s: State): State = removeTagged(s, CommandAliasKey) def removeAlias(s: State, name: String): State = s.copy(definedCommands = s.definedCommands.filter(c => !isAliasNamed(name, c)) ) @@ -263,8 +265,14 @@ object BasicCommands def newAlias(name: String, value: String): Command = Command.make(name, (name, "'" + value + "'"), "Alias of '" + value + "'")(aliasBody(name, value)).tag(CommandAliasKey, (name, value)) - def aliasBody(name: String, value: String)(state: State): Parser[() => State] = - OptSpace ~> Parser(Command.combine(removeAlias(state,name).definedCommands)(state))(value) + def aliasBody(name: String, value: String)(state: State): Parser[() => State] = { + val aliasRemoved = removeAlias(state,name) + // apply the alias value to the commands of `state` except for the alias to avoid recursion (#933) + val partiallyApplied = Parser(Command.combine(aliasRemoved.definedCommands)(aliasRemoved))(value) + val arg = matched( partiallyApplied & (success() | (SpaceClass ~ any.*)) ) + // by scheduling the expanded alias instead of directly executing, we get errors on the expanded string (#598) + arg.map( str => () => (value + str) :: state) + } def delegateToAlias(name: String, orElse: Parser[() => State])(state: State): Parser[() => State] = aliases(state, (nme,_) => nme == name).headOption match { diff --git a/sbt/src/sbt-test/actions/aliasrc/.sbtrc b/sbt/src/sbt-test/actions/aliasrc/.sbtrc index 3d7d3773e..f819183b9 100644 --- a/sbt/src/sbt-test/actions/aliasrc/.sbtrc +++ b/sbt/src/sbt-test/actions/aliasrc/.sbtrc @@ -1 +1,2 @@ alias info= set logLevel := Level.Info +alias cl= ;clean diff --git a/sbt/src/sbt-test/actions/aliasrc/test b/sbt/src/sbt-test/actions/aliasrc/test index 2ae698547..256040408 100644 --- a/sbt/src/sbt-test/actions/aliasrc/test +++ b/sbt/src/sbt-test/actions/aliasrc/test @@ -1 +1,3 @@ > info +> cl +> clean