Skip to content

Commit

Permalink
draft: migrate analyzer to Scala 3.5.0
Browse files Browse the repository at this point in the history
  • Loading branch information
halotukozak committed Sep 29, 2024
1 parent 80a2d81 commit d8c89bd
Show file tree
Hide file tree
Showing 35 changed files with 1,032 additions and 953 deletions.
19 changes: 1 addition & 18 deletions .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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
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
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
Loading

0 comments on commit d8c89bd

Please sign in to comment.