diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 1bee37c42..852f57cfc 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -14,7 +14,7 @@ jobs: fail-fast: false matrix: java-version: [8, 11] - scala-version: [2.12.17, 2.13.10, 3.2.2] + scala-version: [2.12.18, 2.13.12, 3.3.1] runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -29,7 +29,7 @@ jobs: fail-fast: false matrix: java-version: [8, 11] - scala-version: [2.12.17, 2.13.10, 3.2.2] + scala-version: [2.12.18, 2.13.12, 3.3.1] runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 diff --git a/LICENSE b/LICENSE index 307d8e963..38b697dd9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,10 +1,6 @@ -License -======= +MIT License - -The MIT License (MIT) - -Copyright (c) 2014 Li Haoyi (haoyi.sg@gmail.com) +Copyright (c) 2015 Li Haoyi (haoyi.sg@gmail.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -13,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/amm/compiler/interface/src/main/scala/ammonite/compiler/iface/CompilerBuilder.scala b/amm/compiler/interface/src/main/scala/ammonite/compiler/iface/CompilerBuilder.scala index eecaaeb47..de682f4f6 100644 --- a/amm/compiler/interface/src/main/scala/ammonite/compiler/iface/CompilerBuilder.scala +++ b/amm/compiler/interface/src/main/scala/ammonite/compiler/iface/CompilerBuilder.scala @@ -12,7 +12,8 @@ abstract class CompilerBuilder { headFrame: => Frame, dependencyCompleter: => Option[String => (Int, Seq[String])], whiteList: Set[Seq[String]], - initialClassLoader: ClassLoader + initialClassLoader: ClassLoader, + settings: Seq[String] ): CompilerLifecycleManager def create( diff --git a/amm/compiler/src/main/scala-2.13.1+/ammonite/compiler/MakeReporter.scala b/amm/compiler/src/main/scala-2.13.1-2.13.11/ammonite/compiler/MakeReporter.scala similarity index 100% rename from amm/compiler/src/main/scala-2.13.1+/ammonite/compiler/MakeReporter.scala rename to amm/compiler/src/main/scala-2.13.1-2.13.11/ammonite/compiler/MakeReporter.scala diff --git a/amm/compiler/src/main/scala-2.13.12+/ammonite/compiler/MakeReporter.scala b/amm/compiler/src/main/scala-2.13.12+/ammonite/compiler/MakeReporter.scala new file mode 100644 index 000000000..02a864110 --- /dev/null +++ b/amm/compiler/src/main/scala-2.13.12+/ammonite/compiler/MakeReporter.scala @@ -0,0 +1,45 @@ +package ammonite.compiler + +import ammonite.util.Classpath + +import scala.reflect.internal.util.{CodeAction, Position} +import scala.reflect.io.FileZipArchive +import scala.tools.nsc +import scala.tools.nsc.classpath.{AggregateClassPath, ZipAndJarClassPathFactory} +import scala.tools.nsc.{Global, Settings} +import scala.tools.nsc.interactive.{InteractiveAnalyzer, Global => InteractiveGlobal} +import scala.tools.nsc.plugins.Plugin +import scala.tools.nsc.reporters.FilteringReporter +import scala.tools.nsc.typechecker.Analyzer + +object MakeReporter { + + type Reporter = scala.tools.nsc.reporters.Reporter + + def makeReporter(errorLogger: (Position, String) => Unit, + warningLogger: (Position, String) => Unit, + infoLogger: (Position, String) => Unit, + outerSettings: Settings): Reporter = + new FilteringReporter { + + override + def doReport(pos: scala.reflect.internal.util.Position, + msg: String, + severity: Severity, + actions: List[CodeAction]): Unit = + display(pos, msg, severity) + + def display(pos: Position, msg: String, severity: Severity) = + severity match{ + case ERROR => + Classpath.traceClasspathProblem(s"ERROR: $msg") + errorLogger(pos, msg) + case WARNING => + warningLogger(pos, msg) + case INFO => + infoLogger(pos, msg) + } + + def settings = outerSettings + } +} diff --git a/amm/compiler/src/main/scala-2/ammonite/compiler/Compiler.scala b/amm/compiler/src/main/scala-2/ammonite/compiler/Compiler.scala index 892121ecb..2c68e989d 100644 --- a/amm/compiler/src/main/scala-2/ammonite/compiler/Compiler.scala +++ b/amm/compiler/src/main/scala-2/ammonite/compiler/Compiler.scala @@ -56,11 +56,8 @@ object Compiler{ output.write(bytes) output.close() - for (dir <- outputDir0) { - val dest = dir / elems - os.makeDir.all(dest / os.up) - os.write(dest, bytes) - } + for (dir <- outputDir0) + os.write.over(dir / elems, bytes, createFolders = true) } } @@ -196,7 +193,6 @@ object Compiler{ settings.outputDirs.setSingleOutput(vd) - settings.nowarnings.value = true // Otherwise the presence of `src`'s source files mixed with // classfiles causes scalac to get confused settings.termConflict.value = "object" diff --git a/amm/compiler/src/main/scala-2/ammonite/compiler/CompilerBuilder.scala b/amm/compiler/src/main/scala-2/ammonite/compiler/CompilerBuilder.scala index 26d35ee2a..f3eaf3fe6 100644 --- a/amm/compiler/src/main/scala-2/ammonite/compiler/CompilerBuilder.scala +++ b/amm/compiler/src/main/scala-2/ammonite/compiler/CompilerBuilder.scala @@ -80,7 +80,8 @@ case class CompilerBuilder( headFrame: => Frame, dependencyCompleter: => Option[String => (Int, Seq[String])], whiteList: Set[Seq[String]], - initialClassLoader: ClassLoader + initialClassLoader: ClassLoader, + settings: Seq[String] ): CompilerLifecycleManager = new CompilerLifecycleManager( rtCacheDir, @@ -88,7 +89,8 @@ case class CompilerBuilder( dependencyCompleter, whiteList, initialClassLoader, - outputDir + outputDir, + settings ) def scalaVersion = CompilerBuilder.scalaVersion diff --git a/amm/compiler/src/main/scala-2/ammonite/compiler/CompilerLifecycleManager.scala b/amm/compiler/src/main/scala-2/ammonite/compiler/CompilerLifecycleManager.scala index 718f95be1..9b4a1c248 100644 --- a/amm/compiler/src/main/scala-2/ammonite/compiler/CompilerLifecycleManager.scala +++ b/amm/compiler/src/main/scala-2/ammonite/compiler/CompilerLifecycleManager.scala @@ -32,7 +32,8 @@ class CompilerLifecycleManager( dependencyCompleteOpt: => Option[String => (Int, Seq[String])], classPathWhitelist: Set[Seq[String]], initialClassLoader: ClassLoader, - val outputDir: Option[Path] + val outputDir: Option[Path], + initialSettings: Seq[String] ) extends ICompilerLifecycleManager { def scalaVersion = scala.util.Properties.versionNumberString @@ -88,6 +89,9 @@ class CompilerLifecycleManager( // Otherwise activating autocomplete makes the presentation compiler mangle // the shared settings and makes the main compiler sad val settings = Option(compiler).fold(new Settings)(_.compiler.settings.copy) + val (success, trailingSettings) = settings.processArguments(initialSettings.toList, processAll = true) + if (!success) + System.err.println(s"Error processing initial settings ${initialSettings.mkString(" ")}") onSettingsInit.foreach(_(settings)) val initialClassPath = Classpath.classpath(initialClassLoader, rtCacheDir) diff --git a/amm/compiler/src/main/scala-2/ammonite/compiler/DefaultPreprocessor.scala b/amm/compiler/src/main/scala-2/ammonite/compiler/DefaultPreprocessor.scala index ff5f926be..cd0030e65 100644 --- a/amm/compiler/src/main/scala-2/ammonite/compiler/DefaultPreprocessor.scala +++ b/amm/compiler/src/main/scala-2/ammonite/compiler/DefaultPreprocessor.scala @@ -111,8 +111,13 @@ class DefaultPreprocessor(parse: => String => Either[String, Seq[G#Tree]], val Import = Processor{ case (name, code, tree: G#Import) => - val Array(keyword, body) = code.split(" ", 2) - val tq = "\"\"\"" + val body = fastparse.parse(code, Parsers.ImportFinder(_)) match { + case s: fastparse.Parsed.Success[String] => + s.value + case _ => + val Array(keyword, body) = code.split(" ", 2) + body + } Expanded(code, Seq( s""" _root_.ammonite diff --git a/amm/compiler/src/main/scala-2/ammonite/compiler/Parsers.scala b/amm/compiler/src/main/scala-2/ammonite/compiler/Parsers.scala index 10a251503..53621e7ff 100644 --- a/amm/compiler/src/main/scala-2/ammonite/compiler/Parsers.scala +++ b/amm/compiler/src/main/scala-2/ammonite/compiler/Parsers.scala @@ -17,7 +17,7 @@ object Parsers extends IParser { private def `_`[_: P] = scalaparse.Scala.Underscore - private def ImportSplitter[_: P]: P[Seq[ammonite.util.ImportTree]] = { + private def ImportExpr[_: P]: P[ammonite.util.ImportTree] = { def IdParser = P( (Id | `_` ).! ).map( s => if (s(0) == '`') s.drop(1).dropRight(1) else s ) @@ -28,18 +28,22 @@ object Parsers extends IParser { ) def Prefix = P( IdParser.rep(1, sep = ".") ) def Suffix = P( "." ~/ (BulkImport | Selectors) ) - def ImportExpr: P[ammonite.util.ImportTree] = { - // Manually use `WL0` parser here, instead of relying on WhitespaceApi, as - // we do not want the whitespace to be consumed even if the WL0 parser parses - // to the end of the input (which is the default behavior for WhitespaceApi) - P( Index ~~ Prefix ~~ (WL0 ~~ Suffix).? ~~ Index).map{ - case (start, idSeq, selectors, end) => - ammonite.util.ImportTree(idSeq, selectors, start, end) - } + + // Manually use `WL0` parser here, instead of relying on WhitespaceApi, as + // we do not want the whitespace to be consumed even if the WL0 parser parses + // to the end of the input (which is the default behavior for WhitespaceApi) + P( Index ~~ Prefix ~~ (WL0 ~~ Suffix).? ~~ Index).map{ + case (start, idSeq, selectors, end) => + ammonite.util.ImportTree(idSeq, selectors, start, end) } - P( `import` ~/ ImportExpr.rep(1, sep = ","./) ) } + def ImportSplitter[_: P]: P[Seq[ammonite.util.ImportTree]] = + P( WL ~ `import` ~/ ImportExpr.rep(1, sep = ","./) ) + + def ImportFinder[_: P]: P[String] = + P(WL ~ `import` ~/ ImportExpr.! ~ End) + private def PatVarSplitter[_: P] = { def Prefixes = P(Prelude ~ (`var` | `val`)) def Lhs = P( Prefixes ~/ BindPattern.rep(1, "," ~/ Pass) ~ (`:` ~/ Type).? ) @@ -63,8 +67,11 @@ object Parsers extends IParser { private def StatementBlock[_: P](blockSep: => P0) = P( Semis.? ~ (Index ~ (!blockSep ~ TmplStat ~~ WS ~~ (Semis | &("}") | End)).!).repX) - private def Splitter0[_: P] = P( StatementBlock(Fail) ) - def Splitter[_: P] = P( ("{" ~ Splitter0 ~ "}" | Splitter0) ~ End ) + private def Splitter0[_: P] = P(StatementBlock(Fail)) + + private def HighlightSplitter[_: P] = P( ("{" ~ Splitter0 ~ "}" | Splitter0) ~ End ) + + def Splitter[_: P] = P(("{" ~~ WL.! ~~ Splitter0 ~ "}" | WL.! ~~ Splitter0) ~ End) private def ObjParser[_: P] = P( ObjDef ) @@ -94,14 +101,29 @@ object Parsers extends IParser { case f @ Parsed.Failure(_, _, _) => Some(Left( formatFastparseError(fileName, code, f) )) - case Parsed.Success(value, index) => Some(Right(value.map(_._2))) + case Parsed.Success(value, index) => { + val (str, seq) = value + if (seq.isEmpty) { + Some(Right(Nil)) + } else { + Some(Right(Seq(str + seq.head._2) ++ seq.tail.map(_._2))) + } + } } } else parse(code, Splitter(_)) match{ case f @ Parsed.Failure(_, _, _) => Some(Left( formatFastparseError(fileName, code, f) )) - case Parsed.Success(value, index) => Some(Right(value.map(_._2))) + case Parsed.Success(value, index) => { + val (str, seq) = value + if (seq.isEmpty) { + Some(Right(Nil)) + } else { + Some(Right(Seq(str + seq.head._2) ++ seq.tail.map(_._2))) + } + + } } def isObjDef(code: String): Boolean = { @@ -265,7 +287,7 @@ object Parsers extends IParser { keyword: fansi.Attrs, notImplemented: fansi.Attrs, reset: fansi.Attrs) = { - Highlighter.defaultHighlight0(Splitter(_), buffer, comment, `type`, literal, keyword, reset) + Highlighter.defaultHighlight0(HighlightSplitter(_), buffer, comment, `type`, literal, keyword, reset) } def defaultHighlightIndices(buffer: Vector[Char], comment: fansi.Attrs, @@ -273,11 +295,11 @@ object Parsers extends IParser { literal: fansi.Attrs, keyword: fansi.Attrs, reset: fansi.Attrs) = Highlighter.defaultHighlightIndices0( - Splitter(_), buffer, comment, `type`, literal, keyword, reset + HighlightSplitter(_), buffer, comment, `type`, literal, keyword, reset ) def highlightIndices[T](buffer: Vector[Char], ruleColors: PartialFunction[String, T], endColor: T): Seq[(Int, T)] = - Highlighter.highlightIndices(Parsers.Splitter(_), buffer, ruleColors, endColor) + Highlighter.highlightIndices(Parsers.HighlightSplitter(_), buffer, ruleColors, endColor) } diff --git a/amm/compiler/src/main/scala-3/ammonite/compiler/Compiler.scala b/amm/compiler/src/main/scala-3/ammonite/compiler/Compiler.scala index d5b637add..82c060f47 100644 --- a/amm/compiler/src/main/scala-3/ammonite/compiler/Compiler.scala +++ b/amm/compiler/src/main/scala-3/ammonite/compiler/Compiler.scala @@ -234,47 +234,53 @@ class Compiler( val result = if (ctx.reporter.hasErrors) Left(reporter.fold(ctx.reporter.removeBufferedMessages)(_ => Nil)) - else Right(unit) + else Right((reporter.fold(ctx.reporter.removeBufferedMessages)(_ => Nil), unit)) + + def formatDiagnostics(diagnostics: List[reporting.Diagnostic]): List[String] = { + val scalaPosToScPos = PositionOffsetConversion.scalaPosToScPos( + new String(src).drop(importsLen), + 0, + 0, + new String(src), + importsLen + ) + val scFile = new SourceFile(sourceFile.file, sourceFile.content().drop(importsLen)) + def scalaOffsetToScOffset(scalaOffset: Int): Option[Int] = + scalaPosToScPos(sourceFile.offsetToLine(scalaOffset), sourceFile.column(scalaOffset)).map { + case (scLine, scCol) => scFile.lineToOffset(scLine) + scCol + } + def scalaSpanToScSpan(scalaSpan: Span): Option[Span] = + for { + scStart <- scalaOffsetToScOffset(scalaSpan.start) + scEnd <- scalaOffsetToScOffset(scalaSpan.end) + scPoint <- scalaOffsetToScOffset(scalaSpan.point) + } yield Span(scStart, scEnd, scPoint) + def scalaSourcePosToScSourcePos(sourcePos: SourcePosition): Option[SourcePosition] = + if (sourcePos.source == sourceFile) + scalaSpanToScSpan(sourcePos.span).map { scSpan => + SourcePosition(scFile, scSpan, sourcePos.outer) + } + else + None + def scalaDiagnosticToScDiagnostic(diag: reporting.Diagnostic): Option[reporting.Diagnostic] = + scalaSourcePosToScSourcePos(diag.pos).map { scPos => + new reporting.Diagnostic(diag.msg, scPos, diag.level) + } + + diagnostics + .map(d => scalaDiagnosticToScDiagnostic(d).getOrElse(d)) + .map(formatError) + .map(_.msg.toString) + } result match { case Left(errors) => - val scalaPosToScPos = PositionOffsetConversion.scalaPosToScPos( - new String(src).drop(importsLen), - 0, - 0, - new String(src), - importsLen - ) - val scFile = new SourceFile(sourceFile.file, sourceFile.content().drop(importsLen)) - def scalaOffsetToScOffset(scalaOffset: Int): Option[Int] = - scalaPosToScPos(sourceFile.offsetToLine(scalaOffset), sourceFile.column(scalaOffset)).map { - case (scLine, scCol) => scFile.lineToOffset(scLine) + scCol - } - def scalaSpanToScSpan(scalaSpan: Span): Option[Span] = - for { - scStart <- scalaOffsetToScOffset(scalaSpan.start) - scEnd <- scalaOffsetToScOffset(scalaSpan.end) - scPoint <- scalaOffsetToScOffset(scalaSpan.point) - } yield Span(scStart, scEnd, scPoint) - def scalaSourcePosToScSourcePos(sourcePos: SourcePosition): Option[SourcePosition] = - if (sourcePos.source == sourceFile) - scalaSpanToScSpan(sourcePos.span).map { scSpan => - SourcePosition(scFile, scSpan, sourcePos.outer) - } - else - None - def scalaDiagnosticToScDiagnostic(diag: reporting.Diagnostic): Option[reporting.Diagnostic] = - scalaSourcePosToScSourcePos(diag.pos).map { scPos => - new reporting.Diagnostic(diag.msg, scPos, diag.level) - } - - errors - .map(d => scalaDiagnosticToScDiagnostic(d).getOrElse(d)) - .map(formatError) - .map(_.msg.toString) - .foreach(printer.error) + for (err <- formatDiagnostics(errors)) + printer.error(err) None - case Right(unit) => + case Right((warnings, unit)) => + for (warn <- formatDiagnostics(warnings)) + printer.warning(warn) val newImports = unfusedPhases.collectFirst { case p: AmmonitePhase => p.importData }.getOrElse(Seq.empty[ImportData]) @@ -507,11 +513,8 @@ object Compiler: output.write(bytes) output.close() - for (dir <- outputDir0) { - val dest = dir / elems - os.makeDir.all(dest / os.up) - os.write(dest, bytes) - } + for (dir <- outputDir0) + os.write.over(dir / elems, bytes, createFolders = true) } } diff --git a/amm/compiler/src/main/scala-3/ammonite/compiler/CompilerBuilder.scala b/amm/compiler/src/main/scala-3/ammonite/compiler/CompilerBuilder.scala index 8f252ea85..1081ee768 100644 --- a/amm/compiler/src/main/scala-3/ammonite/compiler/CompilerBuilder.scala +++ b/amm/compiler/src/main/scala-3/ammonite/compiler/CompilerBuilder.scala @@ -47,7 +47,8 @@ case class CompilerBuilder( headFrame: => Frame, dependencyCompleter: => Option[String => (Int, Seq[String])], whiteList: Set[Seq[String]], - initialClassLoader: ClassLoader + initialClassLoader: ClassLoader, + settings: Seq[String] ): ICompilerLifecycleManager = new CompilerLifecycleManager( rtCacheDir, @@ -55,7 +56,8 @@ case class CompilerBuilder( dependencyCompleter, whiteList, initialClassLoader, - outputDir + outputDir, + settings ) object CompilerBuilder: diff --git a/amm/compiler/src/main/scala-3/ammonite/compiler/CompilerLifecycleManager.scala b/amm/compiler/src/main/scala-3/ammonite/compiler/CompilerLifecycleManager.scala index ede67f98e..f6510874a 100644 --- a/amm/compiler/src/main/scala-3/ammonite/compiler/CompilerLifecycleManager.scala +++ b/amm/compiler/src/main/scala-3/ammonite/compiler/CompilerLifecycleManager.scala @@ -30,7 +30,8 @@ class CompilerLifecycleManager( dependencyCompleteOpt: => Option[String => (Int, Seq[String])], classPathWhitelist: Set[Seq[String]], initialClassLoader: ClassLoader, - val outputDir: Option[Path] + val outputDir: Option[Path], + initialSettings: Seq[String] ) extends ammonite.compiler.iface.CompilerLifecycleManager { def scalaVersion = dotc.config.Properties.versionNumberString @@ -96,7 +97,8 @@ class CompilerLifecycleManager( headFrame.classloader, classPathWhitelist, dependencyCompleteOpt = dependencyCompleteOpt, - contextInit = c => onSettingsInit.foreach(_(c)) + contextInit = c => onSettingsInit.foreach(_(c)), + settings = initialSettings ) onCompilerInit.foreach(_(compiler.compiler)) diff --git a/amm/compiler/src/main/scala/ammonite/compiler/CompilerUtil.scala b/amm/compiler/src/main/scala/ammonite/compiler/CompilerUtil.scala index 8190d3fa6..85049237e 100644 --- a/amm/compiler/src/main/scala/ammonite/compiler/CompilerUtil.scala +++ b/amm/compiler/src/main/scala/ammonite/compiler/CompilerUtil.scala @@ -14,6 +14,20 @@ object CompilerUtil { "$main", // Don't care about this "toString", + "equals", + "wait", + "notify", + "notifyAll", + "synchronized", + "hashCode", + "getClass", + "eq", + "ne", + "##", + "==", + "!=", + "isInstanceOf", + "asInstanceOf", // Behaves weird in 2.10.x, better to just ignore. "_" ) diff --git a/amm/interp/src/main/scala/ammonite/interp/Interpreter.scala b/amm/interp/src/main/scala/ammonite/interp/Interpreter.scala index c2fe72c15..91d2b2984 100644 --- a/amm/interp/src/main/scala/ammonite/interp/Interpreter.scala +++ b/amm/interp/src/main/scala/ammonite/interp/Interpreter.scala @@ -61,7 +61,8 @@ class Interpreter(val compilerBuilder: CompilerBuilder, headFrame, Some(dependencyComplete), classPathWhitelist, - Option(initialClassLoader).getOrElse(headFrame.classloader) + Option(initialClassLoader).getOrElse(headFrame.classloader), + if (warnings) Seq("-deprecation", "-feature") else Seq("-nowarn") ) val eval = Evaluator(headFrame) @@ -232,7 +233,7 @@ class Interpreter(val compilerBuilder: CompilerBuilder, silent: Boolean = false, incrementLine: () => Unit): Res[Evaluated] = synchronized{ - val wrapperName = Name("cmd" + currentLine) + val wrapperName = Name(wrapperNamePrefix + currentLine) val codeSource = CodeSource( wrapperName, @@ -422,7 +423,7 @@ class Interpreter(val compilerBuilder: CompilerBuilder, def processExec(code: String, currentLine: Int, incrementLine: () => Unit): Res[Imports] = synchronized{ - val wrapperName = Name("cmd" + currentLine) + val wrapperName = Name(wrapperNamePrefix + currentLine) val fileName = wrapperName.encoded + ".sc" for { blocks <- Res(parser().splitScript(Interpreter.skipSheBangLine(code), fileName)) @@ -656,7 +657,7 @@ class Interpreter(val compilerBuilder: CompilerBuilder, } - private[this] lazy val interpApi: InterpAPI = new InterpAPI{ outer => + lazy val interpApi: InterpAPI = new InterpAPI{ outer => val colors = parameters.colors @@ -712,6 +713,10 @@ class Interpreter(val compilerBuilder: CompilerBuilder, object Interpreter{ + /** @param wrapperNamePrefix + * Name to be used as a prefix for source file and classes wrapping user code, that ends in + * compilation errors or stack traces in particular + */ case class Parameters( printer: Printer = Printer( System.out, @@ -728,7 +733,9 @@ object Interpreter{ initialClassLoader: ClassLoader = null, importHooks: Map[Seq[String], ImportHook] = ImportHook.defaults, alreadyLoadedDependencies: Seq[Dependency] = Nil, - classPathWhitelist: Set[Seq[String]] = Set.empty + classPathWhitelist: Set[Seq[String]] = Set.empty, + wrapperNamePrefix: String = "cmd", + warnings: Boolean = false ) val predefImports = Imports( diff --git a/amm/interp/src/main/scala/ammonite/interp/script/AmmoniteBuildServer.scala b/amm/interp/src/main/scala/ammonite/interp/script/AmmoniteBuildServer.scala index e2e3c3e63..d5865786c 100644 --- a/amm/interp/src/main/scala/ammonite/interp/script/AmmoniteBuildServer.scala +++ b/amm/interp/src/main/scala/ammonite/interp/script/AmmoniteBuildServer.scala @@ -126,7 +126,6 @@ class AmmoniteBuildServer( clientOpt = Some(client) } - def buildInitialize(params: InitializeBuildParams): CompletableFuture[InitializeBuildResult] = nonBlocking { diff --git a/amm/interp/src/main/scala/ammonite/interp/script/DummyBuildServerImplems.scala b/amm/interp/src/main/scala/ammonite/interp/script/DummyBuildServerImplems.scala index f5c15ef30..dfc7ab9f2 100644 --- a/amm/interp/src/main/scala/ammonite/interp/script/DummyBuildServerImplems.scala +++ b/amm/interp/src/main/scala/ammonite/interp/script/DummyBuildServerImplems.scala @@ -5,8 +5,13 @@ import ch.epfl.scala.bsp4j._ import scala.collection.JavaConverters._ private[script] trait DummyBuildServerImplems extends BuildServer with ScalaBuildServer { + override def buildTargetDependencyModules(dmp: DependencyModulesParams): CompletableFuture[DependencyModulesResult] = + CompletableFuture.completedFuture(new DependencyModulesResult(List.empty.asJava)) - def buildTargetResources(params: ResourcesParams): CompletableFuture[ResourcesResult] = { + override def buildTargetOutputPaths(opp: OutputPathsParams): CompletableFuture[OutputPathsResult] = + CompletableFuture.completedFuture(new OutputPathsResult(List.empty.asJava)) + + override def buildTargetResources(params: ResourcesParams): CompletableFuture[ResourcesResult] = { val items = params.getTargets.asScala.toList.map { target => new ResourcesItem(target, List.empty[String].asJava) } @@ -14,20 +19,19 @@ private[script] trait DummyBuildServerImplems extends BuildServer with ScalaBuil CompletableFuture.completedFuture(result) } - def buildTargetRun(params: RunParams): CompletableFuture[RunResult] = { + override def buildTargetRun(params: RunParams): CompletableFuture[RunResult] = { val result = new RunResult(StatusCode.ERROR) result.setOriginId(params.getOriginId) CompletableFuture.completedFuture(result) } - def buildTargetTest(params: TestParams): CompletableFuture[TestResult] = { + + override def buildTargetTest(params: TestParams): CompletableFuture[TestResult] = { val result = new TestResult(StatusCode.ERROR) result.setOriginId(params.getOriginId) CompletableFuture.completedFuture(result) } - def onBuildExit(): Unit = () - def onBuildInitialized(): Unit = () - def buildTargetScalaMainClasses( + override def buildTargetScalaMainClasses( params: ScalaMainClassesParams ): CompletableFuture[ScalaMainClassesResult] = { val items = params.getTargets.asScala.map { target => @@ -36,7 +40,8 @@ private[script] trait DummyBuildServerImplems extends BuildServer with ScalaBuil val result = new ScalaMainClassesResult(items.asJava) CompletableFuture.completedFuture(result) } - def buildTargetScalaTestClasses( + + override def buildTargetScalaTestClasses( params: ScalaTestClassesParams ): CompletableFuture[ScalaTestClassesResult] = { val items = params.getTargets.asScala.map { target => @@ -46,4 +51,12 @@ private[script] trait DummyBuildServerImplems extends BuildServer with ScalaBuil CompletableFuture.completedFuture(result) } + override def debugSessionStart(dsp: DebugSessionParams): CompletableFuture[DebugSessionAddress] = + CompletableFuture.completedFuture(new DebugSessionAddress("")) + + override def onBuildExit(): Unit = () + override def onBuildInitialized(): Unit = () + + override def workspaceReload(): CompletableFuture[Object] = + CompletableFuture.completedFuture(new {}) } diff --git a/amm/repl/src/main/scala/ammonite/repl/ApiImpls.scala b/amm/repl/src/main/scala/ammonite/repl/ApiImpls.scala index 07c13b74c..ca174799c 100644 --- a/amm/repl/src/main/scala/ammonite/repl/ApiImpls.scala +++ b/amm/repl/src/main/scala/ammonite/repl/ApiImpls.scala @@ -28,7 +28,8 @@ class SessionApiImpl(frames0: => StableRef[List[Frame]]) extends Session{ ), parent.imports, parent.classpath, - parent.usedEarlierDefinitions + parent.usedEarlierDefinitions, + parent.hooks ) def save(name: String = "") = { diff --git a/amm/repl/src/main/scala/ammonite/repl/Repl.scala b/amm/repl/src/main/scala/ammonite/repl/Repl.scala index 7b6e28382..1202a9ba2 100644 --- a/amm/repl/src/main/scala/ammonite/repl/Repl.scala +++ b/amm/repl/src/main/scala/ammonite/repl/Repl.scala @@ -32,7 +32,8 @@ class Repl(input: InputStream, parser: Parser, initialClassLoader: ClassLoader = classOf[ammonite.repl.api.ReplAPI].getClassLoader, - classPathWhitelist: Set[Seq[String]]) { repl => + classPathWhitelist: Set[Seq[String]], + warnings: Boolean) { repl => val prompt = Ref("@ ") @@ -57,7 +58,7 @@ class Repl(input: InputStream, """ }.mkString(newLine) - val frames = Ref(List(Frame.createInitial(initialClassLoader))) + val frames = Ref(List(ammonite.runtime.Frame.createInitial(initialClassLoader))) /** * The current line number of the REPL, used to make sure every snippet @@ -82,7 +83,8 @@ class Repl(input: InputStream, initialClassLoader = initialClassLoader, importHooks = importHooks, classPathWhitelist = classPathWhitelist, - alreadyLoadedDependencies = alreadyLoadedDependencies + alreadyLoadedDependencies = alreadyLoadedDependencies, + warnings = warnings ) val interp = new Interpreter( compilerBuilder, @@ -177,7 +179,9 @@ class Repl(input: InputStream, case ex => Res.Exception(ex, "") } - + // workaround to wildcard imports breaking code completion, see + // https://github.com/com-lihaoyi/Ammonite/issues/1009 + importsForCompletion = Imports(fullImports.value.filter(_.fromName.raw != "package")) _ <- Signaller("INT") { // Put a fake `ThreadDeath` error in `lastException`, because `Thread#stop` // raises an error with the stack trace of *this interrupt thread*, rather @@ -192,7 +196,7 @@ class Repl(input: InputStream, output, colors().prompt()(prompt()).render, colors(), - interp.compilerManager.complete(_, fullImports.toString, _), + interp.compilerManager.complete(_, importsForCompletion.toString, _), storage.fullHistory(), addHistory = (code) => if (code != "") { storage.fullHistory() = storage.fullHistory() :+ code diff --git a/amm/repl/src/test/scala/ammonite/DualTestRepl.scala b/amm/repl/src/test/scala/ammonite/DualTestRepl.scala index 08007f7f9..75ad62ecf 100644 --- a/amm/repl/src/test/scala/ammonite/DualTestRepl.scala +++ b/amm/repl/src/test/scala/ammonite/DualTestRepl.scala @@ -9,15 +9,22 @@ import ammonite.util.{Evaluated, Res} class DualTestRepl { dual => def predef: (String, Option[os.Path]) = ("", None) + def wrapperNamePrefix = Option.empty[String] + + def warnings = true def compilerBuilder = ammonite.compiler.CompilerBuilder() val repls = Seq( new TestRepl(compilerBuilder) { override def predef = dual.predef + override def wrapperNamePrefix = dual.wrapperNamePrefix + override def warnings = dual.warnings }, new TestRepl(compilerBuilder) { override def predef = dual.predef override def codeWrapper = CodeClassWrapper + override def wrapperNamePrefix = dual.wrapperNamePrefix + override def warnings = dual.warnings } ) diff --git a/amm/repl/src/test/scala/ammonite/TestRepl.scala b/amm/repl/src/test/scala/ammonite/TestRepl.scala index 3c907e472..63ee33adf 100644 --- a/amm/repl/src/test/scala/ammonite/TestRepl.scala +++ b/amm/repl/src/test/scala/ammonite/TestRepl.scala @@ -29,6 +29,8 @@ class TestRepl(compilerBuilder: ICompilerBuilder = CompilerBuilder()) { self => var allOutput = "" def predef: (String, Option[os.Path]) = ("", None) def codeWrapper: CodeWrapper = DefaultCodeWrapper + def wrapperNamePrefix = Option.empty[String] + def warnings = true val tempDir = os.Path( java.nio.file.Files.createTempDirectory("ammonite-tester") @@ -77,7 +79,9 @@ class TestRepl(compilerBuilder: ICompilerBuilder = CompilerBuilder()) { self => initialClassLoader = initialClassLoader, alreadyLoadedDependencies = Defaults.alreadyLoadedDependencies("amm-test-dependencies.txt"), importHooks = ImportHook.defaults, - classPathWhitelist = ammonite.repl.Repl.getClassPathWhitelist(thin = true) + classPathWhitelist = ammonite.repl.Repl.getClassPathWhitelist(thin = true), + wrapperNamePrefix = wrapperNamePrefix.getOrElse(Interpreter.Parameters().wrapperNamePrefix), + warnings = warnings ) val interp = try { new Interpreter( @@ -267,7 +271,9 @@ class TestRepl(compilerBuilder: ICompilerBuilder = CompilerBuilder()) { self => val strippedExpected = expected.stripPrefix("warning: ") assert(warning.contains(strippedExpected)) - }else if (expected.startsWith("info: ")){ + }else if (expected == "warning:") + assert(warning.isEmpty) + else if (expected.startsWith("info: ")){ val strippedExpected = expected.stripPrefix("info: ") assert(info.contains(strippedExpected)) diff --git a/amm/repl/src/test/scala/ammonite/session/AdvancedTests.scala b/amm/repl/src/test/scala/ammonite/session/AdvancedTests.scala index 9420be445..a927094cf 100644 --- a/amm/repl/src/test/scala/ammonite/session/AdvancedTests.scala +++ b/amm/repl/src/test/scala/ammonite/session/AdvancedTests.scala @@ -695,5 +695,170 @@ object AdvancedTests extends TestSuite{ val files = os.walk(dir).filter(os.isFile(_)).map(_.relativeTo(dir)) assert(files.sorted == expectedFiles.sorted) } + test("comment and import") { + check.session( + """ + @ import $ivy.`org.typelevel::cats-kernel:2.6.1` + + @ { + @ // hello + @ import cats.kernel._ + @ } + import cats.kernel._ + """ + ) + } + test("hook in block") { + check.session( + """ + @ { + @ import $ivy.`org.typelevel::cats-kernel:2.6.1` + @ } + import $ivy.$ + + @ import cats.kernel._ + import cats.kernel._ + """ + ) + } + test("class-path-hook") { + val sbv = check.scalaBinaryVersion + check.session( + s""" + @ var deps = Set.empty[String] + + @ repl.sess.frames.head.addHook { + @ new ammonite.util.Frame.Hook { + @ def addClasspath(additional: Seq[java.net.URL]): Unit = { + @ deps = deps ++ additional.map(_.toString).filter(!_.endsWith("-sources.jar")).map(url => url.drop(url.lastIndexOf('/') + 1)) + @ } + @ } + @ } + + @ import $$ivy.`org.typelevel::cats-core:2.9.0` + + @ val firstExpectedDeps = Set( + @ "cats-core_$sbv-2.9.0.jar", + @ "cats-kernel_$sbv-2.9.0.jar" + @ ) + + @ val firstCheck = deps == firstExpectedDeps + firstCheck: Boolean = true + + @ deps = Set.empty[String] + + @ import $$ivy.`org.typelevel::cats-core:2.9.0` + + @ val firstEmptyCheck = deps.isEmpty + firstEmptyCheck: Boolean = true + + @ deps = Set.empty[String] + + @ interp.load.ivy("info.picocli" % "picocli" % "4.7.3") + + @ val secondExpectedDeps = Set( + @ "picocli-4.7.3.jar" + @ ) + + @ val secondCheck = deps == secondExpectedDeps + secondCheck: Boolean = true + + @ deps = Set.empty[String] + + @ interp.load.ivy("info.picocli" % "picocli" % "4.7.3") + + @ val secondEmptyCheck = deps.isEmpty + secondEmptyCheck: Boolean = true + """ + ) + } + + test("custom wrapper name prefix") { + val check = new DualTestRepl { + override def wrapperNamePrefix = Some("cell") + } + // Helper suffix stripped for class-based code wrapping + check.session( + """ + @ val clsName = getClass.getName.stripPrefix("ammonite.$sess.").stripSuffix("Helper") + clsName: String = "cell0$" + """ + ) + } + + test("warnings") { + + val checkWithoutWarnings = new DualTestRepl { + override def warnings = false + } + + checkWithoutWarnings.session( + """ + @ @deprecated("foo", "1.2") def value(): Int = 2 + + @ val n = value() + warning: + + @ val n0 = n + n0: Int = 2 + """ + ) + + val objCheck = new TestRepl + val clsCheck = new TestRepl { + override def codeWrapper = ammonite.compiler.CodeClassWrapper + } + + if (scala2) { + objCheck.session( + """ + @ @deprecated("foo", "1.2") def value(): Int = 2 + defined function value + + @ val n = value() + warning: cmd1.sc:1: method value in object cmd0 is deprecated (since 1.2): foo + val n = value() + ^ + """ + ) + clsCheck.session( + """ + @ @deprecated("foo", "1.2") def value(): Int = 2 + defined function value + + @ val n = value() + warning: cmd1.sc:1: method value in class Helper is deprecated (since 1.2): foo + val n = value() + ^ + """ + ) + } + else { + objCheck.session( + """ + @ @deprecated("foo", "1.2") def value(): Int = 2 + defined function value + + @ val n = value() + warning: -- Warning: cmd1.sc:1:8 -------------------------------------------------------- + 1 |val n = value() + | ^^^^^ + | method value in object cmd0 is deprecated since 1.2: foo + """ + ) + clsCheck.session( + """ + @ @deprecated("foo", "1.2") def value(): Int = 2 + defined function value + + @ val n = value() + warning: -- Warning: cmd1.sc:1:8 -------------------------------------------------------- + 1 |val n = value() + | ^^^^^ + | method value in class Helper is deprecated since 1.2: foo + """ + ) + } + } } } diff --git a/amm/repl/src/test/scala/ammonite/session/FailureTests.scala b/amm/repl/src/test/scala/ammonite/session/FailureTests.scala index 1f36bcccb..d8d8b1c0c 100644 --- a/amm/repl/src/test/scala/ammonite/session/FailureTests.scala +++ b/amm/repl/src/test/scala/ammonite/session/FailureTests.scala @@ -77,5 +77,54 @@ object FailureTests extends TestSuite{ !x.contains("Something unexpected went wrong =(") ) } + + test("lineNumbersInStackTrace1") { + if (check.scala2) { + check.fail( + """ + | + | + | 1 / 0 + |""".stripMargin, x => + x.contains("/ by zero") && + x.contains("cmd0.sc:4") // check that the line number is correct + + ) + } + } + + test("lineNumbersInStackTrace2") { + if (check.scala2) { + check.fail( + """ + |{ + | + | // block command + | 1 / 0 + |} + |""".stripMargin, x => + x.contains("/ by zero") && + x.contains("cmd0.sc:5") // check that the line number is correct + ) + } + } + + test("wrapperNameInStackTrace") { + val check = new DualTestRepl { + override def wrapperNamePrefix = Some("cell") + } + check.fail( + """{ + | + | // block command + | 1 / 0 + |} + |""".stripMargin, + x => + x.contains("/ by zero") && + !x.contains("cmd0.sc:") && + x.contains("cell0.sc:") + ) + } } } diff --git a/amm/runtime/src/main/scala/ammonite/runtime/ClassLoaders.scala b/amm/runtime/src/main/scala/ammonite/runtime/ClassLoaders.scala index c9110af25..e1007094f 100644 --- a/amm/runtime/src/main/scala/ammonite/runtime/ClassLoaders.scala +++ b/amm/runtime/src/main/scala/ammonite/runtime/ClassLoaders.scala @@ -9,6 +9,7 @@ import java.util.Collections import ammonite.util.{Imports, Util} +import scala.annotation.tailrec import scala.collection.mutable @@ -25,7 +26,8 @@ class Frame(val classloader: SpecialClassLoader, val pluginClassloader: SpecialClassLoader, private[this] var imports0: Imports, private[this] var classpath0: Seq[java.net.URL], - private[this] var usedEarlierDefinitions0: Seq[String]) extends ammonite.util.Frame{ + private[this] var usedEarlierDefinitions0: Seq[String], + private[this] var hooks0: Seq[ammonite.util.Frame.Hook]) extends ammonite.util.Frame{ private var frozen0 = false def frozen = frozen0 def freeze(): Unit = { @@ -48,8 +50,14 @@ class Frame(val classloader: SpecialClassLoader, def addClasspath(additional: Seq[java.net.URL]) = { if (!frozen0) { version0 += 1 - additional.foreach(classloader.add) + val actualAdditional = additional + .iterator + .map(url => (url, classloader.add(url))) + .filter(_._2) + .map(_._1) + .toVector classpath0 = classpath0 ++ additional + hooks.foreach(_.addClasspath(actualAdditional)) } } def addPluginClasspath(additional: Seq[java.net.URL]) = { @@ -60,6 +68,10 @@ class Frame(val classloader: SpecialClassLoader, } def usedEarlierDefinitions_=(usedEarlierDefinitions: Seq[String]): Unit = usedEarlierDefinitions0 = usedEarlierDefinitions + def hooks: Seq[ammonite.util.Frame.Hook] = hooks0 + def addHook(hook: ammonite.util.Frame.Hook): Unit = { + hooks0 = hooks0 :+ hook + } } object Frame{ def createInitial(baseClassLoader: ClassLoader = Thread.currentThread().getContextClassLoader) = { @@ -77,7 +89,7 @@ object Frame{ likelyJdkSourceLocation.wrapped.toUri.toURL ) - new Frame(special, special, Imports(), Seq(), Seq()) + new Frame(special, special, Imports(), Seq(), Seq(), Seq()) } } @@ -240,10 +252,27 @@ class SpecialClassLoader(parent: ClassLoader, } else super.findClass(name) } - def add(url: URL) = { - classpathSignature0 = classpathSignature0 ++ Seq(jarSignature(url)) - this.addURL(url) + def hasUrl(url: URL): Boolean = { + @tailrec + def viaParents(loader: ClassLoader = parent): Boolean = { + val hasUrl0 = loader match { + case null => false + case s: SpecialClassLoader => s.getURLs.contains(url) + case _ => false + } + if (hasUrl0) true + else if (loader == null) false + else viaParents(loader.getParent) + } + getURLs.contains(url) || viaParents() } + def add(url: URL): Boolean = + if (hasUrl(url)) false + else { + classpathSignature0 = classpathSignature0 ++ Seq(jarSignature(url)) + this.addURL(url) + true + } override def close() = { // DO NOTHING LOLZ diff --git a/amm/runtime/src/main/scala/ammonite/runtime/ImportHook.scala b/amm/runtime/src/main/scala/ammonite/runtime/ImportHook.scala index beb5ecfb2..8e0a3a5ce 100644 --- a/amm/runtime/src/main/scala/ammonite/runtime/ImportHook.scala +++ b/amm/runtime/src/main/scala/ammonite/runtime/ImportHook.scala @@ -8,7 +8,11 @@ import ammonite.util.Util.CodeSource import ammonite.util._ import coursier.cputil.ClassPathUtil import coursierapi.{Dependency, IvyRepository, MavenRepository, Repository} +import dependency.ScalaParameters +import dependency.api.ops._ +import dependency.parser.DependencyParser +import scala.collection.JavaConverters._ import scala.util.{Failure, Success, Try} /** @@ -180,25 +184,16 @@ object ImportHook{ val (dottyCompat, coords) = if (signature.endsWith(" compat")) (true, signature.stripSuffix(" compat")) else (false, signature) - coords.split(':') match{ - case Array(a, b, c) => - Right(Dependency.of(a, b, c)) - case Array(a, "", b, c) => - val sbv = - if (dottyCompat && interp.scalaVersion.startsWith("3.")) "2.13" - else IvyConstructor.scalaBinaryVersion(interp.scalaVersion) - Right(Dependency.of(a, b + "_" + sbv, c)) - case Array(a, "", "", b, c) => - val sv = - // FIXME We may need to bump that version from time to time, or - // to use a different one, depending on the 3.x version. - if (dottyCompat && interp.scalaVersion.startsWith("3.")) - // Should be the 2.13 version we want - scala.util.Properties.versionNumberString - else - interp.scalaVersion - Right(Dependency.of(a, b + "_" + sv, c)) - case _ => Left(signature) + DependencyParser.parse(coords).map { dep => + val scalaVersion = + if ((dottyCompat || dep.userParams.get("compat").nonEmpty) && !interp.scalaVersion.startsWith("2.")) + // When dotty compatibility is enabled, pull Scala 2.13 dependencies rather than Scala 3 ones. + // versionNumberString gives us the right 2.13 version for the current Scala 3 version. + scala.util.Properties.versionNumberString + else + interp.scalaVersion + val params = ScalaParameters(scalaVersion) + dep.applyParams(params).toCs } } val errors = splitted.collect{case Left(error) => error} diff --git a/amm/src/main/scala/ammonite/AmmoniteMain.scala b/amm/src/main/scala/ammonite/AmmoniteMain.scala index 13157f927..aded51887 100644 --- a/amm/src/main/scala/ammonite/AmmoniteMain.scala +++ b/amm/src/main/scala/ammonite/AmmoniteMain.scala @@ -1,22 +1,15 @@ package ammonite import java.io.{InputStream, OutputStream, PrintStream} -import java.net.URLClassLoader -import java.nio.file.NoSuchFileException -import ammonite.compiler.{CodeClassWrapper, DefaultCodeWrapper} -import ammonite.compiler.iface.{CodeWrapper, CompilerBuilder, Parser} -import ammonite.interp.{Watchable, Interpreter, PredefInitialization} +import ammonite.compiler.DefaultCodeWrapper +import ammonite.compiler.iface.CompilerBuilder +import ammonite.interp.PredefInitialization import ammonite.interp.script.AmmoniteBuildServer -import ammonite.runtime.{Frame, Storage} import ammonite.main._ -import ammonite.repl.{FrontEndAPIImpl, Repl} import ammonite.util.Util.newLine import ammonite.util._ -import scala.annotation.tailrec -import ammonite.runtime.ImportHook -import coursierapi.Dependency import scala.concurrent.Await import scala.concurrent.duration.Duration diff --git a/amm/src/main/scala/ammonite/Main.scala b/amm/src/main/scala/ammonite/Main.scala index 4b0ca3b99..002a33b15 100644 --- a/amm/src/main/scala/ammonite/Main.scala +++ b/amm/src/main/scala/ammonite/Main.scala @@ -85,7 +85,8 @@ case class Main(predefCode: String = "", compilerBuilder: CompilerBuilder = ammonite.compiler.CompilerBuilder(), // by-name, so that fastparse isn't loaded when we don't need it parser: () => Parser = () => ammonite.compiler.Parsers, - classPathWhitelist: Set[Seq[String]] = Set.empty){ + classPathWhitelist: Set[Seq[String]] = Set.empty, + warnings: Boolean = false){ def loadedPredefFile = predefFile match{ case Some(path) => @@ -149,7 +150,8 @@ case class Main(predefCode: String = "", compilerBuilder = compilerBuilder, parser = parser(), initialClassLoader = initialClassLoader, - classPathWhitelist = classPathWhitelist + classPathWhitelist = classPathWhitelist, + warnings = warnings ) } @@ -182,7 +184,8 @@ case class Main(predefCode: String = "", initialClassLoader = initialClassLoader, importHooks = importHooks, classPathWhitelist = classPathWhitelist, - alreadyLoadedDependencies = alreadyLoadedDependencies + alreadyLoadedDependencies = alreadyLoadedDependencies, + warnings = warnings ) val interp = new Interpreter( compilerBuilder, diff --git a/amm/src/main/scala/ammonite/MainRunner.scala b/amm/src/main/scala/ammonite/MainRunner.scala index 46027c91a..086a3055d 100644 --- a/amm/src/main/scala/ammonite/MainRunner.scala +++ b/amm/src/main/scala/ammonite/MainRunner.scala @@ -146,7 +146,8 @@ class MainRunner(cliConfig: Config, if (cliConfig.repl.tmpOutputDirectory.value) Some(os.temp.dir(prefix = "ammonite-output").toNIO) else None } - ) + ), + warnings = !cliConfig.core.noWarnings.value ) } diff --git a/amm/src/main/scala/ammonite/main/Config.scala b/amm/src/main/scala/ammonite/main/Config.scala index 05df1c92b..3ccfb050e 100644 --- a/amm/src/main/scala/ammonite/main/Config.scala +++ b/amm/src/main/scala/ammonite/main/Config.scala @@ -55,7 +55,9 @@ object Config{ @arg(doc = "Print this message") help: Flag, @arg(name = "version", short = 'v', doc = "Show Ammonite's version") - showVersion: Flag + showVersion: Flag, + @arg(name = "no-warn", doc = "Disable compiler warnings") + noWarnings: Flag ) implicit val coreParser = ParserForClass[Core] diff --git a/amm/util/src/main/scala/ammonite/util/Frame.scala b/amm/util/src/main/scala/ammonite/util/Frame.scala index 8d0a8af46..4c528b4d8 100644 --- a/amm/util/src/main/scala/ammonite/util/Frame.scala +++ b/amm/util/src/main/scala/ammonite/util/Frame.scala @@ -8,4 +8,15 @@ trait Frame { def classpath: Seq[URL] def version: Int + + /** Adds a [[Frame.Hook]] to be called every time JARs are added to the class path */ + def addHook(hook: Frame.Hook): Unit +} + +object Frame { + /** A hook that can be called every time JARs are added to the class path */ + trait Hook { + /** Called when new JARs are added to the class path */ + def addClasspath(additional: Seq[java.net.URL]): Unit + } } diff --git a/build.sc b/build.sc index c12bf7e29..599c8680c 100644 --- a/build.sc +++ b/build.sc @@ -42,16 +42,18 @@ val commitsSinceTaggedVersion = { // don't. val cross2_3Version = (scala3Ver: String) => if (scala3Ver.startsWith("3.0.") || scala3Ver.startsWith("3.1.")) "2.13.7" - else "2.13.10" + else if (scala3Ver.startsWith("3.2.")) "2.13.10" + else "2.13.11" -val scala2_12Versions = Seq("2.12.8", "2.12.9", "2.12.10", "2.12.11", "2.12.12", "2.12.13", "2.12.14", "2.12.15", "2.12.16", "2.12.17") -val scala2_13Versions = Seq("2.13.0", "2.13.1", "2.13.2", "2.13.3", "2.13.4", "2.13.5", "2.13.6", "2.13.7", "2.13.8", "2.13.9", "2.13.10") +val scala2_12Versions = Seq("2.12.8", "2.12.9", "2.12.10", "2.12.11", "2.12.12", "2.12.13", "2.12.14", "2.12.15", "2.12.16", "2.12.17", "2.12.18") +val scala2_13Versions = Seq("2.13.0", "2.13.1", "2.13.2", "2.13.3", "2.13.4", "2.13.5", "2.13.6", "2.13.7", "2.13.8", "2.13.9", "2.13.10", "2.13.11", "2.13.12") val scala31Versions = Seq("3.1.0", "3.1.1", "3.1.2", "3.1.3") val scala32Versions = Seq("3.2.0", "3.2.1", "3.2.2") -val scala3Versions = scala31Versions ++ scala32Versions +val scala33Versions = Seq("3.3.0", "3.3.1") +val scala3Versions = scala31Versions ++ scala32Versions ++ scala33Versions -val binCrossScalaVersions = Seq(scala2_12Versions.last, scala2_13Versions.last, scala31Versions.last, scala32Versions.last) +val binCrossScalaVersions = Seq(scala2_12Versions.last, scala2_13Versions.last, scala31Versions.last, scala32Versions.last, scala33Versions.last) def isScala2_12_10OrLater(sv: String): Boolean = { (sv.startsWith("2.12.") && sv.stripPrefix("2.12.").length > 1) || (sv.startsWith("2.13.") && sv != "2.13.0") } @@ -73,17 +75,18 @@ val (buildVersion, unstable) = scala.util.Try( case Some(tagName) => (tagName, false) } -val bspVersion = "2.0.0-M6" +val bspVersion = "2.1.0-M5" val fastparseVersion = "3.0.0" -val scalametaVersion = "4.5.13" +val scalametaVersion = "4.8.5" object Deps { - val acyclic = ivy"com.lihaoyi:::acyclic:0.3.6" + val acyclic = ivy"com.lihaoyi:::acyclic:0.3.9" val bsp4j = ivy"ch.epfl.scala:bsp4j:${bspVersion}" val bcprovJdk15on = ivy"org.bouncycastle:bcprov-jdk15on:1.56" val cask = ivy"com.lihaoyi::cask:0.6.0" val classPathUtil = ivy"io.get-coursier::class-path-util:0.1.4" val coursierInterface = ivy"io.get-coursier:interface:1.0.16" + val coursierDependencyInterface = ivy"io.get-coursier::dependency-interface:0.2.3" val fastparse = ivy"com.lihaoyi::fastparse:$fastparseVersion" val geny = ivy"com.lihaoyi::geny:1.0.0" val javaparserCore = ivy"com.github.javaparser:javaparser-core:3.2.5" @@ -270,8 +273,10 @@ trait AmmInternalModule extends CrossSbtModule with Bloop.Module{ val extraDir3 = if (isScala2()) { val dir = - if (sv.startsWith("2.13.") && sv != "2.13.0") - millSourcePath / "src" / "main" / "scala-2.13.1+" + if (sv.startsWith("2.13.") && sv.stripPrefix("2.13.").toInt >= 1 && sv.stripPrefix("2.13.").toInt <= 11) + millSourcePath / "src" / "main" / "scala-2.13.1-2.13.11" + else if (sv.startsWith("2.13.") && sv.stripPrefix("2.13.").toInt >= 12) + millSourcePath / "src" / "main" / "scala-2.13.12+" else if (sv.startsWith("2.12.") && sv.stripPrefix("2.12.").toInt >= 13) millSourcePath / "src" / "main" / "scala-2.12.13+" else @@ -419,7 +424,8 @@ object amm extends Cross[MainModule](fullCrossScalaVersions:_*){ Deps.classPathUtil, Deps.upickle, Deps.requests, - Deps.mainargs.use_3(crossScalaVersion) + Deps.mainargs.use_3(crossScalaVersion), + Deps.coursierDependencyInterface ) }