-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
draft: migrate analyzer to Scala 3.5.0
- Loading branch information
1 parent
80a2d81
commit d8c89bd
Showing
35 changed files
with
1,032 additions
and
953 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
135 changes: 68 additions & 67 deletions
135
analyzer/src/main/scala/com/avsystem/commons/analyzer/AnalyzerPlugin.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,84 +1,85 @@ | ||
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.ast.tpd | ||
import dotty.tools.dotc.core.Contexts.Context | ||
import dotty.tools.dotc.plugins.{PluginPhase, StandardPlugin} | ||
import dotty.tools.dotc.reporting.Diagnostic | ||
import dotty.tools.dotc.transform.{Pickler, Staging} | ||
import dotty.tools.dotc.util.NoSourcePosition | ||
|
||
final class AnalyzerPlugin(val global: Global) extends Plugin { plugin => | ||
final class AnalyzerPlugin extends StandardPlugin: | ||
plugin => | ||
|
||
override def init(options: List[String], error: String => Unit): Boolean = { | ||
private lazy val rules = List( | ||
// new ImportJavaUtil(global), | ||
// new VarargsAtLeast | ||
// new CheckMacroPrivate(global), | ||
// new ExplicitGenerics(global), | ||
// new ValueEnumExhaustiveMatch(global), | ||
// new ShowAst(global), | ||
// new FindUsages(global), | ||
// new CheckBincompat(global), | ||
new ImportJavaUtil | ||
// new ThrowableObjects(global), | ||
// new DiscardedMonixTask(global), | ||
// new BadSingletonComponent(global), | ||
// new ConstantDeclarations(global), | ||
// new BasePackage(global) | ||
) | ||
|
||
override val description = "AVSystem custom Scala static analyzer" | ||
val name = "AVSystemAnalyzer" | ||
|
||
override def initialize(options: List[String])(using ctx: Context): List[PluginPhase] = | ||
parseOptions(options) | ||
rules | ||
|
||
end initialize | ||
|
||
private def parseOptions(options: List[String])(using ctx: 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 ctx: 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 |
106 changes: 61 additions & 45 deletions
106
analyzer/src/main/scala/com/avsystem/commons/analyzer/AnalyzerRule.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,56 +1,72 @@ | ||
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 global._ | ||
abstract class AnalyzerRule(val name: String, defaultLevel: Level = Level.Warn) extends PluginPhase: | ||
import tpd.* | ||
|
||
// final def analyze(tree: Tree)(using Context): Tree = | ||
// try analyzeTree.applyOrElse(tree, (_: Tree) => ()) | ||
// catch | ||
// case NonFatal(t) => | ||
// val sw = new StringWriter | ||
// t.printStackTrace(new PrintWriter(sw)) | ||
// report(s"Analyzer rule $this failed: " + sw.toString)(using tree.sourcePos) | ||
// | ||
// end try | ||
// tree | ||
// | ||
// end analyze | ||
override val runsAfter: Set[String] = Set(Pickler.name) | ||
override val runsBefore: Set[String] = Set(Staging.name) | ||
override val phaseName = s"avsAnalyze$name" | ||
var level: Level = defaultLevel | ||
var argument: String = _ | ||
var argument: String = uninitialized | ||
override def toString: String = getClass.getSimpleName | ||
|
||
protected def classType(fullName: String): Type = | ||
try rootMirror.staticClass(fullName).asType.toType.erasure catch { | ||
case _: ScalaReflectionException => NoType | ||
} | ||
protected final def report( | ||
message: String, | ||
site: Symbol = NoSymbol | ||
)(using | ||
position: SourcePosition, | ||
context: Context | ||
): Unit = context.reporter.report { | ||
level match | ||
case Level.Off => | ||
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) | ||
} | ||
|
||
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) | ||
} | ||
private def adjustMsg(msg: String): Message = s"[AVS] $msg".toMessage | ||
|
||
private def adjustMsg(msg: String): String = s"[AVS] $msg" | ||
end AnalyzerRule | ||
|
||
protected final def report( | ||
pos: Position, | ||
message: String, | ||
category: WarningCategory = WarningCategory.Lint, | ||
site: Symbol = NoSymbol | ||
): Unit = | ||
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 | ||
} | ||
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 |
44 changes: 24 additions & 20 deletions
44
analyzer/src/main/scala/com/avsystem/commons/analyzer/Any2StringAdd.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Oops, something went wrong.