Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade to scalameta 3.7.0 with default settings. #675

Merged
merged 10 commits into from
Apr 9, 2018
40 changes: 28 additions & 12 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import sbt.ScriptedPlugin
import sbt.ScriptedPlugin._
import Dependencies._

Expand Down Expand Up @@ -147,6 +146,7 @@ val cli = MultiScalaProject(
mainClass in assembly := Some("scalafix.cli.Cli"),
assemblyJarName in assembly := "scalafix.jar",
libraryDependencies ++= Seq(
metacp,
"com.github.alexarchambault" %% "case-app" % "1.2.0",
"org.typelevel" %% "paiges-core" % "0.2.0",
"com.martiansoftware" % "nailgun-server" % "0.9.1",
Expand Down Expand Up @@ -207,10 +207,18 @@ val scalafixSbt = MultiSbtProject(
).enablePlugins(BuildInfoPlugin)
.disablePlugins(ScalafixPlugin)
)
lazy val scalafixSbt1 =
scalafixSbt(scala212, sbt1, _.dependsOn(testUtils212 % Test))
lazy val scalafixSbt013 =
scalafixSbt(scala210, sbt013, _.dependsOn(testUtils210 % Test))
lazy val scalafixSbt1 = scalafixSbt(
scala212,
sbt1,
_.dependsOn(testUtils212 % Test)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this formatting change intended?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, for consistency with the changes below

)
lazy val scalafixSbt013 = scalafixSbt(
scala210,
sbt013,
_.settings(
scalacOptions -= warnUnusedImports
).dependsOn(testUtils210 % Test)
)

val testUtils = MultiScalaProject(
"test-utils",
Expand All @@ -221,7 +229,12 @@ val testUtils = MultiScalaProject(
)
)
)
lazy val testUtils210 = testUtils(scala210)
lazy val testUtils210 = testUtils(
scala210,
_.settings(
scalacOptions -= warnUnusedImports
)
)
lazy val testUtils211 = testUtils(scala211)
lazy val testUtils212 = testUtils(scala212)

