Scalafix

Scalafix

  • User guide
  • Developer guide
  • Browse sources
  • GitHub

›API Reference

Implementing rules

  • Setup
  • Before you write code
  • Tutorial

API Reference

  • Overview
  • Patch
  • SymbolMatcher
  • SymbolInformation
  • SemanticType
  • SemanticTree

Contributing

  • Guide
Edit

SymbolMatcher

Symbol matcher is a trait that is defined by a single method matches(Symbol): Boolean.

trait SymbolMatcher {
  def matches(symbol: Symbol): Boolean
}

On top of matches(), symbol matchers provide additional methods to extract interesting tree nodes with pattern matching.

All code examples in this document assume you have the following imports in scope.

import scalafix.v1._
import scala.meta._

SemanticDB

Scalafix symbols are based on SemanticDB symbols. To learn more about SemanticDB symbols, consult the SemanticDB specification:

  • General symbols
  • Scala symbols
  • Java symbols

SymbolMatcher reference

There are two kinds of symbol matchers: exact and normalized. To illustrate the differences between exact and normalized symbol matchers, we use the following input source file as a running example.

// Main.scala
package com
class Main() {}            // com/Main# class
object Main extends App {  // com/Main. object
  println()                // println()   overload with no parameter
  println("Hello Predef!") // println(+1) overload with string parameter
}

Observe that the name

  • Main can refer to both the class or the companion object.
  • println can refer to different method overloads.

exact()

Exact symbol matchers allow precise comparison between symbols with the ability to distinguish the differences between method overloads and a class from its companion object.

To match only the Main class in the example above.

val mainClass  = SymbolMatcher.exact("com/Main#")
// mainClass: SymbolMatcher = ExactSymbolMatcher(com/Main#)
mainClass.matches(Symbol("com/Main#"))
// res0: Boolean = true
mainClass.matches(Symbol("com/Main."))
// res1: Boolean = false

To match only the println(String) method overload and not the println() method.

val printlnString  = SymbolMatcher.exact("scala/Predef.println(+1).")
// printlnString: SymbolMatcher = ExactSymbolMatcher(scala/Predef.println(+1).)
printlnString.matches(Symbol("scala/Predef.println()."))
// res2: Boolean = false
printlnString.matches(Symbol("scala/Predef.println(+1)."))
// res3: Boolean = true

normalized()

Normalized symbol matchers ignore the differences between overloaded methods and a class from its companion object. The benefit of normalized symbols is that they use a simple symbol syntax: fully qualified names with a dot . separator. For example,

  • java/lang/String# is equal to java.lang.String normalized
  • scala/Predef.println(+1). is equal to scala.Predef.println normalized

To match against either the Main class or companion object

val main = SymbolMatcher.normalized("com.Main")
// main: SymbolMatcher = NormalizedSymbolMatcher(com.Main.)
main.matches(Symbol("com/Main#")) // Main class
// res4: Boolean = true // Main class
main.matches(Symbol("com/Main.")) // Main object
// res5: Boolean = true

To match against all overloaded println methods

val print = SymbolMatcher.normalized("scala.Predef.println")
// print: SymbolMatcher = NormalizedSymbolMatcher(scala.Predef.println.)
print.matches(Symbol("scala/Predef#println()."))   // println()
// res6: Boolean = true   // println()
print.matches(Symbol("scala/Predef#println(+1).")) // println(String)
// res7: Boolean = true

unapply(Tree)

Use the unapply(Tree) method to pattern match against tree nodes that resolve to a specific symbol. Consider the following input file.

// Main.scala
object Main {
  util.Success(1)
}

To match against the Scala util package symbol

val utilPackage = SymbolMatcher.exact("scala/util/")
// utilPackage: SymbolMatcher = ExactSymbolMatcher(scala/util/)
doc.tree.traverse {
  case utilPackage(name) =>
    println(name.pos.formatMessage("info", "Here is the util package"))
}
// Main.scala:3:3: info: Here is the util package
//   util.Success(1)
//   ^^^^

A common mistake when using unapply(Tree) is to match multiple tree nodes that resolve to the same symbol.

val successObject = SymbolMatcher.exact("scala/util/Success.")
// successObject: SymbolMatcher = ExactSymbolMatcher(scala/util/Success.)
doc.tree.traverse {
  case successObject(name) =>
    println(name.pos.formatMessage("info",
      "Matched Success for tree node " + name.productPrefix))
}
// Main.scala:3:3: info: Matched Success for tree node Term.Apply
//   util.Success(1)
//   ^^^^^^^^^^^^^^^
// Main.scala:3:3: info: Matched Success for tree node Term.Select
//   util.Success(1)
//   ^^^^^^^^^^^^
// Main.scala:3:8: info: Matched Success for tree node Term.Name
//   util.Success(1)
//        ^^^^^^^

To ensure we match the symbol only once, refine the pattern match to Name.

doc.tree.traverse {
  case successObject(name @ Name(_)) =>
    println(name.pos.formatMessage("info", "Here is the Success name"))
}
// Main.scala:3:8: info: Here is the Success name
//   util.Success(1)
//        ^^^^^^^

Alternatively, enclose the symbol matcher guard inside a more complicated pattern.

doc.tree.traverse {
  case function @ Term.Apply(successObject(_), List(argument)) =>
    println(function.pos.formatMessage("info",
      s"Argument of Success is $argument"))
}
// Main.scala:3:3: info: Argument of Success is 1
//   util.Success(1)
//   ^^^^^^^^^^^^^^^

+(SymbolMatcher)

Use the +(SymbolMatcher) method to combine two symbol matchers.

val printOrMain = main + print

The combined matcher returns true when either the main or print matchers return true.

printOrMain.matches(Symbol("com/Main#"))
// res13: Boolean = true
printOrMain.matches(Symbol("scala/Predef.println()."))
// res14: Boolean = true
← PreviousNext →
  • SemanticDB
  • SymbolMatcher reference
    • exact()
    • normalized()
    • unapply(Tree)
    • +(SymbolMatcher)
Scalafix
Docs
Get startedRulesExtend Scalafix
Community
Chat on GitterDiscuss on Scala Users
More
GitHub
Copyright © 2018 Scala Center