Scalameta
libraryDependencies += "org.scalameta" %% "scalameta" % "4.2.0"
mostly works with source code (parses it to trees, transforms trees) before its compilation i.e. when there can't be any information about symbols and types. To get information about symbols and types one should start compiler (one of).
This is exactly what SemanticDB is for. If you switch on semanticdb-scalac
compiler plugin
addCompilerPlugin("org.scalameta" % "semanticdb-scalac" % "4.1.0" cross CrossVersion.full)
scalacOptions ++= Seq(
"-Yrangepos",
"-P:semanticdb:synthetics:on",
)
then upon compilation it will generate .semanticdb
files near to .class
files. Information about symbols and types will be there. These files can be parsed with semanticdb
libraryDependencies += "org.scalameta" %% "semanticdb" % "4.1.0"
For example if you have App1.scala
object App1 {
var x = 10
}
then
import java.nio.file.Paths
import scala.meta.internal.semanticdb.Locator
Locator(
Paths.get("./target/scala-2.12/classes/META-INF/semanticdb/src/main/scala/App1.scala.semanticdb")
)((path, textDocuments) =>
println(textDocuments)
)
produces
TextDocuments(Vector(TextDocument(SEMANTICDB4,src/main/scala/App1.scala,,29E9BFD566BEFD436FBE59679524E53D,SCALA,Vector(SymbolInformation(_empty_/App1.`x_=`().,SCALA,METHOD,2048,x_=,MethodSignature(Some(Scope(Vector(),Vector())),Vector(Scope(Vector(_empty_/App1.`x_=`().(x$1)),Vector())),TypeRef(Empty,scala/Unit#,Vector())),Vector(),PublicAccess()), SymbolInformation(_empty_/App1.,SCALA,OBJECT,8,App1,ClassSignature(Some(Scope(Vector(),Vector())),Vector(TypeRef(Empty,scala/AnyRef#,Vector())),Empty,Some(Scope(Vector(_empty_/App1.x()., _empty_/App1.`x_=`().),Vector()))),Vector(),PublicAccess()), SymbolInformation(_empty_/App1.`x_=`().(x$1),SCALA,PARAMETER,0,x$1,ValueSignature(TypeRef(Empty,scala/Int#,Vector())),Vector(),Empty), SymbolInformation(_empty_/App1.x().,SCALA,METHOD,2048,x,MethodSignature(Some(Scope(Vector(),Vector())),Vector(),TypeRef(Empty,scala/Int#,Vector())),Vector(),PublicAccess())),Vector(SymbolOccurrence(Some(Range(1,6,1,7)),_empty_/App1.x().,DEFINITION), SymbolOccurrence(Some(Range(0,7,0,11)),_empty_/App1.,DEFINITION)),Vector(),Vector())))
You can pretty-print this file with Metap
import scala.meta.cli.Metap
Metap.main(Array("target/scala-2.12/classes/META-INF/semanticdb/src/main/scala/App1.scala.semanticdb"))
Output
src/main/scala/App1.scala
-------------------------
Summary:
Schema => SemanticDB v4
Uri => src/main/scala/App1.scala
Text => empty
Language => Scala
Symbols => 4 entries
Occurrences => 2 entries
Symbols:
_empty_/App1. => final object App1 extends AnyRef { +2 decls }
_empty_/App1.`x_=`(). => var method x_=(x$1: Int): Unit
_empty_/App1.`x_=`().(x$1) => param x$1: Int
_empty_/App1.x(). => var method x: Int
Occurrences:
[0:7..0:11) <= _empty_/App1.
[1:6..1:7) <= _empty_/App1.x().
And
import scala.meta.internal.semanticdb.TypeRef
import scala.meta.internal.semanticdb.SignatureMessage.SealedValue.{ClassSignature, MethodSignature, TypeSignature, ValueSignature, Empty}
Locator(
Paths.get("./target/scala-2.12/classes/META-INF/semanticdb/src/main/scala/App1.scala.semanticdb")
)((path, textDocuments) =>
for {
document <- textDocuments.documents
symbol <- document.symbols
} println(s"symbol=${symbol.displayName}, ${symbol.signature.asMessage.sealedValue match {
case v: ValueSignature => s"ValueSignature, type=${v.value.tpe match { case t: TypeRef => t.symbol}}"
case m: MethodSignature => s"MethodSignature, returnType=${m.value.returnType match { case t: TypeRef => t.symbol}}"
case c: ClassSignature => "ClassSignature"
case t: TypeSignature => "TypeSignature"
case Empty => "Empty"
}}")
)
produces
symbol=x_=, MethodSignature, returnType=scala/Unit#
symbol=App1, ClassSignature
symbol=x$1, ValueSignature, type=scala/Int#
symbol=x, MethodSignature, returnType=scala/Int#
Scheme is here.
Also you can try Scalafix
sbt new scalacenter/scalafix.g8 --repo="scalafixdemo"
cd scalafix
sbt ~tests/test
If you write in input/src/main/scala/fix/Scalafixdemo.scala
package fix
object Scalafixdemo {
var x = 10
}
and in rules/src/main/scala/fix/Scalafixdemo.scala
package fix
import scalafix.v1._
import scala.meta._
class Scalafixdemo extends SemanticRule("Scalafixdemo") {
override def fix(implicit doc: SemanticDocument): Patch = {
// println("Tree.syntax: " + doc.tree.syntax)
// println("Tree.structure: " + doc.tree.structure)
// println("Tree.structureLabeled: " + doc.tree.structureLabeled)
doc.tree.traverse {
case t@q"..$mods var ..$patsnel: $tpeopt = $expropt" =>
println(t.symbol.info.get.signature)
}
Patch.empty
}
}
then it will print : Int
https://github.com/DmytroMitin/scalafix-codegen