Expand Down Expand Up @@ -259,10 +272,13 @@ val testsInput = TestProject(
project.settings(
noPublish,
semanticdbSettings,
scalacOptions += {
val sourceroot = baseDirectory.in(ThisBuild).value / srcMain
s"-P:semanticdb:sourceroot:$sourceroot"
},
scalacOptions ++= List(
{
val sourceroot = baseDirectory.in(ThisBuild).value / srcMain
s"-P:semanticdb:sourceroot:$sourceroot"
},
s"-P:semanticdb:denotations:all"
),
scalacOptions ~= (_.filterNot(_ == "-Yno-adapted-args")),
scalacOptions += "-Ywarn-adapted-args", // For NoAutoTupling
scalacOptions += "-Ywarn-unused-import", // For RemoveUnusedImports
Expand Down Expand Up @@ -325,8 +341,8 @@ def unit(
javaOptions := Nil,
buildInfoPackage := "scalafix.tests",
buildInfoObject := "BuildInfo",
sources.in(Test) += (baseDirectory
.in(ThisBuild))
sources.in(Test) += baseDirectory
.in(ThisBuild)
.value / "scalafix-sbt" / "src" / "main" / "scala" / "scalafix" / "internal" / "sbt" / "ScalafixJarFetcher.scala",
libraryDependencies ++= coursierDeps ++ testsDeps
).enablePlugins(BuildInfoPlugin)
Expand Down
3 changes: 2 additions & 1 deletion project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._
/* scalafmt: { maxColumn = 120 }*/

object Dependencies {
val scalametaV = "3.3.1"
val scalametaV = "3.7.0"
val metaconfigV = "0.6.0-RC1"
def dotty = "0.1.1-bin-20170530-f8f52cc-NIGHTLY"
def scala210 = "2.10.6"
Expand All @@ -26,6 +26,7 @@ object Dependencies {
def ammonite = "com.lihaoyi" %% "ammonite-ops" % "0.9.0"
def googleDiff = "com.googlecode.java-diff-utils" % "diffutils" % "1.3.0"

def metacp = "org.scalameta" %% "metacp" % scalametaV
def scalameta = Def.setting("org.scalameta" %%% "contrib" % scalametaV)
val scalatest = "org.scalatest" %% "scalatest" % "3.0.0"

Expand Down
2 changes: 1 addition & 1 deletion project/ScalafixBuild.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ object ScalafixBuild extends AutoPlugin with GhpagesKeys {
)
lazy val warnUnusedImports = "-Ywarn-unused-import"
lazy val compilerOptions = Seq(
"-Xlint",
warnUnusedImports,
"-deprecation",
"-encoding",
"UTF-8",
Expand Down
27 changes: 20 additions & 7 deletions scalafix-cli/src/main/scala/scalafix/cli/CliRunner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import java.util.concurrent.atomic.AtomicReference
import java.util.function.UnaryOperator
import java.util.regex.Pattern
import java.util.regex.PatternSyntaxException

import scala.meta._
import scala.meta.inputs.Input
import scala.meta.internal.inputs._
Expand Down Expand Up @@ -42,6 +41,7 @@ import scalafix.syntax._
import metaconfig.Configured.Ok
import metaconfig._
import metaconfig.ConfError
import scalafix.internal.cli.ClasspathOps

sealed abstract case class CliRunner(
sourceroot: AbsolutePath,
Expand Down Expand Up @@ -89,7 +89,11 @@ sealed abstract case class CliRunner(
val code = exitCode.get()
if (config.lint.reporter.hasErrors) {
ExitStatus.merge(ExitStatus.LinterError, code)
} else code
} else if (config.reporter.hasErrors) {
ExitStatus.merge(ExitStatus.UnexpectedError, code)
} else {
code
}
}
display.completedTask(msg, exit == ExitStatus.Ok)
display.stop()
Expand Down Expand Up @@ -315,8 +319,8 @@ object CliRunner {
.map(path => AbsolutePath(path)(common.workingPath))
.toList

def resolveClasspath: Configured[Classpath] =
classpath match {
def resolveClasspath: Configured[Classpath] = {
classDirectory match {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a blocker but I don't like that this name hints that the classpath is a single directory entry, which is not what I usually see for a classpath.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree this is confusing. Note that the cli still accepts --classpath as an alias for --class-directory. I renamed for consistency with sbt classDirectory to distinguish it from dependencyClasspath.

The public facing API is fully compatible with the previous behavior despite this change so we have wiggle room to improve on this in the future.

case Some(cp) =>
val paths = toClasspath(cp)
Ok(Classpath(paths))
Expand All @@ -336,6 +340,7 @@ object CliRunner {
.notOk
}
}
}

val resolvedSourceroot: Configured[AbsolutePath] = {
val result = sourceroot
Expand All @@ -357,9 +362,17 @@ object CliRunner {
val result: Configured[SemanticdbIndex] = cachedDatabase.getOrElse {
(resolveClasspath |@| resolvedSourceroot).andThen {
case (classpath, root) =>
val patched = Database.load(classpath, Sourcepath(root))
val db =
EagerInMemorySemanticdbIndex(patched, Sourcepath(root), classpath)
val index = Database.load(classpath, Sourcepath(root))
val deps = dependencyClasspath.map(toClasspath).getOrElse(Nil)
val symbolTable = ClasspathOps.newSymbolTable(
Classpath(classpath.shallow ++ deps),
common.out)
val db = EagerInMemorySemanticdbIndex(
index,
Sourcepath(root),
classpath,
symbolTable
)
if (verbose) {
common.err.println(
s"Loaded database with ${db.documents.length} documents.")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,41 @@
package scalafix.internal.cli

import java.io.File
import java.io.PrintStream
import scala.meta.Classpath
import scala.meta.metacp
import scalafix.internal.util.LazySymbolTable
import scalafix.internal.util.SymbolTable

object ClasspathOps {

def bootClasspath: Option[Classpath] = sys.props.collectFirst {
case (k, v) if k.endsWith(".boot.class.path") => Classpath(v)
}

/**
* Process classpath with metacp to build semanticdbs of global symbols.
*
* @param sclasspath Regular classpath to process.
* @param out The output stream to print out error messages.
*/
def toMetaClasspath(sclasspath: Classpath, out: PrintStream): Classpath = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fact that these methods take a printstream looks really awkward.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awkward sure, but what do you propose instead? This is not a public API

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use System.out directly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scalafix runs via nailgun, in IDEs with custom logging, etc,

It's not OK to use System.out

val withJDK = Classpath(
bootClasspath.fold(sclasspath.shallow)(_.shallow ::: sclasspath.shallow))
val settings = metacp
.Settings()
.withClasspath(withJDK)
.withScalaLibrarySynthetics(true)
val reporter = metacp.Reporter().withOut(out)
val mclasspath = scala.meta.cli.Metacp.process(settings, reporter).get
mclasspath
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it okay to crash if metacp returns None?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've refactored the code to propagate the error and report a helpful message on failure.

}

def newSymbolTable(classpath: Classpath, out: PrintStream): SymbolTable = {
val mclasspath = toMetaClasspath(classpath, out)
new LazySymbolTable(mclasspath)
}

def getCurrentClasspath: String = {
Thread.currentThread.getContextClassLoader match {
case url: java.net.URLClassLoader =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,20 @@ case class ScalafixOptions(
sourceroot: Option[String] = None,
@HelpMessage(
"java.io.File.pathSeparator separated list of directories or jars containing " +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a list or a single directory?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It remains a list, unchanged from the previous behavior of --classpath. I've renamed it back to --classpath and added more details about --dependency-classpath.

"'.semanticdb' files. The 'semanticdb' files are emitted by the " +
"'.semanticdb' files. SemanticDB files are emitted by the " +
"semanticdb-scalac compiler plugin and are necessary for semantic rules " +
"like ExplicitResultTypes to function."
)
@ValueDescription("entry1.jar:entry2.jar:target/scala-2.12/classes/")
classpath: Option[String] = None,
@ExtraName("classpath")
classDirectory: Option[String] = None,
@HelpMessage(
"java.io.File.pathSeparator separated list of directories or jars for dependencies. " +
"Does not need to contain SemanticDB files. " +
"Required for semantic rules like ExplicitResultTypes to work."
)
@ValueDescription("entry1.jar:entry2.jar:target/scala-2.12/classes/")
dependencyClasspath: Option[String] = None,
@HelpMessage(
"Automatically infer --classpath starting from these directories. " +
"Ignored if --classpath is provided.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ package scala.meta.internal.scalafix
import scala.meta.Dialect
import scala.meta.Tree
import scala.meta.internal.trees.Origin
import scala.meta.dialects

object ScalafixScalametaHacks {
def dialect(language: String): Dialect = Dialect.standards(language)
def dialect(language: String): Dialect =
if (language == "Scala") dialects.Scala212
else if (language.isEmpty) dialects.Scala212
else Dialect.standards(language)
def resetOrigin(tree: Tree): Tree = tree.withOrigin(Origin.None)
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import scalafix.syntax._
import scalafix.util.TokenOps
import metaconfig.Conf
import metaconfig.Configured
import org.langmeta.internal.semanticdb.XtensionDenotationsInternal

case class ExplicitResultTypes(
index: SemanticdbIndex,
Expand Down Expand Up @@ -74,9 +75,16 @@ case class ExplicitResultTypes(
def defnType(defn: Defn): Option[(Type, Patch)] =
for {
name <- defnName(defn)
symbol <- name.symbol
typ <- symbol.resultType
} yield TypeSyntax.prettify(typ, ctx, config.unsafeShortenNames)
denot <- name.denotation
tpe <- denot.tpeInternal
// Skip existential types for now since they cause problems.
if !tpe.tag.isExistentialType
result <- TypeSyntax.prettify(
tpe,
ctx,
config.unsafeShortenNames,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this config passed? https://scalacenter.github.io/scalafix/docs/users/configuration shows how to add rules to the scalafix config, but does not show if it is possible to pass rule specific config.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's configuration for ExplicitResultTypes documented here https://scalacenter.github.io/scalafix/docs/rules/ExplicitResultTypes

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn’t able to put that together from the docs for some reason but I get it now, thanks.

name.pos)
} yield result
import scala.meta._
def fix(defn: Defn, body: Term): Patch = {
val lst = ctx.tokenList
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package scalafix.internal.util

import org.langmeta.semanticdb.Symbol
import scala.meta.Denotation
import scalafix.SemanticdbIndex
import org.langmeta.internal.semanticdb._
import scala.meta.internal.{semanticdb3 => s}

/**
* Combination of a SymbolTable and SemanticdbIndex.
*
* This utility is necessary because SymbolTable contains only global symbols and
* SemanticdbIndex has local symbols.
*/
class CombinedSymbolTable(index: SemanticdbIndex, table: SymbolTable) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to pass both index and table here, given that EagerInMemorySemanticdbIndex already takes a symbol table?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I've merged this into EagerSemanticdbIndex avoiding the need for CombinedSymbolTable.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is combined symbol table needed if shortenNames is false?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's needed both when shortenNames is true and false.

private def denotationToSymbolInformation(
symbol: String,
denot: Denotation): s.SymbolInformation = {
s.SymbolInformation(
symbol = symbol,
language = s.Language.SCALA,
kind = s.SymbolInformation.Kind.fromValue(denot.skind.value),
properties = denot.sproperties,
name = denot.name,
tpe = denot.tpeInternal
)
}
def info(symbol: String): s.SymbolInformation =
table.info(symbol).getOrElse {
denotationToSymbolInformation(
symbol,
index
.denotation(Symbol(symbol))
.getOrElse(throw new NoSuchElementException(symbol))
)
}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,40 @@
package scalafix
package internal.util

import scala.collection.mutable
import scala.meta._
import scala.{meta => m}

case class EagerInMemorySemanticdbIndex(
database: Database,
sourcepath: Sourcepath,
classpath: Classpath)
extends SemanticdbIndex {
classpath: Classpath,
table: SymbolTable = SymbolTable.empty
) extends SemanticdbIndex {
override def toString: String =
s"$productPrefix($sourcepath, $classpath, database.size=${database.documents.length})"
override def hashCode(): Int = database.hashCode()
private lazy val _denots: Map[Symbol, Denotation] = {
val builder = Map.newBuilder[Symbol, Denotation]
private lazy val _denots: mutable.Map[Symbol, Denotation] = {
val builder = mutable.Map.empty[Symbol, Denotation]
database.symbols.foreach(r => builder += (r.symbol -> r.denotation))
builder.result()
}
private lazy val _names: Map[Position, ResolvedName] = {
val builder = Map.newBuilder[Position, ResolvedName]
def add(r: ResolvedName) = {
builder += (r.position -> r)
private lazy val _names: mutable.Map[Position, ResolvedName] = {
val builder = mutable.Map.empty[Position, ResolvedName]
def add(r: ResolvedName): Unit = {
builder.get(r.position) match {
case Some(conflict) =>
conflict.symbol match {
case m.Symbol.Multi(syms) =>
builder(r.position) =
conflict.copy(symbol = m.Symbol.Multi(r.symbol :: syms))
case sym =>
builder(r.position) =
conflict.copy(symbol = m.Symbol.Multi(r.symbol :: sym :: Nil))
}
case _ =>
builder(r.position) = r
}
}
database.documents.foreach { entry =>
entry.names.foreach(add)
Expand Down
Loading