diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index cb14e62c6..ec573d7d8 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,23 +1,5 @@ - - - - diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 000000000..b4908e131 --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1,13 @@ +version = 3.8.3 +runner.dialect = scala3 +newlines.topLevelStatementBlankLines = [ + {blanks {before = 1, after = 1, beforeEndMarker = 1}} +] +rewrite.rules = [RedundantBraces] +rewrite.scala3.convertToNewSyntax = true +rewrite.scala3.removeOptionalBraces.enabled = true +rewrite.scala3.removeOptionalBraces.oldSyntaxToo = true +rewrite.scala3.insertEndMarkerMinLines = 2 +rewrite.scala3.newSyntax.control = true + +maxColumn = 120 \ No newline at end of file diff --git a/analyzer/src/main/scala/com/avsystem/commons/analyzer/AnalyzerPlugin.scala b/analyzer/src/main/scala/com/avsystem/commons/analyzer/AnalyzerPlugin.scala index e91ef8d0c..1f6f03cfc 100644 --- a/analyzer/src/main/scala/com/avsystem/commons/analyzer/AnalyzerPlugin.scala +++ b/analyzer/src/main/scala/com/avsystem/commons/analyzer/AnalyzerPlugin.scala @@ -1,84 +1,71 @@ package com.avsystem.commons package analyzer -import scala.reflect.internal.util.NoPosition -import scala.tools.nsc.plugins.{Plugin, PluginComponent} -import scala.tools.nsc.{Global, Phase} +import dotty.tools.dotc.core.Contexts.{Context, ctx} +import dotty.tools.dotc.plugins.{PluginPhase, StandardPlugin} +import dotty.tools.dotc.reporting.Diagnostic +import dotty.tools.dotc.util.NoSourcePosition -final class AnalyzerPlugin(val global: Global) extends Plugin { plugin => +final class AnalyzerPlugin extends StandardPlugin: - override def init(options: List[String], error: String => Unit): Boolean = { + private lazy val rules = List( + new ImportJavaUtil, + new ExplicitGenerics, + new OldStyleImplicitConversion + ) + + override val description = "AVSystem custom Scala static analyzer" + val name = "AVSystemAnalyzer" + + override def initialize(options: List[String])(using Context): List[PluginPhase] = + parseOptions(options) + rules + + end initialize + + private def parseOptions(options: List[String])(using Context): Unit = + lazy val rulesByName = rules.map(r => (r.name, r)).toMap options.foreach { option => - if (option.startsWith("requireJDK=")) { - val jdkVersionRegex = option.substring(option.indexOf('=') + 1) - val javaVersion = System.getProperty("java.version", "") - if (!javaVersion.matches(jdkVersionRegex)) { - global.reporter.error(NoPosition, - s"This project must be compiled on JDK version that matches $jdkVersionRegex but got $javaVersion") - } - } else { - val level = option.charAt(0) match { - case '-' => Level.Off - case '*' => Level.Info - case '+' => Level.Error - case _ => Level.Warn - } - val nameArg = if (level != Level.Warn) option.drop(1) else option - if (nameArg == "_") { - rules.foreach(_.level = level) - } else { - val (name, arg) = nameArg.split(":", 2) match { + if option.startsWith("requireJDK=") then validateJdk(option) + else + val level = Level.fromChar(option.charAt(0)) + val nameArg = if level != Level.Warn then option.drop(1) else option + if nameArg == "_" then rules.foreach(_.level = level) + else + val (name, arg) = nameArg.split(":", 2) match case Array(n, a) => (n, a) - case Array(n) => (n, null) - } - rulesByName.get(name) match { + case Array(n) => (n, null) + rulesByName.get(name) match case Some(rule) => rule.level = level rule.argument = arg case None => - error(s"Unrecognized AVS analyzer rule: $name") - } - } - } - } - true - } - - private lazy val rules = List( - new ImportJavaUtil(global), - new VarargsAtLeast(global), - new CheckMacroPrivate(global), - new ExplicitGenerics(global), - new ValueEnumExhaustiveMatch(global), - new ShowAst(global), - new FindUsages(global), - new CheckBincompat(global), - new Any2StringAdd(global), - new ThrowableObjects(global), - new DiscardedMonixTask(global), - new BadSingletonComponent(global), - new ConstantDeclarations(global), - new BasePackage(global), - ) + ctx.reporter.report( + Diagnostic.Error( + s"Unrecognized AVS analyzer rule: $name", + NoSourcePosition + ) + ) + end match - private lazy val rulesByName = rules.map(r => (r.name, r)).toMap + end if + } - val name = "AVSystemAnalyzer" - val description = "AVSystem custom Scala static analyzer" - val components: List[PluginComponent] = List(component) + end parseOptions - private object component extends PluginComponent { - val global: plugin.global.type = plugin.global - val runsAfter = List("typer") - override val runsBefore = List("patmat", "silencer") - val phaseName = "avsAnalyze" + private def validateJdk(option: String)(using Context): Unit = + val jdkVersionRegex = option.substring(option.indexOf('=') + 1) + val javaVersion = System.getProperty("java.version", "") + if !javaVersion.matches(jdkVersionRegex) then + ctx.reporter.report( + Diagnostic.Error( + s"This project must be compiled on JDK version that matches $jdkVersionRegex but got $javaVersion", + NoSourcePosition + ) + ) - import global._ + end if - def newPhase(prev: Phase): StdPhase = new StdPhase(prev) { - def apply(unit: CompilationUnit): Unit = - rules.foreach(rule => if (rule.level != Level.Off) rule.analyze(unit.asInstanceOf[rule.global.CompilationUnit])) - } - } + end validateJdk -} +end AnalyzerPlugin diff --git a/analyzer/src/main/scala/com/avsystem/commons/analyzer/AnalyzerRule.scala b/analyzer/src/main/scala/com/avsystem/commons/analyzer/AnalyzerRule.scala index d5b42f0f0..03755f404 100644 --- a/analyzer/src/main/scala/com/avsystem/commons/analyzer/AnalyzerRule.scala +++ b/analyzer/src/main/scala/com/avsystem/commons/analyzer/AnalyzerRule.scala @@ -1,56 +1,66 @@ package com.avsystem.commons package analyzer -import java.io.{PrintWriter, StringWriter} -import scala.tools.nsc.Global -import scala.tools.nsc.Reporting.WarningCategory -import scala.util.control.NonFatal +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Decorators.* +import dotty.tools.dotc.core.Symbols.* +import dotty.tools.dotc.plugins.PluginPhase +import dotty.tools.dotc.reporting.{Diagnostic, Message} +import dotty.tools.dotc.transform.{Pickler, Staging} +import dotty.tools.dotc.util.SourcePosition -abstract class AnalyzerRule(val global: Global, val name: String, defaultLevel: Level = Level.Warn) { +import scala.compiletime.uninitialized +import scala.util.ChainingSyntax - import global._ +abstract class AnalyzerRule(val name: String, defaultLevel: Level = Level.Warn) extends PluginPhase, ChainingSyntax: - var level: Level = defaultLevel - var argument: String = _ + import tpd.* - protected def classType(fullName: String): Type = - try rootMirror.staticClass(fullName).asType.toType.erasure catch { - case _: ScalaReflectionException => NoType - } + override val runsAfter: Set[String] = Set(Pickler.name) + override val runsBefore: Set[String] = Set(Staging.name) - protected def analyzeTree(fun: PartialFunction[Tree, Unit])(tree: Tree): Unit = - try fun.applyOrElse(tree, (_: Tree) => ()) catch { - case NonFatal(t) => - val sw = new StringWriter - t.printStackTrace(new PrintWriter(sw)) - reporter.error(tree.pos, s"Analyzer rule $this failed: " + sw.toString) - } + override val phaseName = s"avsAnalyze$name" + var level: Level = defaultLevel + var argument: String = uninitialized + override def toString: String = getClass.getSimpleName - private def adjustMsg(msg: String): String = s"[AVS] $msg" + inline protected final def report(message: String, tree: Tree)(using Context): Unit = + report(message, tree.symbol)(using tree.sourcePos) protected final def report( - pos: Position, - message: String, - category: WarningCategory = WarningCategory.Lint, - site: Symbol = NoSymbol - ): Unit = - level match { + message: String, + site: Symbol = NoSymbol + )(using + position: SourcePosition, + ctx: Context + ): Unit = ctx.reporter.report { + level match case Level.Off => - case Level.Info => reporter.echo(pos, adjustMsg(message)) - case Level.Warn => currentRun.reporting.warning(pos, adjustMsg(message), category, site) - case Level.Error => reporter.error(pos, adjustMsg(message)) - } - - def analyze(unit: CompilationUnit): Unit - - override def toString: String = - getClass.getSimpleName -} - -sealed trait Level -object Level { - case object Off extends Level - case object Info extends Level - case object Warn extends Level - case object Error extends Level -} + return // awful + case Level.Info => + Diagnostic.Info(adjustMsg(message), position) + case Level.Warn => + Diagnostic.UncheckedWarning(adjustMsg(message), position) // todo not sure if correct type of warning + case Level.Error => + Diagnostic.Error(adjustMsg(message), position) + } + + private def adjustMsg(msg: String): Message = s"[AVS] $msg".toMessage + +end AnalyzerRule + +enum Level: + case Off, Info, Warn, Error + +end Level + +object Level: + + def fromChar(c: Char): Level = c match + case '-' => Level.Off + case '*' => Level.Info + case '+' => Level.Error + case _ => Level.Warn + +end Level diff --git a/analyzer/src/main/scala/com/avsystem/commons/analyzer/Any2StringAdd.scala b/analyzer/src/main/scala/com/avsystem/commons/analyzer/Any2StringAdd.scala index 8d1e8d36f..03aff4094 100644 --- a/analyzer/src/main/scala/com/avsystem/commons/analyzer/Any2StringAdd.scala +++ b/analyzer/src/main/scala/com/avsystem/commons/analyzer/Any2StringAdd.scala @@ -1,20 +1,24 @@ -package com.avsystem.commons -package analyzer - -import scala.tools.nsc.Global - -class Any2StringAdd(g: Global) extends AnalyzerRule(g, "any2stringadd", Level.Off) { - - import global._ - - private lazy val any2stringaddSym = - typeOf[Predef.type].member(TermName("any2stringadd")).alternatives.find(_.isMethod).get - - def analyze(unit: CompilationUnit): Unit = { - unit.body.foreach(analyzeTree { - case t if t.symbol == any2stringaddSym => - report(t.pos, "concatenating arbitrary values with strings is disabled, " + - "use explicit toString or string interpolation") - }) - } -} +//package com.avsystem.commons +//package analyzer +// +//import dotty.tools.dotc.ast.tpd +//import dotty.tools.dotc.core.Contexts.Context +//import dotty.tools.dotc.core.Symbols +//import dotty.tools.dotc.core.Symbols.* +// +//final class Any2StringAdd extends AnalyzerRule("any2stringadd", Level.Off): +// import tpd.* +// +// private lazy val any2stringaddSym: Context ?=> Symbol = +// Symbols.requiredClass("scala.Predef").classDenot.requiredMethod("any2stringadd") +// +// override protected def analyzeTree(using Context): PartialFunction[Tree, Unit] = { +// case tree if tree.symbol == any2stringaddSym => +// report( +// "concatenating arbitrary values with strings is disabled, " + +// "use explicit toString or string interpolation", +// tree.symbol +// )(using tree.sourcePos) +// } +// +//end Any2StringAdd diff --git a/analyzer/src/main/scala/com/avsystem/commons/analyzer/BadSingletonComponent.scala b/analyzer/src/main/scala/com/avsystem/commons/analyzer/BadSingletonComponent.scala index b7e4107d9..d763d0a1f 100644 --- a/analyzer/src/main/scala/com/avsystem/commons/analyzer/BadSingletonComponent.scala +++ b/analyzer/src/main/scala/com/avsystem/commons/analyzer/BadSingletonComponent.scala @@ -1,48 +1,48 @@ -package com.avsystem.commons -package analyzer - -import scala.annotation.tailrec -import scala.tools.nsc.Global - -class BadSingletonComponent(g: Global) extends AnalyzerRule(g, "badSingletonComponent") { - - import global._ - - lazy val componentsTpe: Type = classType("com.avsystem.commons.di.Components") - lazy val cachedSym: Symbol = componentsTpe.member(TermName("cached")) - - object UnwrapApply { - @tailrec def unapply(tree: Tree): Some[Tree] = tree match { - case Apply(fun, _) => unapply(fun) - case TypeApply(fun, _) => unapply(fun) - case t => Some(t) - } - } - - object Unwrap { - @tailrec def unapply(t: Tree): Some[Tree] = t match { - case Block(Nil, expr) => unapply(expr) - case Typed(expr, _) => unapply(expr) - case Annotated(_, expr) => unapply(expr) - case UnwrapApply(Select(prefix, _)) if prefix.tpe =:= t.tpe => unapply(prefix) - case _ => Some(t) - } - } - - def analyze(unit: CompilationUnit): Unit = - if (componentsTpe != NoType) { - object traverser extends Traverser { - override def traverse(tree: Tree): Unit = tree match { - case mdef@DefDef(_, _, Nil, Nil, _, Unwrap(app@Apply(_, List(arg, _)))) - if app.symbol == cachedSym && mdef.symbol.owner.isClass && ThisType(mdef.symbol.owner) <:< componentsTpe => - traverse(arg) - case t if t.symbol == cachedSym => - report(t.pos, "singleton(...) macro can only be used as a body of a parameterless method in a Components trait implementation") - case _ => - super.traverse(tree) - } - } - - traverser.traverse(unit.body) - } -} +//package com.avsystem.commons +//package analyzer +// +//import scala.annotation.tailrec +//import scala.tools.nsc.Global +// +//class BadSingletonComponent(g: Global) extends AnalyzerRule(g, "badSingletonComponent") { +// +// import global._ +// +// lazy val componentsTpe: Type = classType("com.avsystem.commons.di.Components") +// lazy val cachedSym: Symbol = componentsTpe.member(TermName("cached")) +// +// object UnwrapApply { +// @tailrec def unapply(tree: Tree): Some[Tree] = tree match { +// case Apply(fun, _) => unapply(fun) +// case TypeApply(fun, _) => unapply(fun) +// case t => Some(t) +// } +// } +// +// object Unwrap { +// @tailrec def unapply(t: Tree): Some[Tree] = t match { +// case Block(Nil, expr) => unapply(expr) +// case Typed(expr, _) => unapply(expr) +// case Annotated(_, expr) => unapply(expr) +// case UnwrapApply(Select(prefix, _)) if prefix.tpe =:= t.tpe => unapply(prefix) +// case _ => Some(t) +// } +// } +// +// def analyze(unit: CompilationUnit): Unit = +// if (componentsTpe != NoType) { +// object traverser extends Traverser { +// override def traverse(tree: Tree): Unit = tree match { +// case mdef@DefDef(_, _, Nil, Nil, _, Unwrap(app@Apply(_, List(arg, _)))) +// if app.symbol == cachedSym && mdef.symbol.owner.isClass && ThisType(mdef.symbol.owner) <:< componentsTpe => +// traverse(arg) +// case t if t.symbol == cachedSym => +// report(t.pos, "singleton(...) macro can only be used as a body of a parameterless method in a Components trait implementation") +// case _ => +// super.traverse(tree) +// } +// } +// +// traverser.traverse(unit.body) +// } +//} diff --git a/analyzer/src/main/scala/com/avsystem/commons/analyzer/BasePackage.scala b/analyzer/src/main/scala/com/avsystem/commons/analyzer/BasePackage.scala index a8d2805a8..c25a58eeb 100644 --- a/analyzer/src/main/scala/com/avsystem/commons/analyzer/BasePackage.scala +++ b/analyzer/src/main/scala/com/avsystem/commons/analyzer/BasePackage.scala @@ -1,29 +1,29 @@ -package com.avsystem.commons -package analyzer - -import scala.annotation.tailrec -import scala.tools.nsc.Global - -class BasePackage(g: Global) extends AnalyzerRule(g, "basePackage") { - - import global._ - - object SkipImports { - @tailrec def unapply(stats: List[Tree]): Some[List[Tree]] = stats match { - case Import(_, _) :: tail => unapply(tail) - case stats => Some(stats) - } - } - - def analyze(unit: CompilationUnit): Unit = if (argument != null) { - val requiredBasePackage = argument - - @tailrec def validate(tree: Tree): Unit = tree match { - case PackageDef(pid, _) if pid.symbol.hasPackageFlag && pid.symbol.fullName == requiredBasePackage => - case PackageDef(_, SkipImports(List(stat))) => validate(stat) - case t => report(t.pos, s"`$requiredBasePackage` must be one of the base packages in this file") - } - - validate(unit.body) - } -} +//package com.avsystem.commons +//package analyzer +// +//import scala.annotation.tailrec +//import scala.tools.nsc.Global +// +//class BasePackage(g: Global) extends AnalyzerRule(g, "basePackage") { +// +// import global._ +// +// object SkipImports { +// @tailrec def unapply(stats: List[Tree]): Some[List[Tree]] = stats match { +// case Import(_, _) :: tail => unapply(tail) +// case stats => Some(stats) +// } +// } +// +// def analyze(unit: CompilationUnit): Unit = if (argument != null) { +// val requiredBasePackage = argument +// +// @tailrec def validate(tree: Tree): Unit = tree match { +// case PackageDef(pid, _) if pid.symbol.hasPackageFlag && pid.symbol.fullName == requiredBasePackage => +// case PackageDef(_, SkipImports(List(stat))) => validate(stat) +// case t => report(t.pos, s"`$requiredBasePackage` must be one of the base packages in this file") +// } +// +// validate(unit.body) +// } +//} diff --git a/analyzer/src/main/scala/com/avsystem/commons/analyzer/CheckBincompat.scala b/analyzer/src/main/scala/com/avsystem/commons/analyzer/CheckBincompat.scala index 2d0874a97..2245b8bc6 100644 --- a/analyzer/src/main/scala/com/avsystem/commons/analyzer/CheckBincompat.scala +++ b/analyzer/src/main/scala/com/avsystem/commons/analyzer/CheckBincompat.scala @@ -1,19 +1,19 @@ -package com.avsystem.commons -package analyzer - -import scala.tools.nsc.Global - -class CheckBincompat(g: Global) extends AnalyzerRule(g, "bincompat") { - - import global._ - - private lazy val bincompatAnnotType = classType("com.avsystem.commons.annotation.bincompat") - - def analyze(unit: CompilationUnit): Unit = - unit.body.foreach(analyzeTree { - case tree@(_: Ident | _: Select | _: New) if tree.symbol != null && - tree.symbol.annotations.exists(_.tree.tpe <:< bincompatAnnotType) => - report(tree.pos, "Symbols annotated as @bincompat exist only for binary compatibility " + - "and should not be used directly") - }) -} +//package com.avsystem.commons +//package analyzer +// +//import scala.tools.nsc.Global +// +//class CheckBincompat(g: Global) extends AnalyzerRule(g, "bincompat") { +// +// import global._ +// +// private lazy val bincompatAnnotType = classType("com.avsystem.commons.annotation.bincompat") +// +// def analyze(unit: CompilationUnit): Unit = +// unit.body.foreach(analyzeTree { +// case tree@(_: Ident | _: Select | _: New) if tree.symbol != null && +// tree.symbol.annotations.exists(_.tree.tpe <:< bincompatAnnotType) => +// report(tree.pos, "Symbols annotated as @bincompat exist only for binary compatibility " + +// "and should not be used directly") +// }) +//} diff --git a/analyzer/src/main/scala/com/avsystem/commons/analyzer/CheckMacroPrivate.scala b/analyzer/src/main/scala/com/avsystem/commons/analyzer/CheckMacroPrivate.scala index 7be50395c..53954710c 100644 --- a/analyzer/src/main/scala/com/avsystem/commons/analyzer/CheckMacroPrivate.scala +++ b/analyzer/src/main/scala/com/avsystem/commons/analyzer/CheckMacroPrivate.scala @@ -1,33 +1,33 @@ -package com.avsystem.commons -package analyzer - -import scala.tools.nsc.Global - -class CheckMacroPrivate(g: Global) extends AnalyzerRule(g, "macroPrivate") { - - import global._ - - lazy val macroPrivateAnnotTpe = classType("com.avsystem.commons.annotation.macroPrivate") - - def analyze(unit: CompilationUnit) = if (macroPrivateAnnotTpe != NoType) { - def analyzeTree(tree: Tree): Unit = analyzer.macroExpandee(tree) match { - case `tree` | EmptyTree => - tree match { - case _: Ident | _: Select | _: SelectFromTypeTree | _: New - if tree.symbol != null && tree.pos != NoPosition => - - val sym = tree.symbol - val macroPrivate = (sym :: sym.overrides).iterator - .flatMap(_.annotations).exists(_.tree.tpe <:< macroPrivateAnnotTpe) - if (macroPrivate) { - report(tree.pos, s"$sym can only be used in macro-generated code") - } - case _ => - } - tree.children.foreach(analyzeTree) - case prevTree => - analyzeTree(prevTree) - } - analyzeTree(unit.body) - } -} +//package com.avsystem.commons +//package analyzer +// +//import scala.tools.nsc.Global +// +//class CheckMacroPrivate(g: Global) extends AnalyzerRule(g, "macroPrivate") { +// +// import global._ +// +// lazy val macroPrivateAnnotTpe = classType("com.avsystem.commons.annotation.macroPrivate") +// +// def analyze(unit: CompilationUnit) = if (macroPrivateAnnotTpe != NoType) { +// def analyzeTree(tree: Tree): Unit = analyzer.macroExpandee(tree) match { +// case `tree` | EmptyTree => +// tree match { +// case _: Ident | _: Select | _: SelectFromTypeTree | _: New +// if tree.symbol != null && tree.pos != NoPosition => +// +// val sym = tree.symbol +// val macroPrivate = (sym :: sym.overrides).iterator +// .flatMap(_.annotations).exists(_.tree.tpe <:< macroPrivateAnnotTpe) +// if (macroPrivate) { +// report(tree.pos, s"$sym can only be used in macro-generated code") +// } +// case _ => +// } +// tree.children.foreach(analyzeTree) +// case prevTree => +// analyzeTree(prevTree) +// } +// analyzeTree(unit.body) +// } +//} diff --git a/analyzer/src/main/scala/com/avsystem/commons/analyzer/ConstantDeclarations.scala b/analyzer/src/main/scala/com/avsystem/commons/analyzer/ConstantDeclarations.scala index 7911fb9dd..f6180b4ab 100644 --- a/analyzer/src/main/scala/com/avsystem/commons/analyzer/ConstantDeclarations.scala +++ b/analyzer/src/main/scala/com/avsystem/commons/analyzer/ConstantDeclarations.scala @@ -1,37 +1,37 @@ -package com.avsystem.commons -package analyzer - -import scala.tools.nsc.Global - -class ConstantDeclarations(g: Global) extends AnalyzerRule(g, "constantDeclarations", Level.Off) { - - import global._ - - def analyze(unit: CompilationUnit): Unit = unit.body.foreach { - case t@ValDef(_, name, tpt, rhs) - if t.symbol.hasGetter && t.symbol.owner.isEffectivelyFinal => - - val getter = t.symbol.getterIn(t.symbol.owner) - if (getter.isPublic && getter.isStable && getter.overrides.isEmpty) { - val constantValue = rhs.tpe match { - case ConstantType(_) => true - case _ => false - } - - def doReport(msg: String): Unit = - report(t.pos, msg, site = t.symbol) - - val firstChar = name.toString.charAt(0) - if (constantValue && (firstChar.isLower || !getter.isFinal)) { - doReport("a literal-valued constant should be declared as a `final val` with an UpperCamelCase name") - } - if (!constantValue && firstChar.isUpper && !getter.isFinal) { - doReport("a constant with UpperCamelCase name should be declared as a `final val`") - } - if (getter.isFinal && constantValue && !(tpt.tpe =:= rhs.tpe)) { - doReport("a constant with a literal value should not have an explicit type annotation") - } - } - case _ => - } -} +//package com.avsystem.commons +//package analyzer +// +//import scala.tools.nsc.Global +// +//class ConstantDeclarations(g: Global) extends AnalyzerRule(g, "constantDeclarations", Level.Off) { +// +// import global._ +// +// def analyze(unit: CompilationUnit): Unit = unit.body.foreach { +// case t@ValDef(_, name, tpt, rhs) +// if t.symbol.hasGetter && t.symbol.owner.isEffectivelyFinal => +// +// val getter = t.symbol.getterIn(t.symbol.owner) +// if (getter.isPublic && getter.isStable && getter.overrides.isEmpty) { +// val constantValue = rhs.tpe match { +// case ConstantType(_) => true +// case _ => false +// } +// +// def doReport(msg: String): Unit = +// report(t.pos, msg, site = t.symbol) +// +// val firstChar = name.toString.charAt(0) +// if (constantValue && (firstChar.isLower || !getter.isFinal)) { +// doReport("a literal-valued constant should be declared as a `final val` with an UpperCamelCase name") +// } +// if (!constantValue && firstChar.isUpper && !getter.isFinal) { +// doReport("a constant with UpperCamelCase name should be declared as a `final val`") +// } +// if (getter.isFinal && constantValue && !(tpt.tpe =:= rhs.tpe)) { +// doReport("a constant with a literal value should not have an explicit type annotation") +// } +// } +// case _ => +// } +//} diff --git a/analyzer/src/main/scala/com/avsystem/commons/analyzer/DiscardedMonixTask.scala b/analyzer/src/main/scala/com/avsystem/commons/analyzer/DiscardedMonixTask.scala index 310738be5..59ecdfa74 100644 --- a/analyzer/src/main/scala/com/avsystem/commons/analyzer/DiscardedMonixTask.scala +++ b/analyzer/src/main/scala/com/avsystem/commons/analyzer/DiscardedMonixTask.scala @@ -1,68 +1,68 @@ -package com.avsystem.commons -package analyzer - -import scala.tools.nsc.Global - -class DiscardedMonixTask(g: Global) extends AnalyzerRule(g, "discardedMonixTask") { - - import global._ - - lazy val monixTaskTpe: Type = classType("monix.eval.Task") match { - case NoType => NoType - case tpe => TypeRef(NoPrefix, tpe.typeSymbol, List(definitions.AnyTpe)) - } - - private def checkDiscardedTask(tree: Tree, discarded: Boolean): Unit = tree match { - case tree if !discarded && tree.tpe != null && tree.tpe =:= definitions.UnitTpe => - checkDiscardedTask(tree, discarded = true) - - case Block(stats, expr) => - stats.foreach(checkDiscardedTask(_, discarded = true)) - checkDiscardedTask(expr, discarded) - - case Template(parents, self, body) => - parents.foreach(checkDiscardedTask(_, discarded = false)) - checkDiscardedTask(self, discarded = false) - body.foreach(checkDiscardedTask(_, discarded = true)) - - case If(_, thenp, elsep) => - checkDiscardedTask(thenp, discarded) - checkDiscardedTask(elsep, discarded) - - case LabelDef(_, _, rhs) => - checkDiscardedTask(rhs, discarded = true) - - case Try(body, catches, finalizer) => - checkDiscardedTask(body, discarded) - catches.foreach(checkDiscardedTask(_, discarded)) - checkDiscardedTask(finalizer, discarded = true) - - case CaseDef(_, _, body) => - checkDiscardedTask(body, discarded) - - case Match(_, cases) => - cases.foreach(checkDiscardedTask(_, discarded)) - - case Annotated(_, arg) => - checkDiscardedTask(arg, discarded) - - case Typed(expr, _) => - checkDiscardedTask(expr, discarded) - - case Apply(TypeApply(Select(prefix, TermName("foreach")), List(_)), List(Function(_, body))) => - checkDiscardedTask(prefix, discarded = false) - checkDiscardedTask(body, discarded) - - case _: Ident | _: Select | _: Apply | _: TypeApply if discarded && - tree.tpe != null && tree.tpe <:< monixTaskTpe && !(tree.tpe <:< definitions.NullTpe) => - report(tree.pos, "discarded Monix Task - this is probably a mistake because the Task must be run for its side effects") - - case tree => - tree.children.foreach(checkDiscardedTask(_, discarded = false)) - } - - def analyze(unit: CompilationUnit): Unit = - if (monixTaskTpe != NoType) { - checkDiscardedTask(unit.body, discarded = false) - } -} +//package com.avsystem.commons +//package analyzer +// +//import scala.tools.nsc.Global +// +//class DiscardedMonixTask(g: Global) extends AnalyzerRule(g, "discardedMonixTask") { +// +// import global._ +// +// lazy val monixTaskTpe: Type = classType("monix.eval.Task") match { +// case NoType => NoType +// case tpe => TypeRef(NoPrefix, tpe.typeSymbol, List(definitions.AnyTpe)) +// } +// +// private def checkDiscardedTask(tree: Tree, discarded: Boolean): Unit = tree match { +// case tree if !discarded && tree.tpe != null && tree.tpe =:= definitions.UnitTpe => +// checkDiscardedTask(tree, discarded = true) +// +// case Block(stats, expr) => +// stats.foreach(checkDiscardedTask(_, discarded = true)) +// checkDiscardedTask(expr, discarded) +// +// case Template(parents, self, body) => +// parents.foreach(checkDiscardedTask(_, discarded = false)) +// checkDiscardedTask(self, discarded = false) +// body.foreach(checkDiscardedTask(_, discarded = true)) +// +// case If(_, thenp, elsep) => +// checkDiscardedTask(thenp, discarded) +// checkDiscardedTask(elsep, discarded) +// +// case LabelDef(_, _, rhs) => +// checkDiscardedTask(rhs, discarded = true) +// +// case Try(body, catches, finalizer) => +// checkDiscardedTask(body, discarded) +// catches.foreach(checkDiscardedTask(_, discarded)) +// checkDiscardedTask(finalizer, discarded = true) +// +// case CaseDef(_, _, body) => +// checkDiscardedTask(body, discarded) +// +// case Match(_, cases) => +// cases.foreach(checkDiscardedTask(_, discarded)) +// +// case Annotated(_, arg) => +// checkDiscardedTask(arg, discarded) +// +// case Typed(expr, _) => +// checkDiscardedTask(expr, discarded) +// +// case Apply(TypeApply(Select(prefix, TermName("foreach")), List(_)), List(Function(_, body))) => +// checkDiscardedTask(prefix, discarded = false) +// checkDiscardedTask(body, discarded) +// +// case _: Ident | _: Select | _: Apply | _: TypeApply if discarded && +// tree.tpe != null && tree.tpe <:< monixTaskTpe && !(tree.tpe <:< definitions.NullTpe) => +// report(tree.pos, "discarded Monix Task - this is probably a mistake because the Task must be run for its side effects") +// +// case tree => +// tree.children.foreach(checkDiscardedTask(_, discarded = false)) +// } +// +// def analyze(unit: CompilationUnit): Unit = +// if (monixTaskTpe != NoType) { +// checkDiscardedTask(unit.body, discarded = false) +// } +//} diff --git a/analyzer/src/main/scala/com/avsystem/commons/analyzer/ExplicitGenerics.scala b/analyzer/src/main/scala/com/avsystem/commons/analyzer/ExplicitGenerics.scala index 585f58ce5..4601b99ac 100644 --- a/analyzer/src/main/scala/com/avsystem/commons/analyzer/ExplicitGenerics.scala +++ b/analyzer/src/main/scala/com/avsystem/commons/analyzer/ExplicitGenerics.scala @@ -1,35 +1,33 @@ package com.avsystem.commons package analyzer -import scala.tools.nsc.Global - -class ExplicitGenerics(g: Global) extends AnalyzerRule(g, "explicitGenerics") { - - import global._ - - lazy val explicitGenericsAnnotTpe = classType("com.avsystem.commons.annotation.explicitGenerics") - - def analyze(unit: CompilationUnit) = if (explicitGenericsAnnotTpe != NoType) { - def requiresExplicitGenerics(sym: Symbol): Boolean = - sym != NoSymbol && (sym :: sym.overrides).flatMap(_.annotations).exists(_.tree.tpe <:< explicitGenericsAnnotTpe) - - def analyzeTree(tree: Tree): Unit = analyzer.macroExpandee(tree) match { - case `tree` | EmptyTree => - tree match { - case t@TypeApply(pre, args) if requiresExplicitGenerics(pre.symbol) => - val inferredTypeParams = args.forall { - case tt: TypeTree => tt.original == null || tt.original == EmptyTree - case _ => false - } - if (inferredTypeParams) { - report(t.pos, s"${pre.symbol} requires that its type arguments are explicit (not inferred)") - } - case _ => - } - tree.children.foreach(analyzeTree) - case prevTree => - analyzeTree(prevTree) - } - analyzeTree(unit.body) +import dotty.tools.dotc.ast.tpd.* +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.{Contexts, Symbols} + +final class ExplicitGenerics extends AnalyzerRule("explicitGenerics"): + import ExplicitGenerics.* + + override def transformTypeApply(tree: TypeApply)(using Context): Tree = tree.tap { + case TypeApply(fun: Tree, args) + if fun.symbol.hasAnnotation(explicitGenericsSymbol) && args.containsInferredTypeParameters => + report(s"${fun.symbol} requires that its type arguments are explicit (not inferred)", tree) + case _ => } -} + + override def transformInlined(tree: Inlined)(using Context): Tree = tree.tap { + case Inlined(Apply(typeApply: TypeApply, _), _, _) => transformTypeApply(typeApply) + case _ => + } + +end ExplicitGenerics + +private object ExplicitGenerics: + + private final val explicitGenericsSymbol = + (ctx: Context) ?=> Symbols.requiredClass("com.avsystem.commons.annotation.explicitGenerics") + + extension (args: List[Tree]) + private def containsInferredTypeParameters: Boolean = args.exists(_.isInstanceOf[TypeTree]) + +end ExplicitGenerics diff --git a/analyzer/src/main/scala/com/avsystem/commons/analyzer/FindUsages.scala b/analyzer/src/main/scala/com/avsystem/commons/analyzer/FindUsages.scala index 1febb6729..a72481519 100644 --- a/analyzer/src/main/scala/com/avsystem/commons/analyzer/FindUsages.scala +++ b/analyzer/src/main/scala/com/avsystem/commons/analyzer/FindUsages.scala @@ -1,20 +1,20 @@ -package com.avsystem.commons -package analyzer - -import scala.tools.nsc.Global - -class FindUsages(g: Global) extends AnalyzerRule(g, "findUsages") { - - import global._ - - lazy val rejectedSymbols: Set[String] = - if (argument == null) Set.empty else argument.split(";").toSet - - override def analyze(unit: CompilationUnit): Unit = if (rejectedSymbols.nonEmpty) { - unit.body.foreach { tree => - if (tree.symbol != null && rejectedSymbols.contains(tree.symbol.fullName)) { - report(tree.pos, s"found usage of ${tree.symbol.fullName}") - } - } - } -} +//package com.avsystem.commons +//package analyzer +// +//import scala.tools.nsc.Global +// +//class FindUsages(g: Global) extends AnalyzerRule(g, "findUsages") { +// +// import global._ +// +// lazy val rejectedSymbols: Set[String] = +// if (argument == null) Set.empty else argument.split(";").toSet +// +// override def analyze(unit: CompilationUnit): Unit = if (rejectedSymbols.nonEmpty) { +// unit.body.foreach { tree => +// if (tree.symbol != null && rejectedSymbols.contains(tree.symbol.fullName)) { +// report(tree.pos, s"found usage of ${tree.symbol.fullName}") +// } +// } +// } +//} diff --git a/analyzer/src/main/scala/com/avsystem/commons/analyzer/ImplicitTypes.scala b/analyzer/src/main/scala/com/avsystem/commons/analyzer/ImplicitTypes.scala deleted file mode 100644 index fa48a6f54..000000000 --- a/analyzer/src/main/scala/com/avsystem/commons/analyzer/ImplicitTypes.scala +++ /dev/null @@ -1,15 +0,0 @@ -package com.avsystem.commons -package analyzer - -import scala.tools.nsc.Global - -class ImplicitTypes(g: Global) extends AnalyzerRule(g, "implicitTypes") { - - import global._ - - def analyze(unit: CompilationUnit): Unit = unit.body.foreach { - case t@ValOrDefDef(mods, _, tpt@TypeTree(), _) if tpt.original == null && mods.isImplicit && !mods.isSynthetic => - report(t.pos, s"Implicit definitions must have type annotated explicitly") - case _ => - } -} diff --git a/analyzer/src/main/scala/com/avsystem/commons/analyzer/ImportJavaUtil.scala b/analyzer/src/main/scala/com/avsystem/commons/analyzer/ImportJavaUtil.scala index 4418ab394..b3d18fa31 100644 --- a/analyzer/src/main/scala/com/avsystem/commons/analyzer/ImportJavaUtil.scala +++ b/analyzer/src/main/scala/com/avsystem/commons/analyzer/ImportJavaUtil.scala @@ -1,17 +1,35 @@ package com.avsystem.commons package analyzer -import scala.tools.nsc.Global +import dotty.tools.dotc.ast.untpd.ImportSelector +import dotty.tools.dotc.ast.{Trees, tpd} +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Symbols +import dotty.tools.dotc.core.Symbols.* +import tpd.* +import ImportJavaUtil.* -class ImportJavaUtil(g: Global) extends AnalyzerRule(g, "importJavaUtil") { +final class ImportJavaUtil extends AnalyzerRule("importJavaUtil"): - import global._ - - def analyze(unit: CompilationUnit): Unit = { - unit.body.foreach(analyzeTree { - case tree@q"import java.util" => - report(tree.pos, "Don't import java.util: either import with rename (e.g. import java.{util => ju}) " + - "or use type aliases from JavaInterop (e.g. JList, JSet, etc)") - }) + override def transformOther(tree: Tree)(using Context): Tree = tree.tap { + case Import(Ident(`javaPkgName`), selectors: List[ImportSelector]) if selectors.exists { + case ImportSelector(Ident(`javaUtilPkgName`), EmptyTree, _) => true + case _ => false + } => + report( + "Don't import java.util: either import with rename (e.g. import java.{util => ju}) " + + "or use type aliases from JavaInterop (e.g. JList, JSet, etc)", + tree + ) + case _ => } -} + + end transformOther + +end ImportJavaUtil + +private object ImportJavaUtil: + private final val javaPkgName = (ctx: Context) ?=> Symbols.requiredPackageRef("java").name + private final val javaUtilPkgName = (ctx: Context) ?=> Symbols.requiredPackageRef("java.util").name + +end ImportJavaUtil diff --git a/analyzer/src/main/scala/com/avsystem/commons/analyzer/OldStyleImplicitConversion.scala b/analyzer/src/main/scala/com/avsystem/commons/analyzer/OldStyleImplicitConversion.scala new file mode 100644 index 000000000..0b3029dcc --- /dev/null +++ b/analyzer/src/main/scala/com/avsystem/commons/analyzer/OldStyleImplicitConversion.scala @@ -0,0 +1,18 @@ +package com.avsystem.commons +package analyzer + +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Contexts + +final class OldStyleImplicitConversion extends AnalyzerRule("oldStyleImplicitConversion"): + + override def transformDefDef(tree: tpd.DefDef)(using Contexts.Context): tpd.Tree = tree.tap { tree => + if tree.symbol.isOldStyleImplicitConversion(directOnly = true) then + report("old-style implicit conversions are deprecated, use Conversion instead", tree) + else if tree.symbol.isOldStyleImplicitConversion(forImplicitClassOnly = true) then + report("old-style implicit conversions are deprecated, use extension", tree) + + end if + } + +end OldStyleImplicitConversion diff --git a/analyzer/src/main/scala/com/avsystem/commons/analyzer/ShowAst.scala b/analyzer/src/main/scala/com/avsystem/commons/analyzer/ShowAst.scala index 990372707..f872e3258 100644 --- a/analyzer/src/main/scala/com/avsystem/commons/analyzer/ShowAst.scala +++ b/analyzer/src/main/scala/com/avsystem/commons/analyzer/ShowAst.scala @@ -1,30 +1,30 @@ -package com.avsystem.commons -package analyzer - -import scala.tools.nsc.Global - -class ShowAst(g: Global) extends AnalyzerRule(g, "showAst", Level.Error) { - - import global._ - - lazy val showAstAnnotType: Type = classType("com.avsystem.commons.annotation.showAst") - - def analyze(unit: CompilationUnit) = if (showAstAnnotType != NoType) { - def analyzeTree(tree: Tree): Unit = analyzer.macroExpandee(tree) match { - case `tree` | EmptyTree => - tree match { - case Annotated(annot, arg) if annot.tpe <:< showAstAnnotType => - report(arg.pos, showCode(arg)) - case Typed(expr, tpt) if tpt.tpe.annotations.exists(_.tpe <:< showAstAnnotType) => - report(expr.pos, showCode(expr)) - case _: MemberDef if tree.symbol.annotations.exists(_.tpe <:< showAstAnnotType) => - report(tree.pos, showCode(tree)) - case _ => - } - tree.children.foreach(analyzeTree) - case prevTree => - analyzeTree(prevTree) - } - analyzeTree(unit.body) - } -} +//package com.avsystem.commons +//package analyzer +// +//import scala.tools.nsc.Global +// +//class ShowAst(g: Global) extends AnalyzerRule(g, "showAst", Level.Error) { +// +// import global._ +// +// lazy val showAstAnnotType: Type = classType("com.avsystem.commons.annotation.showAst") +// +// def analyze(unit: CompilationUnit) = if (showAstAnnotType != NoType) { +// def analyzeTree(tree: Tree): Unit = analyzer.macroExpandee(tree) match { +// case `tree` | EmptyTree => +// tree match { +// case Annotated(annot, arg) if annot.tpe <:< showAstAnnotType => +// report(arg.pos, showCode(arg)) +// case Typed(expr, tpt) if tpt.tpe.annotations.exists(_.tpe <:< showAstAnnotType) => +// report(expr.pos, showCode(expr)) +// case _: MemberDef if tree.symbol.annotations.exists(_.tpe <:< showAstAnnotType) => +// report(tree.pos, showCode(tree)) +// case _ => +// } +// tree.children.foreach(analyzeTree) +// case prevTree => +// analyzeTree(prevTree) +// } +// analyzeTree(unit.body) +// } +//} diff --git a/analyzer/src/main/scala/com/avsystem/commons/analyzer/ThrowableObjects.scala b/analyzer/src/main/scala/com/avsystem/commons/analyzer/ThrowableObjects.scala index 62a2b5c65..b9dceb49a 100644 --- a/analyzer/src/main/scala/com/avsystem/commons/analyzer/ThrowableObjects.scala +++ b/analyzer/src/main/scala/com/avsystem/commons/analyzer/ThrowableObjects.scala @@ -1,24 +1,24 @@ -package com.avsystem.commons -package analyzer - -import scala.tools.nsc.Global - -class ThrowableObjects(g: Global) extends AnalyzerRule(g, "throwableObjects", Level.Warn) { - - import global._ - - private lazy val throwableTpe = typeOf[Throwable] - private lazy val throwableSym = throwableTpe.dealias.typeSymbol - - def analyze(unit: CompilationUnit): Unit = unit.body.foreach { - case md: ModuleDef => - val tpe = md.symbol.typeSignature - def fillInStackTraceSym: Symbol = - tpe.member(TermName("fillInStackTrace")).alternatives.find(_.paramLists == List(Nil)).get - - if (tpe <:< throwableTpe && fillInStackTraceSym.owner == throwableSym) { - report(md.pos, "objects should never extend Throwable unless they have no stack trace") - } - case _ => - } -} +//package com.avsystem.commons +//package analyzer +// +//import scala.tools.nsc.Global +// +//class ThrowableObjects(g: Global) extends AnalyzerRule(g, "throwableObjects", Level.Warn) { +// +// import global._ +// +// private lazy val throwableTpe = typeOf[Throwable] +// private lazy val throwableSym = throwableTpe.dealias.typeSymbol +// +// def analyze(unit: CompilationUnit): Unit = unit.body.foreach { +// case md: ModuleDef => +// val tpe = md.symbol.typeSignature +// def fillInStackTraceSym: Symbol = +// tpe.member(TermName("fillInStackTrace")).alternatives.find(_.paramLists == List(Nil)).get +// +// if (tpe <:< throwableTpe && fillInStackTraceSym.owner == throwableSym) { +// report(md.pos, "objects should never extend Throwable unless they have no stack trace") +// } +// case _ => +// } +//} diff --git a/analyzer/src/main/scala/com/avsystem/commons/analyzer/ValueEnumExhaustiveMatch.scala b/analyzer/src/main/scala/com/avsystem/commons/analyzer/ValueEnumExhaustiveMatch.scala index afbe280c7..548aab590 100644 --- a/analyzer/src/main/scala/com/avsystem/commons/analyzer/ValueEnumExhaustiveMatch.scala +++ b/analyzer/src/main/scala/com/avsystem/commons/analyzer/ValueEnumExhaustiveMatch.scala @@ -1,50 +1,50 @@ -package com.avsystem.commons -package analyzer - -import scala.collection.mutable -import scala.tools.nsc.Global - -class ValueEnumExhaustiveMatch(g: Global) extends AnalyzerRule(g, "valueEnumExhaustiveMatch") { - - import global._ - - lazy val valueEnumTpe: Type = classType("com.avsystem.commons.misc.ValueEnum") - lazy val ExistentialType(_, TypeRef(miscPackageTpe, valueEnumCompanionSym, _)) = - classType("com.avsystem.commons.misc.ValueEnumCompanion") - - def analyze(unit: CompilationUnit): Unit = if (valueEnumTpe != NoType) { - unit.body.foreach(analyzeTree { - case tree@Match(selector, cases) if selector.tpe <:< valueEnumTpe => - val expectedCompanionTpe = TypeRef(miscPackageTpe, valueEnumCompanionSym, List(selector.tpe)) - val companion = selector.tpe.typeSymbol.companion - val companionTpe = companion.toType - if (companionTpe <:< expectedCompanionTpe) { - val unmatched = new mutable.LinkedHashSet[Symbol] - companionTpe.decls.iterator - .filter(s => s.isVal && s.isFinal && !s.isLazy && s.typeSignature <:< selector.tpe) - .map(_.getterIn(companion)).filter(_.isPublic).foreach(unmatched.add) - - def findMatchedEnums(pattern: Tree): Unit = pattern match { - case Bind(_, body) => findMatchedEnums(body) - case Alternative(patterns) => patterns.foreach(findMatchedEnums) - case Ident(termNames.WILDCARD) => unmatched.clear() - case _: Ident | _: Select => unmatched.remove(pattern.symbol) - case _: Literal => - case _ => unmatched.clear() - } - - cases.iterator.foreach { - case CaseDef(pattern, EmptyTree, _) => findMatchedEnums(pattern) - case _ => unmatched.clear() - } - - if (unmatched.nonEmpty) { - val what = - if (unmatched.size > 1) "inputs: " + unmatched.map(_.nameString).mkString(", ") - else "input: " + unmatched.head.nameString - report(tree.pos, "match may not be exhaustive.\nIt would fail on the following " + what) - } - } - }) - } -} +//package com.avsystem.commons +//package analyzer +// +//import scala.collection.mutable +//import scala.tools.nsc.Global +// +//class ValueEnumExhaustiveMatch(g: Global) extends AnalyzerRule(g, "valueEnumExhaustiveMatch") { +// +// import global._ +// +// lazy val valueEnumTpe: Type = classType("com.avsystem.commons.misc.ValueEnum") +// lazy val ExistentialType(_, TypeRef(miscPackageTpe, valueEnumCompanionSym, _)) = +// classType("com.avsystem.commons.misc.ValueEnumCompanion") +// +// def analyze(unit: CompilationUnit): Unit = if (valueEnumTpe != NoType) { +// unit.body.foreach(analyzeTree { +// case tree@Match(selector, cases) if selector.tpe <:< valueEnumTpe => +// val expectedCompanionTpe = TypeRef(miscPackageTpe, valueEnumCompanionSym, List(selector.tpe)) +// val companion = selector.tpe.typeSymbol.companion +// val companionTpe = companion.toType +// if (companionTpe <:< expectedCompanionTpe) { +// val unmatched = new mutable.LinkedHashSet[Symbol] +// companionTpe.decls.iterator +// .filter(s => s.isVal && s.isFinal && !s.isLazy && s.typeSignature <:< selector.tpe) +// .map(_.getterIn(companion)).filter(_.isPublic).foreach(unmatched.add) +// +// def findMatchedEnums(pattern: Tree): Unit = pattern match { +// case Bind(_, body) => findMatchedEnums(body) +// case Alternative(patterns) => patterns.foreach(findMatchedEnums) +// case Ident(termNames.WILDCARD) => unmatched.clear() +// case _: Ident | _: Select => unmatched.remove(pattern.symbol) +// case _: Literal => +// case _ => unmatched.clear() +// } +// +// cases.iterator.foreach { +// case CaseDef(pattern, EmptyTree, _) => findMatchedEnums(pattern) +// case _ => unmatched.clear() +// } +// +// if (unmatched.nonEmpty) { +// val what = +// if (unmatched.size > 1) "inputs: " + unmatched.map(_.nameString).mkString(", ") +// else "input: " + unmatched.head.nameString +// report(tree.pos, "match may not be exhaustive.\nIt would fail on the following " + what) +// } +// } +// }) +// } +//} diff --git a/analyzer/src/main/scala/com/avsystem/commons/analyzer/VarargsAtLeast.scala b/analyzer/src/main/scala/com/avsystem/commons/analyzer/VarargsAtLeast.scala index 50e2ff5b7..401eb8e9f 100644 --- a/analyzer/src/main/scala/com/avsystem/commons/analyzer/VarargsAtLeast.scala +++ b/analyzer/src/main/scala/com/avsystem/commons/analyzer/VarargsAtLeast.scala @@ -1,36 +1,96 @@ package com.avsystem.commons package analyzer -import scala.tools.nsc.Global +import com.avsystem.commons.analyzer.{AnalyzerRule, ExplicitGenerics} +import com.avsystem.commons.analyzer.ExplicitGenerics.explicitGenericsSymbol +import com.avsystem.commons.analyzer.VarargsAtLeast.atLeastSymbol +import dotty.tools.dotc.ast.tpd.* +import dotty.tools.dotc.core.Constants.Constant +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Symbols +//package com.avsystem.commons +//package analyzer +// +//final class VarargsAtLeast extends AnalyzerRule("varargsAtLeast"): +// +// lazy val atLeastAnnotTpe = classType("com.avsystem.commons.annotation.atLeast") +// +// def analyze(unit: CompilationUnit): Unit = if atLeastAnnotTpe != NoType then +// def isVarargParam(tree: Tree) = tree match +// case Typed(_, Ident(typeNames.WILDCARD_STAR)) => true +// case _ => false +// +// unit.body.foreach(analyzeTree { +// case t @ Apply(fun, args) +// if fun.tpe != null && fun.tpe.params.lastOption +// .map(_.tpe.typeSymbol) +// .contains(definitions.RepeatedParamClass) && +// !args.lastOption.exists(isVarargParam) => +// val required = +// fun.tpe.params.last.annotations +// .find(_.tree.tpe <:< atLeastAnnotTpe) +// .map(_.tree.children.tail) +// .collect { case List(Literal(Constant(n: Int))) => +// n +// } +// .getOrElse(0) +// +// val actual = args.size - fun.tpe.params.size + 1 +// +// if actual < required then +// report( +// t.pos, +// s"This method requires at least $required arguments for its repeated parameter, $actual passed." +// ) +// +// end if +// }) +// +//end VarargsAtLeast -class VarargsAtLeast(g: Global) extends AnalyzerRule(g, "varargsAtLeast") { +final class VarargsAtLeast extends AnalyzerRule("varargsAtLeast"): - import global._ + override def transformApply(tree: Apply)(using Context): Tree = tree.tap { + case Apply(fun: Tree, argss: List[Tree]) => + val paramss = fun.symbol.denot.paramSymss - lazy val atLeastAnnotTpe = classType("com.avsystem.commons.annotation.atLeast") + paramss.zip(argss).foreach { case (params, args) => +// params.zip(args).foreach { case (param, arg) => +// println("dupa") +// } - def analyze(unit: CompilationUnit): Unit = if (atLeastAnnotTpe != NoType) { - def isVarargParam(tree: Tree) = tree match { - case Typed(_, Ident(typeNames.WILDCARD_STAR)) => true - case _ => false - } +// param.getAnnotation(atLeastSymbol).flatMap(_.argumentConstant(0)).map(_.intValue).foreach { required => - unit.body.foreach(analyzeTree { - case t@Apply(fun, args) - if fun.tpe != null && fun.tpe.params.lastOption.map(_.tpe.typeSymbol).contains(definitions.RepeatedParamClass) && - !args.lastOption.exists(isVarargParam) => + println("dupa") + } - val required = - fun.tpe.params.last.annotations.find(_.tree.tpe <:< atLeastAnnotTpe).map(_.tree.children.tail).collect { - case List(Literal(Constant(n: Int))) => n - }.getOrElse(0) +// if param.isRepeatedParam && !args.lastOption.exists { +// case Typed(_, Ident(tpnme.WILDCARD_STAR)) => true +// case _ => false +// } then +// val required = param.annotations +// .find(_.tree.tpe <:< atLeastAnnotTpe) +// .map(_.tree.children.tail) +// .collect { case List(Literal(Constant(n: Int))) => +// n +// } +// .getOrElse(0) +// +// val actual = args.size - params.size + 1 +// +// if actual < required then +// report( +// s"This method requires at least $required arguments for its repeated parameter, $actual passed.", +// tree +// ) + case _ => + } - val actual = args.size - fun.tpe.params.size + 1 +end VarargsAtLeast - if (actual < required) { - report(t.pos, - s"This method requires at least $required arguments for its repeated parameter, $actual passed.") - } - }) - } -} +object VarargsAtLeast: + + private final val atLeastSymbol = + (ctx: Context) ?=> Symbols.requiredClass("com.avsystem.commons.annotation.atLeast") + +end VarargsAtLeast diff --git a/analyzer/src/test/scala/com/avsystem/commons/analyzer/AnalyzerTest.scala b/analyzer/src/test/scala/com/avsystem/commons/analyzer/AnalyzerTest.scala index 989647382..a807c2407 100644 --- a/analyzer/src/test/scala/com/avsystem/commons/analyzer/AnalyzerTest.scala +++ b/analyzer/src/test/scala/com/avsystem/commons/analyzer/AnalyzerTest.scala @@ -1,42 +1,56 @@ package com.avsystem.commons package analyzer +import dotty.tools.dotc.Compiler +import dotty.tools.dotc.core.Contexts.{Context, ContextBase} +import dotty.tools.dotc.plugins.Plugin import org.scalactic.source.Position import org.scalatest.Assertions -import scala.reflect.internal.util.BatchSourceFile -import scala.tools.nsc.plugins.Plugin -import scala.tools.nsc.{Global, Settings} +import scala.util.chaining.scalaUtilChainingOps -trait AnalyzerTest { this: Assertions => - val settings = new Settings - settings.usejavacp.value = true - settings.Yrangepos.value = true - settings.pluginOptions.value ++= List("AVSystemAnalyzer:+_") +trait AnalyzerTest: + this: Assertions => - val compiler: Global = new Global(settings) { global => - override protected def loadRoughPluginsList(): List[Plugin] = - new AnalyzerPlugin(global) :: super.loadRoughPluginsList() - } + lazy val compiler = new Compiler - def compile(source: String): Unit = { - compiler.reporter.reset() - val run = new compiler.Run - run.compileSources(List(new BatchSourceFile("test.scala", source))) - } + protected val pluginOptions: List[String] = Nil - def assertErrors(source: String)(implicit pos: Position): Unit = { + protected final def assertErrors(source: String)(using Position): Unit = + given ctx: Context = compilerContext compile(source) - assert(compiler.reporter.hasErrors) - } + assert(ctx.reporter.hasErrors) - def assertErrors(errors: Int, source: String)(implicit pos: Position): Unit = { + end assertErrors + + protected final def assertErrors(errors: Int, source: String)(using Position): Unit = + given ctx: Context = compilerContext compile(source) - assert(compiler.reporter.errorCount == errors) - } + assert(ctx.reporter.errorCount == errors) + + end assertErrors - def assertNoErrors(source: String)(implicit pos: Position): Unit = { + protected final def assertNoErrors(source: String)(using Position): Unit = + given ctx: Context = compilerContext compile(source) - assert(!compiler.reporter.hasErrors) - } -} + assert(!ctx.reporter.hasErrors) + + end assertNoErrors + + private def compile(source: String)(using Position, Context): Unit = + compiler.newRun.compileFromStrings(source :: Nil) + + private def compilerContext: Context = + val base = new ContextBase: + override protected def loadRoughPluginsList(using Context): List[Plugin] = + new AnalyzerPlugin :: super.loadRoughPluginsList + + base.initialCtx.fresh.tap { ctx => + ctx.setSetting(ctx.settings.usejavacp, true) + ctx.setSetting(ctx.settings.pluginOptions, "AVSystemAnalyzer:+_" :: this.pluginOptions) + base.initialize()(using ctx) + } + + end compilerContext + +end AnalyzerTest diff --git a/analyzer/src/test/scala/com/avsystem/commons/analyzer/Any2StringAddTest.scala b/analyzer/src/test/scala/com/avsystem/commons/analyzer/Any2StringAddTest.scala deleted file mode 100644 index 0da8b89c1..000000000 --- a/analyzer/src/test/scala/com/avsystem/commons/analyzer/Any2StringAddTest.scala +++ /dev/null @@ -1,36 +0,0 @@ -package com.avsystem.commons -package analyzer - -import org.scalatest.funsuite.AnyFunSuite - -class Any2StringAddTest extends AnyFunSuite with AnalyzerTest { - test("any2stringadd should be rejected") { - assertErrors( - """ - |object whatever { - | whatever + "fag" - |} - """.stripMargin - ) - } - - test("toString should not be rejected") { - assertNoErrors( - """ - |object whatever { - | whatever.toString + "fag" - |} - """.stripMargin - ) - } - - test("string interpolation should not be rejected") { - assertNoErrors( - """ - |object whatever { - | s"${whatever}fag" - |} - """.stripMargin - ) - } -} diff --git a/analyzer/src/test/scala/com/avsystem/commons/analyzer/BadSingletonComponentTest.scala b/analyzer/src/test/scala/com/avsystem/commons/analyzer/BadSingletonComponentTest.scala index d6e5c1037..6a5b9a1b3 100644 --- a/analyzer/src/test/scala/com/avsystem/commons/analyzer/BadSingletonComponentTest.scala +++ b/analyzer/src/test/scala/com/avsystem/commons/analyzer/BadSingletonComponentTest.scala @@ -3,9 +3,11 @@ package analyzer import org.scalatest.funsuite.AnyFunSuite -class BadSingletonComponentTest extends AnyFunSuite with AnalyzerTest { +final class BadSingletonComponentTest extends AnyFunSuite with AnalyzerTest: + test("general") { - assertErrors(5, + assertErrors( + 5, """ |import com.avsystem.commons.di._ | @@ -26,4 +28,5 @@ class BadSingletonComponentTest extends AnyFunSuite with AnalyzerTest { """.stripMargin ) } -} + +end BadSingletonComponentTest diff --git a/analyzer/src/test/scala/com/avsystem/commons/analyzer/BasePackageTest.scala b/analyzer/src/test/scala/com/avsystem/commons/analyzer/BasePackageTest.scala index 1d6a03009..64f2e63b2 100644 --- a/analyzer/src/test/scala/com/avsystem/commons/analyzer/BasePackageTest.scala +++ b/analyzer/src/test/scala/com/avsystem/commons/analyzer/BasePackageTest.scala @@ -3,12 +3,11 @@ package analyzer import org.scalatest.funsuite.AnyFunSuite -class BasePackageTest extends AnyFunSuite with AnalyzerTest { - settings.pluginOptions.value ++= List("AVSystemAnalyzer:+basePackage:com.avsystem.commons") +final class BasePackageTest extends AnyFunSuite with AnalyzerTest: + override protected val pluginOptions = List("AVSystemAnalyzer:+basePackage:com.avsystem.commons") test("base package only") { - assertNoErrors( - """ + assertNoErrors(""" |package com.avsystem.commons | |object bar @@ -16,8 +15,7 @@ class BasePackageTest extends AnyFunSuite with AnalyzerTest { } test("chained base package") { - assertNoErrors( - """ + assertNoErrors(""" |package com.avsystem |package commons | @@ -26,8 +24,7 @@ class BasePackageTest extends AnyFunSuite with AnalyzerTest { } test("base package with chained subpackage") { - assertNoErrors( - """ + assertNoErrors(""" |package com.avsystem.commons |package core | @@ -36,8 +33,7 @@ class BasePackageTest extends AnyFunSuite with AnalyzerTest { } test("base package object") { - assertNoErrors( - """ + assertNoErrors(""" |package com.avsystem | |package object commons @@ -45,8 +41,7 @@ class BasePackageTest extends AnyFunSuite with AnalyzerTest { } test("base package object with imports") { - assertNoErrors( - """ + assertNoErrors(""" |package com.avsystem | |import scala.collection.mutable.Seq @@ -57,43 +52,51 @@ class BasePackageTest extends AnyFunSuite with AnalyzerTest { } test("no base package") { - assertErrors(1, + assertErrors( + 1, """ |object bar - |""".stripMargin) + |""".stripMargin + ) } test("no base package with imports") { - assertErrors(1, + assertErrors( + 1, """ |import scala.collection.mutable.Seq |import scala.collection.mutable.Set | |object bar - |""".stripMargin) + |""".stripMargin + ) } - test("wrong base package") { - assertErrors(1, + assertErrors( + 1, """ |package com.avsystem.kommons | |object bar - |""".stripMargin) + |""".stripMargin + ) } test("unchained subpackage") { - assertErrors(1, + assertErrors( + 1, """ |package com.avsystem.commons.core | |object bar - |""".stripMargin) + |""".stripMargin + ) } test("unchained subpackage with imports") { - assertErrors(1, + assertErrors( + 1, """ |package com.avsystem.commons.core | @@ -101,6 +104,8 @@ class BasePackageTest extends AnyFunSuite with AnalyzerTest { |import scala.collection.mutable.Set | |object bar - |""".stripMargin) + |""".stripMargin + ) } -} + +end BasePackageTest diff --git a/analyzer/src/test/scala/com/avsystem/commons/analyzer/CheckBincompatTest.scala b/analyzer/src/test/scala/com/avsystem/commons/analyzer/CheckBincompatTest.scala index 94d9946b3..02fe600c2 100644 --- a/analyzer/src/test/scala/com/avsystem/commons/analyzer/CheckBincompatTest.scala +++ b/analyzer/src/test/scala/com/avsystem/commons/analyzer/CheckBincompatTest.scala @@ -3,7 +3,8 @@ package analyzer import org.scalatest.funsuite.AnyFunSuite -class CheckBincompatTest extends AnyFunSuite with AnalyzerTest { +final class CheckBincompatTest extends AnyFunSuite with AnalyzerTest: + test("definitions of @bincompat annotated symbols should not be rejected") { assertNoErrors( """ @@ -19,7 +20,8 @@ class CheckBincompatTest extends AnyFunSuite with AnalyzerTest { } test("usage of @bincompat annotated symbols should be rejected") { - assertErrors(3, + assertErrors( + 3, """ |import com.avsystem.commons.annotation.bincompat | @@ -39,4 +41,5 @@ class CheckBincompatTest extends AnyFunSuite with AnalyzerTest { """.stripMargin ) } -} + +end CheckBincompatTest diff --git a/analyzer/src/test/scala/com/avsystem/commons/analyzer/CheckMacroPrivateTest.scala b/analyzer/src/test/scala/com/avsystem/commons/analyzer/CheckMacroPrivateTest.scala index dd5765c62..50c3b402e 100644 --- a/analyzer/src/test/scala/com/avsystem/commons/analyzer/CheckMacroPrivateTest.scala +++ b/analyzer/src/test/scala/com/avsystem/commons/analyzer/CheckMacroPrivateTest.scala @@ -3,7 +3,8 @@ package analyzer import org.scalatest.funsuite.AnyFunSuite -class CheckMacroPrivateTest extends AnyFunSuite with AnalyzerTest { +final class CheckMacroPrivateTest extends AnyFunSuite with AnalyzerTest: + test("macro private method invoked directly should be rejected") { assertErrors( """ @@ -56,4 +57,5 @@ class CheckMacroPrivateTest extends AnyFunSuite with AnalyzerTest { """.stripMargin ) } -} + +end CheckMacroPrivateTest diff --git a/analyzer/src/test/scala/com/avsystem/commons/analyzer/ConstantDeclarationsTest.scala b/analyzer/src/test/scala/com/avsystem/commons/analyzer/ConstantDeclarationsTest.scala index a4e52edd4..a0b79ed9f 100644 --- a/analyzer/src/test/scala/com/avsystem/commons/analyzer/ConstantDeclarationsTest.scala +++ b/analyzer/src/test/scala/com/avsystem/commons/analyzer/ConstantDeclarationsTest.scala @@ -3,9 +3,11 @@ package analyzer import org.scalatest.funsuite.AnyFunSuite -class ConstantDeclarationsTest extends AnyFunSuite with AnalyzerTest { +final class ConstantDeclarationsTest extends AnyFunSuite with AnalyzerTest: + test("literal-valued constants should be non-lazy final vals with UpperCamelCase and no type annotation") { - assertErrors(4, + assertErrors( + 4, """ |object Whatever { | // bad @@ -17,11 +19,13 @@ class ConstantDeclarationsTest extends AnyFunSuite with AnalyzerTest { | // good | final val E = 10 |} - """.stripMargin) + """.stripMargin + ) } test("effectively final, non-literal UpperCamelCase vals should be final") { - assertErrors(1, + assertErrors( + 1, """ |object Whatever { | // bad @@ -31,12 +35,12 @@ class ConstantDeclarationsTest extends AnyFunSuite with AnalyzerTest { | final val B = "foo".trim | val c = "foo".trim |} - """.stripMargin) + """.stripMargin + ) } test("no constant checking in traits or non-final classes") { - assertNoErrors( - """ + assertNoErrors(""" |trait Whatever { | val a = 10 | val B = 10 @@ -56,8 +60,7 @@ class ConstantDeclarationsTest extends AnyFunSuite with AnalyzerTest { } test("no constant checking for overrides") { - assertNoErrors( - """ + assertNoErrors(""" |trait Whatever { | def a: Int |} @@ -69,8 +72,7 @@ class ConstantDeclarationsTest extends AnyFunSuite with AnalyzerTest { } test("no constant checking for privates") { - assertNoErrors( - """ + assertNoErrors(""" |object Whatever { | private val a = 10 | private val B = 10 @@ -80,4 +82,5 @@ class ConstantDeclarationsTest extends AnyFunSuite with AnalyzerTest { |} """.stripMargin) } -} + +end ConstantDeclarationsTest diff --git a/analyzer/src/test/scala/com/avsystem/commons/analyzer/DiscardedMonixTaskTest.scala b/analyzer/src/test/scala/com/avsystem/commons/analyzer/DiscardedMonixTaskTest.scala index 61fc5df95..7ea1c85fa 100644 --- a/analyzer/src/test/scala/com/avsystem/commons/analyzer/DiscardedMonixTaskTest.scala +++ b/analyzer/src/test/scala/com/avsystem/commons/analyzer/DiscardedMonixTaskTest.scala @@ -3,9 +3,11 @@ package analyzer import org.scalatest.funsuite.AnyFunSuite -class DiscardedMonixTaskTest extends AnyFunSuite with AnalyzerTest { +final class DiscardedMonixTaskTest extends AnyFunSuite with AnalyzerTest: + test("simple") { - assertErrors(10, + assertErrors( + 10, """ |import monix.eval.Task | @@ -34,4 +36,5 @@ class DiscardedMonixTaskTest extends AnyFunSuite with AnalyzerTest { """.stripMargin ) } -} + +end DiscardedMonixTaskTest diff --git a/analyzer/src/test/scala/com/avsystem/commons/analyzer/ExplicitGenericsTest.scala b/analyzer/src/test/scala/com/avsystem/commons/analyzer/ExplicitGenericsTest.scala index aa6e0417a..caff38429 100644 --- a/analyzer/src/test/scala/com/avsystem/commons/analyzer/ExplicitGenericsTest.scala +++ b/analyzer/src/test/scala/com/avsystem/commons/analyzer/ExplicitGenericsTest.scala @@ -2,8 +2,8 @@ package com.avsystem.commons package analyzer import org.scalatest.funsuite.AnyFunSuite +final class ExplicitGenericsTest extends AnyFunSuite with AnalyzerTest: -class ExplicitGenericsTest extends AnyFunSuite with AnalyzerTest { test("inferred generic should be rejected") { assertErrors( """ @@ -28,7 +28,6 @@ class ExplicitGenericsTest extends AnyFunSuite with AnalyzerTest { ) } - test("explicit generic should not be rejected") { assertNoErrors( """ @@ -52,4 +51,5 @@ class ExplicitGenericsTest extends AnyFunSuite with AnalyzerTest { """.stripMargin ) } -} + +end ExplicitGenericsTest diff --git a/analyzer/src/test/scala/com/avsystem/commons/analyzer/FindUsagesTest.scala b/analyzer/src/test/scala/com/avsystem/commons/analyzer/FindUsagesTest.scala index f1a7fd471..7d5cecc69 100644 --- a/analyzer/src/test/scala/com/avsystem/commons/analyzer/FindUsagesTest.scala +++ b/analyzer/src/test/scala/com/avsystem/commons/analyzer/FindUsagesTest.scala @@ -3,11 +3,12 @@ package analyzer import org.scalatest.funsuite.AnyFunSuite -class FindUsagesTest extends AnyFunSuite with AnalyzerTest { - settings.pluginOptions.value ++= List("AVSystemAnalyzer:+findUsages:java.lang.String") +final class FindUsagesTest extends AnyFunSuite with AnalyzerTest: + override protected val pluginOptions = List("AVSystemAnalyzer:+findUsages:java.lang.String") test("java.lang.String usages should be found") { - assertErrors(2, + assertErrors( + 2, """ |object whatever { | val x: String = String.valueOf(123) @@ -15,4 +16,5 @@ class FindUsagesTest extends AnyFunSuite with AnalyzerTest { """.stripMargin ) } -} + +end FindUsagesTest diff --git a/analyzer/src/test/scala/com/avsystem/commons/analyzer/ImplicitTypesTest.scala b/analyzer/src/test/scala/com/avsystem/commons/analyzer/ImplicitTypesTest.scala deleted file mode 100644 index f7d07a83f..000000000 --- a/analyzer/src/test/scala/com/avsystem/commons/analyzer/ImplicitTypesTest.scala +++ /dev/null @@ -1,20 +0,0 @@ -package com.avsystem.commons -package analyzer - -import org.scalatest.funsuite.AnyFunSuite - -class ImplicitTypesTest extends AnyFunSuite with AnalyzerTest { - test("implicit definitions without explicit types should be rejected") { - assertErrors(2, - """ - |object whatever { - | implicit val x = 5 - | implicit val y: Int = 5 - | implicit def conv(x: Int) = x.toString - | implicit def conv2(x: Int): String = x.toString - | implicit object objekt - | implicit class wtf(x: Int) - |} - """.stripMargin) - } -} diff --git a/analyzer/src/test/scala/com/avsystem/commons/analyzer/ImportJavaUtilTest.scala b/analyzer/src/test/scala/com/avsystem/commons/analyzer/ImportJavaUtilTest.scala index 162179c8d..53d917eb6 100644 --- a/analyzer/src/test/scala/com/avsystem/commons/analyzer/ImportJavaUtilTest.scala +++ b/analyzer/src/test/scala/com/avsystem/commons/analyzer/ImportJavaUtilTest.scala @@ -3,7 +3,8 @@ package analyzer import org.scalatest.funsuite.AnyFunSuite -class ImportJavaUtilTest extends AnyFunSuite with AnalyzerTest { +final class ImportJavaUtilTest extends AnyFunSuite with AnalyzerTest: + test("import java.util should be rejected") { assertErrors( """ @@ -13,4 +14,25 @@ class ImportJavaUtilTest extends AnyFunSuite with AnalyzerTest { """.stripMargin ) } -} + + test("import java.util as ju should not be rejected") { + assertNoErrors( + """ + |import java.util as ju + | + |object whatever + """.stripMargin + ) + } + + test("another import should not be rejected") { + assertNoErrors( + """ + |import java.lang + | + |object whatever + """.stripMargin + ) + } + +end ImportJavaUtilTest diff --git a/analyzer/src/test/scala/com/avsystem/commons/analyzer/OldStyleImplicitConversionTest.scala b/analyzer/src/test/scala/com/avsystem/commons/analyzer/OldStyleImplicitConversionTest.scala new file mode 100644 index 000000000..68cd6a6aa --- /dev/null +++ b/analyzer/src/test/scala/com/avsystem/commons/analyzer/OldStyleImplicitConversionTest.scala @@ -0,0 +1,47 @@ +package com.avsystem.commons +package analyzer + +import org.scalatest.funsuite.AnyFunSuite + +final class OldStyleImplicitConversionTest extends AnyFunSuite with AnalyzerTest: + + test("old-style implicit direct conversions should be reported") { + assertErrors( + 1, + """ + |object Whatever { + | implicit def intToString(i: Int): String = i.toString + |} + """.stripMargin + ) + } + + test("old-style implicit class conversions should be reported") { + assertErrors( + 2, + """ + |object Whatever { + | implicit class IntOps(i: Int) { + | def toStr: String = i.toString + | } + | implicit class LongOps(val l: Long) extends AnyVal { + | def toStr: String = l.toString + | } + |} + """.stripMargin + ) + } + + test("new-style implicit conversions should not be reported") { + assertNoErrors( + """ + |object Whatever { + | given intToString: Conversion[Int, String] = _.toString + | extension (i: Int) + | def toStr: String = i.toString + |} + """.stripMargin + ) + } + +end OldStyleImplicitConversionTest diff --git a/analyzer/src/test/scala/com/avsystem/commons/analyzer/TestUtils.scala b/analyzer/src/test/scala/com/avsystem/commons/analyzer/TestUtils.scala index 9a4e00c70..996ca8205 100644 --- a/analyzer/src/test/scala/com/avsystem/commons/analyzer/TestUtils.scala +++ b/analyzer/src/test/scala/com/avsystem/commons/analyzer/TestUtils.scala @@ -1,31 +1,32 @@ package com.avsystem.commons package analyzer -import com.avsystem.commons.annotation.{atLeast, explicitGenerics, macroPrivate} +import com.avsystem.commons.annotation.{atLeast, explicitGenerics} -import scala.reflect.macros.blackbox +import scala.quoted.{Expr, Quotes} -object TestUtils { - def need3Params(@atLeast(3) args: Any*) = () - - @macroPrivate - def macroPrivateMethod = 42 - - def invokeMacroPrivateMethod: Int = macro invokeMacroPrivateMethodImpl - - def invokeMacroPrivateMethodImpl(c: blackbox.Context): c.Tree = { - import c.universe._ - q"${c.prefix}.macroPrivateMethod" - } - - object Extractor { - @macroPrivate def unapply(any: Any): Option[Any] = None - } - - def genericMacroImpl[T](c: blackbox.Context)(arg: c.Tree): c.Tree = arg +object TestUtils: + def need3Params(@atLeast(3) args: Any*)(other: List[Int]) = () + // + // @macroPrivate + // def macroPrivateMethod = 42 + def genericMacroImpl[T](arg: Expr[T])(using Quotes): Expr[T] = arg @explicitGenerics def genericMethod[T](arg: T): T = arg + + // def invokeMacroPrivateMethod: Int = macro invokeMacroPrivateMethodImpl + // def invokeMacroPrivateMethodImpl(c: blackbox.Context): c.Tree = + // import c.universe.* + // q"${c.prefix}.macroPrivateMethod" + // + // end invokeMacroPrivateMethodImpl @explicitGenerics - def genericMacro[T](arg: T): T = macro genericMacroImpl[T] -} + inline def genericMacro[T](arg: T): T = ${ genericMacroImpl[T]('{ arg }) } + // object Extractor: + // @macroPrivate def unapply(any: Any): Option[Any] = None + // + + // end Extractor + +end TestUtils diff --git a/analyzer/src/test/scala/com/avsystem/commons/analyzer/ThrowableObjectsTest.scala b/analyzer/src/test/scala/com/avsystem/commons/analyzer/ThrowableObjectsTest.scala index 32f365927..c0dc141c0 100644 --- a/analyzer/src/test/scala/com/avsystem/commons/analyzer/ThrowableObjectsTest.scala +++ b/analyzer/src/test/scala/com/avsystem/commons/analyzer/ThrowableObjectsTest.scala @@ -3,12 +3,16 @@ package analyzer import org.scalatest.funsuite.AnyFunSuite -class ThrowableObjectsTest extends AnyFunSuite with AnalyzerTest { +final class ThrowableObjectsTest extends AnyFunSuite with AnalyzerTest: + test("throwable objects with stack trace should be rejected") { - assertErrors(1, + assertErrors( + 1, """ |object throwableObject extends Throwable |object noStackTraceThrowableObject extends Throwable with scala.util.control.NoStackTrace - """.stripMargin) + """.stripMargin + ) } -} + +end ThrowableObjectsTest diff --git a/analyzer/src/test/scala/com/avsystem/commons/analyzer/ValueEnumExhaustiveMatchTest.scala b/analyzer/src/test/scala/com/avsystem/commons/analyzer/ValueEnumExhaustiveMatchTest.scala index 5b1512c09..2f17efb76 100644 --- a/analyzer/src/test/scala/com/avsystem/commons/analyzer/ValueEnumExhaustiveMatchTest.scala +++ b/analyzer/src/test/scala/com/avsystem/commons/analyzer/ValueEnumExhaustiveMatchTest.scala @@ -3,7 +3,8 @@ package analyzer import org.scalatest.funsuite.AnyFunSuite -class ValueEnumExhaustiveMatchTest extends AnyFunSuite with AnalyzerTest { +final class ValueEnumExhaustiveMatchTest extends AnyFunSuite with AnalyzerTest: + def source(caseDefs: String): String = s""" |import com.avsystem.commons.misc._ @@ -23,62 +24,77 @@ class ValueEnumExhaustiveMatchTest extends AnyFunSuite with AnalyzerTest { """.stripMargin test("should report two unmatched enum values") { - assertErrors(source( - """ + assertErrors( + source( + """ |case Enumz.One => |case null => """.stripMargin - )) + ) + ) } test("should report one unmatched enum value") { - assertErrors(source( - """ + assertErrors( + source( + """ |case Enumz.One => |case Enumz.Two => """.stripMargin - )) + ) + ) } test("should report one unmatched by alternative enum value") { - assertErrors(source( - """ + assertErrors( + source( + """ |case One | Two => """.stripMargin - )) + ) + ) } test("should not report unmatched values on wildcard") { - assertNoErrors(source( - """ + assertNoErrors( + source( + """ |case _ => """.stripMargin - )) + ) + ) } test("should not report unmatched values with guard") { - assertNoErrors(source( - """ + assertNoErrors( + source( + """ |case x if x.ordinal > 1 => """.stripMargin - )) + ) + ) } test("should not report no unmatched values in alternative") { - assertNoErrors(source( - """ + assertNoErrors( + source( + """ |case One | Two | Three => """.stripMargin - )) + ) + ) } test("should not report no unmatched values") { - assertNoErrors(source( - """ + assertNoErrors( + source( + """ |case Enumz.One => |case Enumz.Two => |case Three => """.stripMargin - )) + ) + ) } -} + +end ValueEnumExhaustiveMatchTest diff --git a/analyzer/src/test/scala/com/avsystem/commons/analyzer/VarargsAtLeastTest.scala b/analyzer/src/test/scala/com/avsystem/commons/analyzer/VarargsAtLeastTest.scala index f6604398d..7ba87e423 100644 --- a/analyzer/src/test/scala/com/avsystem/commons/analyzer/VarargsAtLeastTest.scala +++ b/analyzer/src/test/scala/com/avsystem/commons/analyzer/VarargsAtLeastTest.scala @@ -3,14 +3,16 @@ package analyzer import org.scalatest.funsuite.AnyFunSuite -class VarargsAtLeastTest extends AnyFunSuite with AnalyzerTest { +final class VarargsAtLeastTest extends AnyFunSuite with AnalyzerTest: + test("too few varargs parameters should be rejected") { assertErrors( """ |import com.avsystem.commons.analyzer.TestUtils | + |object T |object whatever { - | TestUtils.need3Params(1, 2) + | TestUtils.need3Params(1, 2, "String", T)(List(8, 9)) |} """.stripMargin ) @@ -40,4 +42,5 @@ class VarargsAtLeastTest extends AnyFunSuite with AnalyzerTest { """.stripMargin ) } -} + +end VarargsAtLeastTest diff --git a/core3/src/main/scala/com/avsystem/commons/annotation/atLeast.scala b/core3/src/main/scala/com/avsystem/commons/annotation/atLeast.scala new file mode 100644 index 000000000..8f3ea72a4 --- /dev/null +++ b/core3/src/main/scala/com/avsystem/commons/annotation/atLeast.scala @@ -0,0 +1,18 @@ +package com.avsystem.commons +package annotation + +import scala.annotation.StaticAnnotation + +/** + * When applied on varargs parameter, indicates that at least some number of parameters is required. + * This is later checked by the static analyzer. + *
+ * WARNING: implementation of method which takes a varargs parameter may NOT assume that given number of + * arguments will always be passed, because it's still possible to pass a `Seq` where + * varargs parameter is required using the `: _*` ascription, e.g. + * {{{ + * varargsMethod(List(): _*) + * }}} + * and that is not checked by the static analyzer. + */ +final class atLeast(n: Int) extends StaticAnnotation diff --git a/core3/src/main/scala/com/avsystem/commons/annotation/explicitGenerics.scala b/core3/src/main/scala/com/avsystem/commons/annotation/explicitGenerics.scala new file mode 100644 index 000000000..dc7b63374 --- /dev/null +++ b/core3/src/main/scala/com/avsystem/commons/annotation/explicitGenerics.scala @@ -0,0 +1,21 @@ +package com.avsystem.commons +package annotation + +import scala.annotation.StaticAnnotation + +/** + * When applied on generic method, requires that all the type parameters are given explicitly + * (cannot be inferred by the compiler). This is meant primarily for methods whose generics cannot be + * inferred from method arguments. Requiring that the programmer specifies them explicitly is a protection + * against the compiler inferring `Nothing` or `Null`. + * {{{ + * @explicitGenerics + * def readJson[T: GenCodec](json: String): T = ... + * + * // raise error, because otherwise we have a hidden bug - the compiler infers `Nothing` in place of `T` + * val x: MyType = readJson("{}") + * // ok + * val x = readJson[MyType]("{}") + * }}} + */ +final class explicitGenerics extends StaticAnnotation diff --git a/core3/src/main/scala/com/avsystem/commons/debug/PrintTree.scala b/core3/src/main/scala/com/avsystem/commons/debug/PrintTree.scala new file mode 100644 index 000000000..4538bde5c --- /dev/null +++ b/core3/src/main/scala/com/avsystem/commons/debug/PrintTree.scala @@ -0,0 +1,12 @@ +package com.avsystem.commons +package debug + +import scala.quoted.{Expr, Quotes, Type} + +object PrintTree { + inline def printTree[T](inline x: T): Unit = ${printTreeImpl('x)} + def printTreeImpl[T: Type](x: Expr[T])(using quotes: Quotes): Expr[Unit] = + import quotes.reflect.* + println(x.asTerm.show(using Printer.TreeStructure)) + '{()} +} diff --git a/core3/src/test/resources/SimpleApi.txt b/core3/src/test/resources/SimpleApi.txt new file mode 100644 index 000000000..d261f966c --- /dev/null +++ b/core3/src/test/resources/SimpleApi.txt @@ -0,0 +1,6 @@ +com.avsystem.commons.rpc.SimpleApi { + def noParamLists: Int + def noParams(): String + def multiParamLists(int: Int)(str: String)(): Double + def takesImplicits(int: Int)(implicit ord: Ordering[Int], moar: DummyImplicit): String +} \ No newline at end of file diff --git a/core3/src/test/resources/StringApi.txt b/core3/src/test/resources/StringApi.txt new file mode 100644 index 000000000..f48dd2fa5 --- /dev/null +++ b/core3/src/test/resources/StringApi.txt @@ -0,0 +1,54 @@ +String { + def length(): Int + def isEmpty(): Boolean + def charAt(x$1: Int): Char + def codePointAt(x$1: Int): Int + def codePointBefore(x$1: Int): Int + def codePointCount(x$1: Int, x$2: Int): Int + def offsetByCodePoints(x$1: Int, x$2: Int): Int + def getChars(x$1: Int, x$2: Int, x$3: Array[Char], x$4: Int): Unit + def getBytes(x$1: Int, x$2: Int, x$3: Array[Byte], x$4: Int): Unit + def getBytes(x$1: String): Array[Byte] + def getBytes(x$1: java.nio.charset.Charset): Array[Byte] + def getBytes(): Array[Byte] + def contentEquals(x$1: StringBuffer): Boolean + def contentEquals(x$1: CharSequence): Boolean + def equalsIgnoreCase(x$1: String): Boolean + def compareTo(x$1: String): Int + def compareToIgnoreCase(x$1: String): Int + def regionMatches(x$1: Int, x$2: String, x$3: Int, x$4: Int): Boolean + def regionMatches(x$1: Boolean, x$2: Int, x$3: String, x$4: Int, x$5: Int): Boolean + def startsWith(x$1: String, x$2: Int): Boolean + def startsWith(x$1: String): Boolean + def endsWith(x$1: String): Boolean + def indexOf(x$1: Int): Int + def indexOf(x$1: Int, x$2: Int): Int + def lastIndexOf(x$1: Int): Int + def lastIndexOf(x$1: Int, x$2: Int): Int + def indexOf(x$1: String): Int + def indexOf(x$1: String, x$2: Int): Int + def lastIndexOf(x$1: String): Int + def lastIndexOf(x$1: String, x$2: Int): Int + def substring(x$1: Int): String + def substring(x$1: Int, x$2: Int): String + def subSequence(x$1: Int, x$2: Int): CharSequence + def concat(x$1: String): String + def replace(x$1: Char, x$2: Char): String + def matches(x$1: String): Boolean + def contains(x$1: CharSequence): Boolean + def replaceFirst(x$1: String, x$2: String): String + def replaceAll(x$1: String, x$2: String): String + def replace(x$1: CharSequence, x$2: CharSequence): String + def split(x$1: String, x$2: Int): Array[String] + def split(x$1: String): Array[String] + def toLowerCase(x$1: java.util.Locale): String + def toLowerCase(): String + def toUpperCase(x$1: java.util.Locale): String + def toUpperCase(): String + def trim(): String + def toCharArray(): Array[Char] + def intern(): String + def +(x$1: Any): String + def chars(): java.util.stream.IntStream + def codePoints(): java.util.stream.IntStream +} \ No newline at end of file diff --git a/project/Commons.scala b/project/Commons.scala index d20714536..7e2adcd21 100644 --- a/project/Commons.scala +++ b/project/Commons.scala @@ -1,15 +1,11 @@ import com.github.ghik.sbt.nosbt.ProjectGroup import com.typesafe.tools.mima.plugin.MimaKeys.* import org.portablescala.sbtplatformdeps.PlatformDepsPlugin.autoImport.* -import org.scalajs.jsdependencies.sbtplugin.JSDependenciesPlugin import org.scalajs.jsenv.nodejs.NodeJSEnv -import org.scalajs.sbtplugin.ScalaJSPlugin import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport.* -import pl.project13.scala.sbt.JmhPlugin import pl.project13.scala.sbt.JmhPlugin.JmhKeys.* import sbt.* import sbt.Keys.* -import sbtghactions.GenerativePlugin import sbtghactions.GenerativePlugin.autoImport.* import sbtide.Keys.* import sbtunidoc.BaseUnidocPlugin.autoImport.{unidoc, unidocProjectFilter} @@ -41,9 +37,9 @@ object Commons extends ProjectGroup("commons") { val upickleVersion = "3.1.2" // benchmark only val scalajsBenchmarkVersion = "0.10.0" val slf4jVersion = "2.0.13" // test only - - // for binary compatibility checking - val previousCompatibleVersions: Set[String] = Set("2.2.4") +// +// // for binary compatibility checking +// val previousCompatibleVersions: Set[String] = Set("2.2.4") override def globalSettings: Seq[Def.Setting[_]] = Seq( cancelable := true, @@ -69,7 +65,8 @@ object Commons extends ProjectGroup("commons") { Developer("ghik", "Roman Janusz", "r.janusz@avsystem.com", url("https://github.com/ghik")), ), - scalaVersion := "2.13.14", +// scalaVersion := "2.13.14", + scalaVersion := "3.5.0", compileOrder := CompileOrder.Mixed, githubWorkflowTargetTags ++= Seq("v*"), @@ -123,7 +120,7 @@ object Commons extends ProjectGroup("commons") { override def commonSettings: Seq[Def.Setting[_]] = Seq( Compile / scalacOptions ++= Seq( "-encoding", "utf-8", - "-Yrangepos", +// "-Yrangepos", "-explaintypes", "-feature", "-deprecation", @@ -133,11 +130,11 @@ object Commons extends ProjectGroup("commons") { "-language:dynamics", "-language:experimental.macros", "-language:higherKinds", - "-Xfatal-warnings", - "-Xsource:3", - "-Xlint:-missing-interpolator,-adapted-args,-unused,_", - "-Ycache-plugin-class-loader:last-modified", - "-Ycache-macro-class-loader:last-modified", +// "-Xfatal-warnings", +// "-Xsource:3", +// "-Xlint:-missing-interpolator,-adapted-args,-unused,_", +// "-Ycache-plugin-class-loader:last-modified", +// "-Ycache-macro-class-loader:last-modified", ), Compile / scalacOptions ++= { @@ -172,9 +169,9 @@ object Commons extends ProjectGroup("commons") { "org.apache.commons" % "commons-io" % commonsIoVersion % Test, "org.slf4j" % "slf4j-simple" % slf4jVersion % Test, ), - mimaPreviousArtifacts := previousCompatibleVersions.map { previousVersion => - organization.value % s"${name.value}_${scalaBinaryVersion.value}" % previousVersion - }, +// mimaPreviousArtifacts := previousCompatibleVersions.map { previousVersion => +// organization.value % s"${name.value}_${scalaBinaryVersion.value}" % previousVersion +// }, Test / jsEnv := new NodeJSEnv(NodeJSEnv.Config().withEnv(Map( "RESOURCES_DIR" -> (Test / resourceDirectory).value.absolutePath) )), @@ -208,7 +205,7 @@ object Commons extends ProjectGroup("commons") { .enablePlugins(ScalaUnidocPlugin) .aggregate( jvm, - js, +// js, ) .settings( noPublishSettings, @@ -218,38 +215,38 @@ object Commons extends ProjectGroup("commons") { ScalaUnidoc / unidoc / unidocProjectFilter := inAnyProject -- inProjects( analyzer, - macros, - `core-js`, - comprof, +// macros, +// `core-js`, +// comprof, ), ) lazy val jvm = mkSubProject.in(file(".jvm")) .aggregate( analyzer, - macros, - core, - jetty, - mongo, - hocon, - spring, - redis, - ) - .settings(aggregateProjectSettings) - - lazy val js = mkSubProject.in(file(".js")) - .aggregate( - `core-js`, - `mongo-js`, +// macros, + core3, +// jetty, +// mongo, +// hocon, +// spring, +// redis, ) .settings(aggregateProjectSettings) +// +// lazy val js = mkSubProject.in(file(".js")) +// .aggregate( +// `core-js`, +// `mongo-js`, +// ) +// .settings(aggregateProjectSettings) lazy val analyzer = mkSubProject - .dependsOn(core % Test) + .dependsOn(core3 % Test) .settings( jvmCommonSettings, libraryDependencies ++= Seq( - "org.scala-lang" % "scala-compiler" % scalaVersion.value, + "org.scala-lang" % "scala3-compiler_3" % scalaVersion.value, "io.monix" %% "monix" % monixVersion % Test, ), ) @@ -271,169 +268,180 @@ object Commons extends ProjectGroup("commons") { if (forIdeaImport) Seq.empty else Seq(name := (proj / name).value) - lazy val macros = mkSubProject.settings( - jvmCommonSettings, - libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value, - ) - - lazy val core = mkSubProject - .dependsOn(macros) +// lazy val macros = mkSubProject.settings( +// jvmCommonSettings, +// libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value, +// ) +// +// lazy val core = mkSubProject +// .dependsOn(macros) +// .settings( +// jvmCommonSettings, +// sourceDirsSettings(_ / "jvm"), +// libraryDependencies ++= Seq( +// "com.google.code.findbugs" % "jsr305" % jsr305Version % Optional, +// "com.google.guava" % "guava" % guavaVersion % Optional, +// "io.monix" %% "monix" % monixVersion % Optional, +// ), +// ) + lazy val core3 = mkSubProject +// .dependsOn(macros) .settings( jvmCommonSettings, sourceDirsSettings(_ / "jvm"), libraryDependencies ++= Seq( "com.google.code.findbugs" % "jsr305" % jsr305Version % Optional, "com.google.guava" % "guava" % guavaVersion % Optional, - "io.monix" %% "monix" % monixVersion % Optional, - ), - ) - - lazy val `core-js` = mkSubProject.in(core.base / "js") - .enablePlugins(ScalaJSPlugin) - .configure(p => if (forIdeaImport) p.dependsOn(core) else p) - .dependsOn(macros) - .settings( - jsCommonSettings, - sameNameAs(core), - sourceDirsSettings(_.getParentFile), - libraryDependencies ++= Seq( - "io.monix" %%% "monix" % monixVersion % Optional, - ) - ) - - lazy val mongo = mkSubProject - .dependsOn(core % CompileAndTest) - .settings( - jvmCommonSettings, - sourceDirsSettings(_ / "jvm"), - libraryDependencies ++= Seq( - "com.google.guava" % "guava" % guavaVersion, - "io.monix" %% "monix" % monixVersion, - "org.mongodb" % "mongodb-driver-core" % mongoVersion, - "org.mongodb" % "mongodb-driver-sync" % mongoVersion % Optional, - "org.mongodb" % "mongodb-driver-reactivestreams" % mongoVersion % Optional, - "org.mongodb.scala" %% "mongo-scala-driver" % mongoVersion % Optional, - ), - ) - - // only to allow @mongoId & MongoEntity to be usedJS/JVM cross-compiled code - lazy val `mongo-js` = mkSubProject.in(mongo.base / "js") - .enablePlugins(ScalaJSPlugin) - .configure(p => if (forIdeaImport) p.dependsOn(mongo) else p) - .dependsOn(`core-js`) - .settings( - jsCommonSettings, - sameNameAs(mongo), - sourceDirsSettings(_.getParentFile), - ) - - lazy val redis = mkSubProject - .dependsOn(core % CompileAndTest) - .settings( - jvmCommonSettings, - libraryDependencies ++= Seq( - "com.google.guava" % "guava" % guavaVersion, - "org.apache.pekko" %% "pekko-stream" % pekkoVersion, - "com.typesafe.scala-logging" %% "scala-logging" % scalaLoggingVersion, - "io.monix" %% "monix" % monixVersion, - ), - Test / parallelExecution := false, - ) - - lazy val hocon = mkSubProject - .dependsOn(core % CompileAndTest) - .settings( - jvmCommonSettings, - libraryDependencies ++= Seq( - "com.typesafe" % "config" % typesafeConfigVersion, - ), - ) - - lazy val spring = mkSubProject - .dependsOn(hocon % CompileAndTest) - .settings( - jvmCommonSettings, - libraryDependencies ++= Seq( - "org.springframework" % "spring-context" % springVersion, - "com.google.code.findbugs" % "jsr305" % jsr305Version % Optional, - ), - ) - - lazy val jetty = mkSubProject - .dependsOn(core % CompileAndTest) - .settings( - jvmCommonSettings, - libraryDependencies ++= Seq( - "org.eclipse.jetty" % "jetty-client" % jettyVersion, - "org.eclipse.jetty" % "jetty-server" % jettyVersion, - "com.typesafe.scala-logging" %% "scala-logging" % scalaLoggingVersion, - - "org.eclipse.jetty" % "jetty-servlet" % jettyVersion % Test, - ), - ) - - lazy val benchmark = mkSubProject - .dependsOn(redis, mongo) - .enablePlugins(JmhPlugin) - .settings( - jvmCommonSettings, - noPublishSettings, - sourceDirsSettings(_ / "jvm"), - libraryDependencies ++= Seq( - "io.circe" %% "circe-core" % circeVersion, - "io.circe" %% "circe-generic" % circeVersion, - "io.circe" %% "circe-jawn" % circeVersion, - "io.circe" %% "circe-parser" % circeVersion, - "com.lihaoyi" %% "upickle" % upickleVersion, - ), - ideExcludedDirectories := (Jmh / managedSourceDirectories).value, - ) - - lazy val `benchmark-js` = mkSubProject.in(benchmark.base / "js") - .enablePlugins(ScalaJSPlugin, JSDependenciesPlugin) - .configure(p => if (forIdeaImport) p.dependsOn(benchmark) else p) - .dependsOn(`core-js`) - .settings( - jsCommonSettings, - noPublishSettings, - sameNameAs(benchmark), - sourceDirsSettings(_.getParentFile), - libraryDependencies ++= Seq( - "io.circe" %%% "circe-core" % circeVersion, - "io.circe" %%% "circe-generic" % circeVersion, - "io.circe" %%% "circe-parser" % circeVersion, - "com.lihaoyi" %%% "upickle" % upickleVersion, - "com.github.japgolly.scalajs-benchmark" %%% "benchmark" % scalajsBenchmarkVersion, - ), - scalaJSUseMainModuleInitializer := true, - ) - - lazy val comprof = mkSubProject - .disablePlugins(GenerativePlugin) - .dependsOn(core) - .settings( - jvmCommonSettings, - noPublishSettings, - ideSkipProject := true, - addCompilerPlugin("ch.epfl.scala" %% "scalac-profiling" % "1.0.0"), - scalacOptions ++= Seq( - s"-P:scalac-profiling:sourceroot:${baseDirectory.value}", - "-P:scalac-profiling:generate-macro-flamegraph", - "-P:scalac-profiling:no-profiledb", - "-Xmacro-settings:statsEnabled", - "-Ystatistics:typer", +// "io.monix" %% "monix" % monixVersion % Optional, ), - Compile / sourceGenerators += Def.task { - val originalSrc = (core / sourceDirectory).value / - "test/scala/com/avsystem/commons/rest/RestTestApi.scala" - val originalContent = IO.read(originalSrc) - (0 until 100).map { i => - val pkg = f"oa$i%02d" - val newContent = originalContent.replace("package rest", s"package rest\npackage $pkg") - val newFile = (Compile / sourceManaged).value / pkg / "RestTestApi.scala" - IO.write(newFile, newContent) - newFile - } - }.taskValue ) +// +// lazy val `core-js` = mkSubProject.in(core.base / "js") +// .enablePlugins(ScalaJSPlugin) +// .configure(p => if (forIdeaImport) p.dependsOn(core) else p) +// .dependsOn(macros) +// .settings( +// jsCommonSettings, +// sameNameAs(core), +// sourceDirsSettings(_.getParentFile), +// libraryDependencies ++= Seq( +// "io.monix" %%% "monix" % monixVersion % Optional, +// ) +// ) +// +// lazy val mongo = mkSubProject +// .dependsOn(core % CompileAndTest) +// .settings( +// jvmCommonSettings, +// sourceDirsSettings(_ / "jvm"), +// libraryDependencies ++= Seq( +// "com.google.guava" % "guava" % guavaVersion, +// "io.monix" %% "monix" % monixVersion, +// "org.mongodb" % "mongodb-driver-core" % mongoVersion, +// "org.mongodb" % "mongodb-driver-sync" % mongoVersion % Optional, +// "org.mongodb" % "mongodb-driver-reactivestreams" % mongoVersion % Optional, +// "org.mongodb.scala" %% "mongo-scala-driver" % mongoVersion % Optional, +// ), +// ) +// +// // only to allow @mongoId & MongoEntity to be usedJS/JVM cross-compiled code +// lazy val `mongo-js` = mkSubProject.in(mongo.base / "js") +// .enablePlugins(ScalaJSPlugin) +// .configure(p => if (forIdeaImport) p.dependsOn(mongo) else p) +// .dependsOn(`core-js`) +// .settings( +// jsCommonSettings, +// sameNameAs(mongo), +// sourceDirsSettings(_.getParentFile), +// ) +// +// lazy val redis = mkSubProject +// .dependsOn(core % CompileAndTest) +// .settings( +// jvmCommonSettings, +// libraryDependencies ++= Seq( +// "com.google.guava" % "guava" % guavaVersion, +// "org.apache.pekko" %% "pekko-stream" % pekkoVersion, +// "com.typesafe.scala-logging" %% "scala-logging" % scalaLoggingVersion, +// "io.monix" %% "monix" % monixVersion, +// ), +// Test / parallelExecution := false, +// ) +// +// lazy val hocon = mkSubProject +// .dependsOn(core % CompileAndTest) +// .settings( +// jvmCommonSettings, +// libraryDependencies ++= Seq( +// "com.typesafe" % "config" % typesafeConfigVersion, +// ), +// ) +// +// lazy val spring = mkSubProject +// .dependsOn(hocon % CompileAndTest) +// .settings( +// jvmCommonSettings, +// libraryDependencies ++= Seq( +// "org.springframework" % "spring-context" % springVersion, +// "com.google.code.findbugs" % "jsr305" % jsr305Version % Optional, +// ), +// ) +// +// lazy val jetty = mkSubProject +// .dependsOn(core % CompileAndTest) +// .settings( +// jvmCommonSettings, +// libraryDependencies ++= Seq( +// "org.eclipse.jetty" % "jetty-client" % jettyVersion, +// "org.eclipse.jetty" % "jetty-server" % jettyVersion, +// "com.typesafe.scala-logging" %% "scala-logging" % scalaLoggingVersion, +// +// "org.eclipse.jetty" % "jetty-servlet" % jettyVersion % Test, +// ), +// ) +// +// lazy val benchmark = mkSubProject +// .dependsOn(redis, mongo) +// .enablePlugins(JmhPlugin) +// .settings( +// jvmCommonSettings, +// noPublishSettings, +// sourceDirsSettings(_ / "jvm"), +// libraryDependencies ++= Seq( +// "io.circe" %% "circe-core" % circeVersion, +// "io.circe" %% "circe-generic" % circeVersion, +// "io.circe" %% "circe-jawn" % circeVersion, +// "io.circe" %% "circe-parser" % circeVersion, +// "com.lihaoyi" %% "upickle" % upickleVersion, +// ), +// ideExcludedDirectories := (Jmh / managedSourceDirectories).value, +// ) +// +// lazy val `benchmark-js` = mkSubProject.in(benchmark.base / "js") +// .enablePlugins(ScalaJSPlugin, JSDependenciesPlugin) +// .configure(p => if (forIdeaImport) p.dependsOn(benchmark) else p) +// .dependsOn(`core-js`) +// .settings( +// jsCommonSettings, +// noPublishSettings, +// sameNameAs(benchmark), +// sourceDirsSettings(_.getParentFile), +// libraryDependencies ++= Seq( +// "io.circe" %%% "circe-core" % circeVersion, +// "io.circe" %%% "circe-generic" % circeVersion, +// "io.circe" %%% "circe-parser" % circeVersion, +// "com.lihaoyi" %%% "upickle" % upickleVersion, +// "com.github.japgolly.scalajs-benchmark" %%% "benchmark" % scalajsBenchmarkVersion, +// ), +// scalaJSUseMainModuleInitializer := true, +// ) +// +// lazy val comprof = mkSubProject +// .disablePlugins(GenerativePlugin) +// .dependsOn(core) +// .settings( +// jvmCommonSettings, +// noPublishSettings, +// ideSkipProject := true, +// addCompilerPlugin("ch.epfl.scala" %% "scalac-profiling" % "1.0.0"), +// scalacOptions ++= Seq( +// s"-P:scalac-profiling:sourceroot:${baseDirectory.value}", +// "-P:scalac-profiling:generate-macro-flamegraph", +// "-P:scalac-profiling:no-profiledb", +// "-Xmacro-settings:statsEnabled", +// "-Ystatistics:typer", +// ), +// Compile / sourceGenerators += Def.task { +// val originalSrc = (core / sourceDirectory).value / +// "test/scala/com/avsystem/commons/rest/RestTestApi.scala" +// val originalContent = IO.read(originalSrc) +// (0 until 100).map { i => +// val pkg = f"oa$i%02d" +// val newContent = originalContent.replace("package rest", s"package rest\npackage $pkg") +// val newFile = (Compile / sourceManaged).value / pkg / "RestTestApi.scala" +// IO.write(newFile, newContent) +// newFile +// } +// }.taskValue +// ) }