diff --git a/.gitignore b/.gitignore index 94fe5777..1d3b2849 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ target/ .settings/ *.sublime-workspace sonatype.sbt +.bsp/ diff --git a/build.sbt b/build.sbt index 03c60f5b..f58c3076 100644 --- a/build.sbt +++ b/build.sbt @@ -4,9 +4,8 @@ scalaVersion in ThisBuild := "2.11.12" crossScalaVersions in ThisBuild := Seq("2.11.12", "2.12.12", "2.13.3") //scalaJSUseRhino in ThisBuild := true -lazy val scalatest = "org.scalatest" %% "scalatest" % "3.2.2" -lazy val specs2 = "org.specs2" %% "specs2-core" % "4.10.5" -lazy val scalameta = "org.scalameta" %% "scalameta" % "4.3.24" +lazy val scalatest = Def.setting("org.scalatest" %%% "scalatest" % "3.2.2") +lazy val specs2 = Def.setting("org.specs2" %%% "specs2-core" % "4.10.5") val commonSettings = Defaults.coreDefaultSettings ++ Seq( unmanagedSourceDirectories in Compile ++= { @@ -27,13 +26,12 @@ lazy val scalamock = crossProject(JSPlatform, JVMPlatform) in file(".") settings publishArtifact in (Compile, packageDoc) := true, publishArtifact in (Compile, packageSrc) := true, publishArtifact in Test := false, - scalacOptions in (Compile, doc) ++= Opts.doc.title("ScalaMock") ++ + scalacOptions in (Compile, doc) ++= Opts.doc.title("ScalaMock") ++ Opts.doc.version(version.value) ++ Seq("-doc-root-content", "rootdoc.txt", "-version"), libraryDependencies ++= Seq( "org.scala-lang" % "scala-reflect" % scalaVersion.value, - scalameta, - scalatest % Optional, - specs2 % Optional + scalatest.value % Optional, + specs2.value % Optional ) ) @@ -45,7 +43,7 @@ lazy val examples = project in file("examples") settings( name := "ScalaMock Examples", skip in publish := true, libraryDependencies ++= Seq( - scalatest % Test, - specs2 % Test + scalatest.value % Test, + specs2.value % Test ) ) dependsOn scalamock.jvm diff --git a/js/src/main/scala/org/scalamock/clazz/MockFunctionFinderImpl.scala b/js/src/main/scala/org/scalamock/clazz/MockFunctionFinderImpl.scala index 9ee7db81..efe27baf 100644 --- a/js/src/main/scala/org/scalamock/clazz/MockFunctionFinderImpl.scala +++ b/js/src/main/scala/org/scalamock/clazz/MockFunctionFinderImpl.scala @@ -29,66 +29,23 @@ object MockFunctionFinderImpl { def mockedFunctionGetter[M: c.WeakTypeTag](c: Context) (obj: c.Tree, name: c.Name, targs: List[c.Type], actuals: List[c.universe.Type]): c.Expr[M] = { import c.universe._ - val utils = new MacroUtils[c.type](c) - import utils._ - - def hasValueTypeArgs(baseSymbol: Symbol, owner: Type): Boolean = { - val baseType = owner.baseType(baseSymbol) - baseType.typeArgs.nonEmpty // && baseType.typeArgs.forall(_ <:< typeOf[AnyVal]) - } - - // this somehow replicates postfix logic from scala-js, there have to be a better way - def privateSuffix(owner: Tree): String = { - val objectSymbol = c.typeOf[Object].typeSymbol - - val tpe = owner.tpe - - val baseNonInterfaceParentCount = tpe.baseClasses.count( symbol => - symbol != objectSymbol && (symbol.isClass && !symbol.asClass.isTrait) - ) - - // this was found in experiments and should be considered as magical cosmological constant - val haveBaseClassWithValTypeArgs = tpe.typeSymbol.owner != null && tpe.typeSymbol.owner.isPackage && - tpe.baseClasses.exists(hasValueTypeArgs(_, owner.tpe)) - - val idx = baseNonInterfaceParentCount + (if (haveBaseClassWithValTypeArgs) 1 else 0) - "$" + idx.toString - } - - def encodeMemberNameInternal(s: String): String = { - s.replace("_", "$und") - } - -// def printTypeSymbol(t: Type): String = { -// t.toString + "[" + t.baseClasses.map( base ⇒ -// t.baseType(base).toString -// ).mkString(",") + "]" -// } def mockFunctionName(name: Name, t: Type, targs: List[Type]) = { val method = t.member(name).asTerm - val nameStr = encodeMemberNameInternal(name.toString) - if (method.isOverloaded) - "mock$" + nameStr + "$" + method.alternatives.indexOf(MockFunctionFinder.resolveOverloaded(c)(method, targs, actuals)) + "mock$" + name + "$" + method.alternatives.indexOf(MockFunctionFinder.resolveOverloaded(c)(method, targs, actuals)) else - "mock$" + nameStr + "$0" + "mock$" + name + "$0" } - val fullName = mockFunctionName(name, obj.tpe, targs) + privateSuffix(obj) - val fld = freshTerm("fld") + val fullName = TermName(mockFunctionName(name, obj.tpe, targs)) - val code = c.Expr[M](q"""{ + val code = c.Expr[M]( + q"""{ import scala.scalajs.js - val $fld = js.Object.getOwnPropertyDescriptor($obj.asInstanceOf[js.Object], $fullName) - if (js.isUndefined($fld)) { - throw new IllegalArgumentException("Property '" + $fullName + "' is not defined in '" + $obj + "'. Available properties: " + - js.Object.getOwnPropertyNames($obj.asInstanceOf[js.Object]).mkString(",") - ) - } - $fld.value.asInstanceOf[${weakTypeOf[M]}] + $obj.asInstanceOf[js.Dynamic].$fullName.asInstanceOf[${weakTypeOf[M]}] }""") - // println(code) + code } } diff --git a/shared/src/test/scala/com/paulbutcher/test/matchers/MatchAnyTest.scala b/jvm/src/test/scala/com.paulbutcher.test/matchers/MatchAnyTest.scala similarity index 100% rename from shared/src/test/scala/com/paulbutcher/test/matchers/MatchAnyTest.scala rename to jvm/src/test/scala/com.paulbutcher.test/matchers/MatchAnyTest.scala diff --git a/shared/src/test/scala/org/scalamock/test/scalatest/AsyncMockFactoryNoDuplicatedRun.scala b/jvm/src/test/scala/org/scalamock/test/scalatest/AsyncMockFactoryNoDuplicatedRun.scala similarity index 100% rename from shared/src/test/scala/org/scalamock/test/scalatest/AsyncMockFactoryNoDuplicatedRun.scala rename to jvm/src/test/scala/org/scalamock/test/scalatest/AsyncMockFactoryNoDuplicatedRun.scala diff --git a/shared/src/test/scala/org/scalamock/test/scalatest/ConcurrencyTest.scala b/jvm/src/test/scala/org/scalamock/test/scalatest/ConcurrencyTest.scala similarity index 100% rename from shared/src/test/scala/org/scalamock/test/scalatest/ConcurrencyTest.scala rename to jvm/src/test/scala/org/scalamock/test/scalatest/ConcurrencyTest.scala diff --git a/shared/src/test/scala/org/scalamock/test/scalatest/MixedMockFactoryTest.scala b/jvm/src/test/scala/org/scalamock/test/scalatest/MixedMockFactoryTest.scala similarity index 100% rename from shared/src/test/scala/org/scalamock/test/scalatest/MixedMockFactoryTest.scala rename to jvm/src/test/scala/org/scalamock/test/scalatest/MixedMockFactoryTest.scala diff --git a/shared/src/test/scala/org/scalamock/test/scalatest/SuiteScopeProxyMockTest.scala b/jvm/src/test/scala/org/scalamock/test/scalatest/SuiteScopeProxyMockTest.scala similarity index 100% rename from shared/src/test/scala/org/scalamock/test/scalatest/SuiteScopeProxyMockTest.scala rename to jvm/src/test/scala/org/scalamock/test/scalatest/SuiteScopeProxyMockTest.scala diff --git a/shared/src/test/scala/org/scalamock/test/specs2/ConcurrencyTest.scala b/jvm/src/test/scala/org/scalamock/test/specs2/ConcurrencyTest.scala similarity index 100% rename from shared/src/test/scala/org/scalamock/test/specs2/ConcurrencyTest.scala rename to jvm/src/test/scala/org/scalamock/test/specs2/ConcurrencyTest.scala diff --git a/shared/src/test/scala/org/scalamock/test/specs2/SuiteScopeMockParallelTest.scala b/jvm/src/test/scala/org/scalamock/test/specs2/SuiteScopeMockParallelTest.scala similarity index 100% rename from shared/src/test/scala/org/scalamock/test/specs2/SuiteScopeMockParallelTest.scala rename to jvm/src/test/scala/org/scalamock/test/specs2/SuiteScopeMockParallelTest.scala diff --git a/shared/src/test/scala/org/scalamock/test/specs2/SuiteScopePresetMockParallelTest.scala b/jvm/src/test/scala/org/scalamock/test/specs2/SuiteScopePresetMockParallelTest.scala similarity index 100% rename from shared/src/test/scala/org/scalamock/test/specs2/SuiteScopePresetMockParallelTest.scala rename to jvm/src/test/scala/org/scalamock/test/specs2/SuiteScopePresetMockParallelTest.scala diff --git a/shared/src/main/scala/org/scalamock/clazz/MockMaker.scala b/shared/src/main/scala/org/scalamock/clazz/MockMaker.scala index fdd77ae3..dcd6f390 100644 --- a/shared/src/main/scala/org/scalamock/clazz/MockMaker.scala +++ b/shared/src/main/scala/org/scalamock/clazz/MockMaker.scala @@ -200,13 +200,17 @@ class MockMaker[C <: Context](val ctx: C) { val clazz = classType(paramCount(mt)) val types = (paramTypes(mt) map mockParamType _) :+ mockParamType(finalResultType(mt)) val name = applyOn(scalaSymbol, "apply", mockNameGenerator.generateMockMethodName(m, mt)) - - ValDef(Modifiers(), + val termName = mockFunctionName(m) + val additionalAnnotations = if(isScalaJs) List(jsExport(termName.encodedName.toString)) else Nil + ValDef( + Modifiers().mapAnnotations(additionalAnnotations ::: _), mockFunctionName(m), AppliedTypeTree(Ident(clazz.typeSymbol), types), // see issue #24 callConstructor( New(AppliedTypeTree(Ident(clazz.typeSymbol), types)), - mockContext.tree, name)) + mockContext.tree, name + ) + ) } // def () = super.() @@ -217,7 +221,7 @@ class MockMaker[C <: Context](val ctx: C) { val constructorArgumentsTypes = primaryConstructorOpt.map { constructor => val constructorTypeContext = constructor.typeSignatureIn(typeToMock) - val constructorArguments = constructor.paramss //constructorTypeContext.paramLists + val constructorArguments = constructor.paramLists constructorArguments.map { symbols => symbols.map(_.typeSignatureIn(constructorTypeContext)) } diff --git a/shared/src/main/scala/org/scalamock/util/MacroAdapter.scala b/shared/src/main/scala/org/scalamock/util/MacroAdapter.scala index 28b43ffa..5615d6e4 100644 --- a/shared/src/main/scala/org/scalamock/util/MacroAdapter.scala +++ b/shared/src/main/scala/org/scalamock/util/MacroAdapter.scala @@ -20,10 +20,12 @@ package org.scalamock.util -class MacroAdapter[C <: MacroAdapter.Context](val ctx2: C) { - import ctx2.universe._ +trait MacroAdapter { - def freshTerm(prefix: String): TermName = ctx2.freshName(TermName(prefix)) + protected val ctx: MacroAdapter.Context + import ctx.universe._ + + def freshTerm(prefix: String): TermName = ctx.freshName(TermName(prefix)) def internalTypeRef(pre: Type, sym: Symbol, args: List[Type]) = internal.typeRef(pre, sym, args) def internalSuperType(thistpe: Type, supertpe: Type): Type = internal.superType(thistpe, supertpe) def internalThisType(thistpe: Symbol) = internal.thisType(thistpe) diff --git a/shared/src/main/scala/org/scalamock/util/MacroUtils.scala b/shared/src/main/scala/org/scalamock/util/MacroUtils.scala index 7c2cccac..11d4a24f 100644 --- a/shared/src/main/scala/org/scalamock/util/MacroUtils.scala +++ b/shared/src/main/scala/org/scalamock/util/MacroUtils.scala @@ -25,8 +25,13 @@ import MacroAdapter.Context /** * Helper functions to work with Scala macros and to create scala.reflect Trees. */ -private[scalamock] class MacroUtils[C <: Context](ctx: C) extends MacroAdapter[C](ctx) { // ctx2 to avoid clash with ctx in MockMaker (eugh!) - import ctx2.universe._ +private[scalamock] class MacroUtils[C <: Context](protected val ctx: C) extends MacroAdapter { + import ctx.universe._ + + final lazy val isScalaJs = + ctx.compilerSettings.exists(o => o.startsWith("-Xplugin:") && o.contains("scalajs-compiler")) + + def jsExport(name: String) = q"new _root_.scala.scalajs.js.annotation.JSExport($name)" // Convert a methodType into its ultimate result type // For nullary and normal methods, this is just the result type @@ -77,7 +82,7 @@ private[scalamock] class MacroUtils[C <: Context](ctx: C) extends MacroAdapter[C def reportError(message: String) = { // Report with both info and abort so that the user still sees something, even if this is within an // implicit conversion (see https://issues.scala-lang.org/browse/SI-5902) - ctx2.info(ctx2.enclosingPosition, message, true) - ctx2.abort(ctx2.enclosingPosition, message) + ctx.info(ctx.enclosingPosition, message, true) + ctx.abort(ctx.enclosingPosition, message) } } diff --git a/shared/src/test/scala/org/scalamock/test/scalatest/TestSuiteRunner.scala b/shared/src/test/scala/org/scalamock/test/scalatest/TestSuiteRunner.scala index 8a8caea9..3b5c94d8 100644 --- a/shared/src/test/scala/org/scalamock/test/scalatest/TestSuiteRunner.scala +++ b/shared/src/test/scala/org/scalamock/test/scalatest/TestSuiteRunner.scala @@ -24,6 +24,7 @@ import org.scalatest.events.{Event, TestFailed} import org.scalatest.matchers.should.Matchers import scala.language.postfixOps +import scala.reflect.ClassTag trait TestSuiteRunner { this: Matchers => @@ -39,7 +40,7 @@ trait TestSuiteRunner { this: Matchers => reporter.lastEvent.get } - def getThrowable[ExnT <: Throwable](event: Event)(implicit m: Manifest[ExnT]): ExnT = { + def getThrowable[ExnT <: Throwable : ClassTag](event: Event): ExnT = { event shouldBe a[TestFailed] val testCaseError = event.asInstanceOf[TestFailed].throwable.get @@ -47,7 +48,7 @@ trait TestSuiteRunner { this: Matchers => testCaseError.asInstanceOf[ExnT] } - def getErrorMessage[ExnT <: Throwable](event: Event)(implicit m: Manifest[ExnT]): String = { + def getErrorMessage[ExnT <: Throwable : ClassTag](event: Event): String = { getThrowable[ExnT](event).getMessage() } }