寻求Scalameta Semantic API使用教程及具体示例指引
I totally get your frustration—Scalameta's Semantic API docs can feel scattered, especially when the FAQ points to Scalafix without clear step-by-step examples. Let's break this down with concrete code to show you how to resolve names like println to their fully qualified paths (_root_.scala.Predef.println).
Step 1: Set Up Your Project First
To use the Semantic API, you need to enable the SemanticDB compiler plugin and add the required dependencies. Here's how to configure an sbt project:
// project/plugins.sbt addSbtPlugin("org.scalameta" % "sbt-scalafix" % "0.11.1") addSbtPlugin("org.scalameta" % "sbt-semanticdb" % "4.9.6") // build.sbt lazy val root = project.in(file(".")) .settings( scalaVersion := "2.13.12", // SemanticDB supports 2.12/2.13/3.x semanticdbEnabled := true, semanticdbVersion := scalafixSemanticdb.revision, scalacOptions ++= Seq( "-Yrangepos", // Required for SemanticDB to track position info "-Xsemanticdb" ) )
Step 2: Basic Name Resolution Example
Here's a standalone example that reads a Scala source file, uses the Semantic API to resolve the fully qualified name of println:
import scala.meta._ import scala.meta.io._ import scala.meta.semanticdb._ import scala.meta.internal.semanticdb.Scala3NameOps // For Scala 3; use Scala2NameOps for Scala 2 object SemanticNameResolver { def main(args: Array[String]): Unit = { // Load your source file val sourceFile = FileIO.fromPath(java.nio.file.Paths.get("src/main/scala/Test.scala")) val source = sourceFile.parse[Source].get // Access the semantic database (generated during compilation) val classpath = Classpath.fromJavaClasspath val database = SemanticDatabase.load(classpath) // Find all identifiers in the source source.collect { case ident @ Ident(name) if name == "println" => // Get the symbol for the identifier val symbol = ident.symbol.get // Resolve the fully qualified name val fullyQualifiedName = symbol.displayName println(s"Resolved '$name' to: $fullyQualifiedName") // For more detailed info, you can get the symbol's owner, kind, etc. println(s"Symbol kind: ${symbol.kind}") } } }
And the corresponding Test.scala file:
object Test { def main(args: Array[String]): Unit = { println("Hello") // This is the println we'll resolve } }
Step 3: Using Semantic API in a Scalafix Rule
If you're working with Scalafix (as the FAQ suggests), here's a simple rule that resolves names and reports their fully qualified paths:
import scalafix.v1._ import scala.meta._ class ResolveNamesRule extends SemanticRule("ResolveNamesRule") { override def fix(implicit doc: SemanticDocument): Patch = { doc.tree.collect { case ident @ Ident(name) if name == "println" => val symbol = ident.symbol.get val fqn = symbol.displayName // Print the resolved name to the console println(s"Resolved '$name' to $fqn") // Or add a comment with the resolved name Patch.addRight(ident, s" // Resolved to: $fqn") }.asPatch } }
To run this rule, add it to your scalafix configuration and execute sbt scalafix.
Key Notes to Remember
- SemanticDB Generation: The Semantic API relies on the compiler plugin generating a semantic database during compilation—you must compile your project first before running any code that uses the API.
- Scala Version Differences: The API has slight variations between Scala 2 and Scala 3 (like the
Scala2NameOpsvsScala3NameOpsimports), so adjust based on your version. - Symbol Information: Symbols carry more than just names—you can access their kind (method, class, etc.), owner, signature, and more to build more complex tools.
For deeper dives, check the official Scalameta test suites (they have plenty of semantic API usage examples) and Scalafix's built-in rules (many use the semantic API for refactoring tasks).
内容的提问来源于stack exchange,提问作者Sledge

