mirror of https://github.com/sbt/sbt.git
[2.x] fix: Backtick-quote project IDs in classloader error messages (#8830)
When a test fails with ClassNotFoundException/IllegalAccessError, sbt suggests a set command to change classLoaderLayeringStrategy. If the project name contains hyphens (e.g. bug-report), the suggested command was syntactically invalid because it parses as subtraction in Scala. Quote project IDs using Util.quoteIfNotScalaId so the suggested commands are valid when copy-pasted. Fixes #5803
This commit is contained in:
parent
33d86d0cd2
commit
c3e72f79c0
|
|
@ -311,6 +311,7 @@ lazy val utilCore = project
|
|||
utilCommonSettings,
|
||||
name := "Util Core",
|
||||
Utils.keywordsSettings,
|
||||
libraryDependencies ++= Seq(hedgehog % Test),
|
||||
mimaSettings,
|
||||
mimaBinaryIssueFilters ++= Seq(
|
||||
),
|
||||
|
|
|
|||
|
|
@ -45,6 +45,14 @@ object Util:
|
|||
|
||||
def quoteIfKeyword(s: String): String = if (ScalaKeywords.values(s)) s"`${s}`" else s
|
||||
|
||||
def quoteIfNotScalaId(s: String): String =
|
||||
if isValidScalaId(s) && !ScalaKeywords.values(s) then s
|
||||
else s"`$s`"
|
||||
|
||||
private def isValidScalaId(s: String): Boolean =
|
||||
s.nonEmpty && (s.charAt(0).isLetter || s.charAt(0) == '_') &&
|
||||
s.forall(c => c.isLetterOrDigit || c == '_')
|
||||
|
||||
def ignoreResult[A](f: => A): Unit = {
|
||||
val _ = f
|
||||
()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2023, Scala center
|
||||
* Copyright 2011 - 2022, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
import hedgehog.*
|
||||
import hedgehog.runner.*
|
||||
|
||||
object UtilSpec extends Properties:
|
||||
override def tests: List[Test] = List(
|
||||
example(
|
||||
"quoteIfNotScalaId should not quote simple identifiers",
|
||||
assertQuote("foo", "foo"),
|
||||
),
|
||||
example(
|
||||
"quoteIfNotScalaId should not quote identifiers with underscores",
|
||||
assertQuote("foo_bar", "foo_bar"),
|
||||
),
|
||||
example(
|
||||
"quoteIfNotScalaId should quote identifiers with hyphens",
|
||||
assertQuote("bug-report", "`bug-report`"),
|
||||
),
|
||||
example(
|
||||
"quoteIfNotScalaId should quote identifiers with dots",
|
||||
assertQuote("my.project", "`my.project`"),
|
||||
),
|
||||
example(
|
||||
"quoteIfNotScalaId should quote identifiers starting with digits",
|
||||
assertQuote("123abc", "`123abc`"),
|
||||
),
|
||||
example(
|
||||
"quoteIfNotScalaId should quote Scala keywords",
|
||||
assertQuote("class", "`class`"),
|
||||
),
|
||||
example(
|
||||
"quoteIfNotScalaId should quote empty strings",
|
||||
assertQuote("", "``"),
|
||||
),
|
||||
)
|
||||
|
||||
private def assertQuote(input: String, expected: String): Result =
|
||||
val actual = Util.quoteIfNotScalaId(input)
|
||||
Result.assert(actual == expected).log(s"input=$input expected=$expected actual=$actual")
|
||||
end UtilSpec
|
||||
|
|
@ -1201,7 +1201,20 @@ object Defaults extends BuildCommon {
|
|||
thisProject,
|
||||
fileConverter,
|
||||
).flatMapN { (s, lt, tl, gp, ex, cp, fp, jo, clls, thisProj, c) =>
|
||||
allTestGroupsTask(s, lt, tl, gp, ex, cp, fp, fpm, jo, clls, s"${thisProj.id} / ", c)
|
||||
allTestGroupsTask(
|
||||
s,
|
||||
lt,
|
||||
tl,
|
||||
gp,
|
||||
ex,
|
||||
cp,
|
||||
fp,
|
||||
fpm,
|
||||
jo,
|
||||
clls,
|
||||
s"${Util.quoteIfNotScalaId(thisProj.id)} / ",
|
||||
c
|
||||
)
|
||||
}
|
||||
}.value),
|
||||
// ((streams in test, loadedTestFrameworks, testLoader, testGrouping in test, testExecution in test, fullClasspath in test, javaHome in test, testForkedParallel, javaOptions in test) flatMap allTestGroupsTask).value,
|
||||
|
|
@ -1377,7 +1390,7 @@ object Defaults extends BuildCommon {
|
|||
testForkedParallelism.value,
|
||||
javaOptions.value,
|
||||
classLoaderLayeringStrategy.value,
|
||||
projectId = s"${thisProject.value.id} / ",
|
||||
projectId = s"${Util.quoteIfNotScalaId(thisProject.value.id)} / ",
|
||||
converter = fileConverter.value,
|
||||
)
|
||||
val taskName = display.show(resolvedScoped.value)
|
||||
|
|
|
|||
Loading…
Reference in New Issue