Skip to content

Commit

Permalink
Add rudimentary ExplicitImplicit semantic rewrite.
Browse files Browse the repository at this point in the history
- Use (super experimental) 2.0 version of scalameta
- Use (super experimental) 4.0 version of scala.meta paradise
- SBT plugin injects the 4.0 paradise compiler plugin and compiles with -Dpersist.enable
- ExplicitImplicit inserts the fully qualified type name, no fancy imports yet
- Everything is tested, including the SBT plugin interaction!
- Fixed tiny bug in VolatileLazyVal because I couldn't help myself
  • Loading branch information
olafurpg committed Oct 13, 2016
1 parent 2b0d358 commit 9f4626f
Show file tree
Hide file tree
Showing 28 changed files with 375 additions and 57 deletions.
3 changes: 0 additions & 3 deletions .scalafmt

This file was deleted.

6 changes: 6 additions & 0 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
assumeStandardLibraryStripMargin = true
align.tokens = [
{ code = "%", owner = "Infix" }
{ code = "%%", owner = "Infix" }
{ code = "%%%", owner = "Infix" }
]
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# scalafix [![codecov.io](https://codecov.io/github/scalacenter/scalafix/coverage.svg?branch=master)](https://codecov.io/github/scalacenter/scalafix?branch=master) [![Build Status](http://stats.lassie.io:8001/api/badges/scalacenter/scalafix/status.svg)](http://stats.lassie.io:8001/scalacenter/scalafix) [![Join the chat at https://gitter.im/scalacenter/scalafix](https://badges.gitter.im/scalacenter/scalafix.svg)](https://gitter.im/scalacenter/scalafix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
# scalafix [![Build Status](http://stats.lassie.io:8001/api/badges/scalacenter/scalafix/status.svg)](http://stats.lassie.io:8001/scalacenter/scalafix) [![Join the chat at https://gitter.im/scalacenter/scalafix](https://badges.gitter.im/scalacenter/scalafix.svg)](https://gitter.im/scalacenter/scalafix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

The [first meeting of the Scala Center advisory board](http://scala-lang.org/blog/2016/05/30/scala-center-advisory-board.html#the-first-meeting)
approved a proposal to define a migration path from Scala 2.x to Dotty.
Expand Down
1 change: 1 addition & 0 deletions bin/nailgun_integration_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
set -e

echo "Running nailgun integration test..."
ng ng-stop || true

cwd=$(pwd)
cd cli/target/pack
Expand Down
7 changes: 5 additions & 2 deletions bin/testAll.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
#!/bin/bash
set -e

sbt clean coverage test

sbt -Dpersist.enable clean semanticCompile/compile
#sbt coverage test # coverage seems to break the semantic tests :/
sbt test
sbt "; publishLocal ; scripted ; cli/pack"
sbt coverageAggregate
#sbt coverageAggregate

# Integration tests
./bin/nailgun_integration_test.sh
Expand Down
16 changes: 13 additions & 3 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import sbt.ScriptedPlugin._
import scoverage.ScoverageSbtPlugin.ScoverageKeys._

scalafmtConfig in ThisBuild := Some(file(".scalafmt"))
val Versions = _root_.scalafix.Versions

lazy val buildSettings = Seq(
organization := "ch.epfl.scala",
assemblyJarName in assembly := "scalafix.jar",
// See core/src/main/scala/ch/epfl/scala/Versions.scala
version := scalafix.Versions.nightly,
scalaVersion := scalafix.Versions.scala,
version := Versions.nightly,
scalaVersion := Versions.scala,
updateOptions := updateOptions.value.withCachedResolution(true)
)

Expand Down Expand Up @@ -97,6 +98,7 @@ lazy val root = project
core,
cli,
readme,
semanticCompile,
sbtScalafix
)
.dependsOn(core)
Expand All @@ -105,11 +107,12 @@ lazy val core = project
.settings(allSettings)
.settings(
moduleName := "scalafix-core",
fork := true,
addCompilerPlugin(
"org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full),
libraryDependencies ++= Seq(
"com.lihaoyi" %% "sourcecode" % "0.1.2",
"org.scalameta" %% "scalameta" % "1.0.0",
"com.geirsson" %% "scalameta" % "2.0.0-M7",
"org.scala-lang" % "scala-reflect" % scalaVersion.value,
// Test dependencies
"org.scalatest" %% "scalatest" % "3.0.0" % "test",
Expand Down Expand Up @@ -159,6 +162,13 @@ lazy val sbtScalafix = project
scriptedBufferLog := false
)

lazy val semanticCompile = project
.settings(
allSettings,
addCompilerPlugin(Versions.paradiseOrg % "paradise_2.11.8" % Versions.paradiseVersion),
scalacOptions += "-Ybackend:GenBCode"
)

lazy val readme = scalatex
.ScalatexReadme(projectId = "readme",
wd = file(""),
Expand Down
60 changes: 37 additions & 23 deletions cli/src/main/scala/scalafix/cli/Cli.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package scalafix.cli
import scala.collection.GenSeq
import scalafix.Failure
import scalafix.Scalafix
import scalafix.ScalafixConfig
import scalafix.rewrite.Rewrite
import scalafix.util.FileOps

Expand All @@ -14,6 +13,8 @@ import java.io.PrintStream
import java.util.concurrent.atomic.AtomicInteger
import ArgParserImplicits._
import scala.util.control.NonFatal
import scalafix.config.ProjectFiles
import scalafix.config.ScalafixConfig

import caseapp._
import caseapp.core.Messages
Expand All @@ -35,7 +36,10 @@ case class ScalafixOptions(
) rewrites: List[Rewrite] = Rewrite.default.toList,
@Hidden @HelpMessage(
"Files to fix. Runs on all *.scala files if given a directory."
) @ExtraName("f") files: List[String] = List.empty[String],
) @ExtraName("f") files: List[String] = Nil,
@HelpMessage(
"Target directory with .class files that contain TASTY fields"
) @ExtraName("f") target: List[String] = Nil,
@HelpMessage(
"If true, writes changes to files instead of printing to stdout."
) @ExtraName("i") inPlace: Boolean = false,
Expand All @@ -47,7 +51,12 @@ case class ScalafixOptions(
) debug: Boolean = false,
@Recurse common: CommonOptions = CommonOptions()
) extends App {

def toConfig = ScalafixConfig(
rewrites = rewrites,
project = ProjectFiles(
targetFiles = target.map(new File(_))
)
)
Cli.runOn(this)
}

Expand All @@ -56,43 +65,48 @@ object Cli extends AppOf[ScalafixOptions] {

val default = ScalafixOptions()

def safeHandleFile(file: File, config: ScalafixOptions): Unit = {
try handleFile(file, config) catch {
def safeHandleFile(file: File,
options: ScalafixOptions,
config: ScalafixConfig): Unit = {
try handleFile(file, options, config)
catch {
case NonFatal(e) =>
config.common.err.println(
s"""Unexpected error fixing file: $file
|Cause: $e""".stripMargin)
options.common.err.println(s"""Unexpected error fixing file: $file
|Cause: $e""".stripMargin)
}
}
def handleFile(file: File, config: ScalafixOptions): Unit = {
Scalafix
.fix(FileOps.readFile(file), ScalafixConfig(config.rewrites)) match {
def handleFile(file: File,
options: ScalafixOptions,
config: ScalafixConfig): Unit = {
val fixed = Scalafix.fix(FileOps.readFile(file), options.toConfig)
fixed match {
case Right(code) =>
if (config.inPlace) {
if (options.inPlace) {
FileOps.writeFile(file, code)
} else config.common.out.write(code.getBytes)
} else options.common.out.write(code.getBytes)
case Left(e: Failure.ParseError) =>
if (config.files.contains(file.getPath)) {
if (options.files.contains(file.getPath)) {
// Only log if user explicitly specified that file.
config.common.err.write(e.toString.getBytes())
options.common.err.write(e.toString.getBytes())
}
case Left(e) =>
config.common.err.write(s"Failed to fix $file. Cause: $e".getBytes)
options.common.err.write(s"Failed to fix $file. Cause: $e".getBytes)
}
}

def runOn(config: ScalafixOptions): Unit = {
config.files.foreach { pathStr =>
def runOn(options: ScalafixOptions): Unit = {
val config = options.toConfig
options.files.foreach { pathStr =>
val path = new File(pathStr)
val workingDirectory = new File(config.common.workingDirectory)
val workingDirectory = new File(options.common.workingDirectory)
val realPath: File =
if (path.isAbsolute) path
else new File(config.common.workingDirectory, path.getPath)
else new File(options.common.workingDirectory, path.getPath)
if (realPath.isDirectory) {
val filesToFix: GenSeq[String] = {
val files =
FileOps.listFiles(realPath).filter(x => x.endsWith(".scala"))
if (config.parallel) files.par
if (options.parallel) files.par
else files
}
val logger = new TermDisplay(new OutputStreamWriter(System.out))
Expand All @@ -102,13 +116,13 @@ object Cli extends AppOf[ScalafixOptions] {
logger.taskLength(msg, filesToFix.length, 0)
val counter = new AtomicInteger()
filesToFix.foreach { x =>
safeHandleFile(new File(x), config)
safeHandleFile(new File(x), options, config)
val progress = counter.incrementAndGet()
logger.taskProgress(msg, progress)
}
logger.stop()
} else {
safeHandleFile(realPath, config)
safeHandleFile(realPath, options, config)
}
}
}
Expand Down
11 changes: 11 additions & 0 deletions core/src/main/scala/scala/meta/Mirror.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package scala.meta

object Mirror {
def apply(artifacts: Artifact*)(implicit resolver: Resolver): Mirror = {
new Mirror {
lazy val domain = Domain(artifacts: _*)(resolver)
override def toString =
s"""Context(${artifacts.mkString(", ")})($resolver)"""
}
}
}
9 changes: 8 additions & 1 deletion core/src/main/scala/scalafix/Scalafix.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package scalafix

import scala.meta._
import scala.meta.inputs.Input
import scalafix.config.ScalafixConfig
import scalafix.rewrite.RewriteCtx
import scalafix.util.Patch
import scalafix.util.TokenList
Expand All @@ -14,7 +15,13 @@ object Scalafix {
def fix(code: Input, config: ScalafixConfig): Fixed = {
config.parser.apply(code, config.dialect) match {
case Parsed.Success(ast) =>
val ctx = RewriteCtx(config, new TokenList(ast.tokens))
val m = Mirror(
config.project.targetFiles.map(x => Artifact(x.getAbsolutePath)): _*)
val ctx = RewriteCtx(
config,
new TokenList(ast.tokens),
Some(m)
)
val patches: Seq[Patch] = config.rewrites.flatMap(_.rewrite(ast, ctx))
Right(Patch.apply(ast.tokens, patches))
case Parsed.Error(pos, msg, e) =>
Expand Down
4 changes: 3 additions & 1 deletion core/src/main/scala/scalafix/Versions.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package scalafix

object Versions {
val nightly = "0.1.0"
val nightly = "0.1.0-SNAPSHOT"
val stable = nightly
val scala = "2.11.8"
val paradiseVersion = "4.0.0-M3"
val paradiseOrg = "com.geirsson"
}
8 changes: 8 additions & 0 deletions core/src/main/scala/scalafix/config/ProjectFiles.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package scalafix.config

import java.io.File

case class ProjectFiles(
targetFiles: Seq[File] = Nil,
files: Seq[File] = Nil
)
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package scalafix
package scalafix.config

import scala.meta.Dialect
import scala.meta.Tree
import scala.meta.dialects.Scala211
import scala.meta.parsers.Parse
import scalafix.rewrite.Rewrite

import java.io.File

case class ScalafixConfig(
rewrites: Seq[Rewrite] = Rewrite.default,
parser: Parse[_ <: Tree] = Parse.parseSource,
dialect: Dialect = Scala211
dialect: Dialect = Scala211,
project: ProjectFiles = ProjectFiles()
)
29 changes: 29 additions & 0 deletions core/src/main/scala/scalafix/rewrite/ExplicitImplicit.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package scalafix.rewrite

import scala.meta._
import scala.meta.internal.prettyprinters.Attributes
import scala.meta.tokens.Token.Comment
import scala.meta.tokens.Token.LeftBrace
import scala.meta.tokens.Token.RightBrace
import scala.meta.tokens.Token.RightParen
import scala.meta.tokens.Token.Space
import scalafix.util.Patch
import scalafix.util.SemanticOracle
import scalafix.util.logger

case object ExplicitImplicit extends Rewrite {

override def rewrite(ast: Tree, ctx: RewriteCtx): Seq[Patch] = {
val builder = Seq.newBuilder[Patch]
val oracle = new SemanticOracle(ctx.mirror.get)
ast.collect {
case Defn.Val(mods, Seq(Pat.Var.Term(t: Term.Name)), decltpe, _)
if decltpe.isEmpty && mods.exists(_.syntax == "implicit") =>
oracle.getType(t).foreach { typ =>
val toks = t.tokens
builder += Patch(toks.head, toks.last, s"${t.syntax}: $typ")
}
}
builder.result()
}
}
3 changes: 1 addition & 2 deletions core/src/main/scala/scalafix/rewrite/Rewrite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import scala.meta._
import scalafix.util.Patch

abstract class Rewrite {

def rewrite(code: Tree, rewriteCtx: RewriteCtx): Seq[Patch]

}

object Rewrite {
Expand All @@ -15,6 +13,7 @@ object Rewrite {
}

val name2rewrite: Map[String, Rewrite] = nameMap[Rewrite](
ExplicitImplicit,
ProcedureSyntax,
VolatileLazyVal
)
Expand Down
13 changes: 11 additions & 2 deletions core/src/main/scala/scalafix/rewrite/RewriteCtx.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
package scalafix.rewrite

import scalafix.ScalafixConfig
import scala.meta.Tree
import scala.meta.inputs.Input
import scala.meta.semantic.Mirror
import scalafix.config.ScalafixConfig
import scalafix.util.TokenList

case class RewriteInput(
input: Input,
ast: Tree
)

case class RewriteCtx(
config: ScalafixConfig,
tokenList: TokenList
tokenList: TokenList,
mirror: Option[Mirror]
)
4 changes: 2 additions & 2 deletions core/src/main/scala/scalafix/rewrite/VolatileLazyVal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ case object VolatileLazyVal extends Rewrite {
case x if x.syntax == "@volatile" =>
None
case x if x.syntax == "lazy" =>
Some(x.tokens.head)
Some(defn.mods.head.tokens.head)
}
}.flatten
}
override def rewrite(ast: Tree, ctx: RewriteCtx): Seq[Patch] = {
ast.collect {
case NonVolatileLazyVal(tok) => Patch(tok, tok, "@volatile lazy")
case NonVolatileLazyVal(tok) => Patch(tok, tok, s"@volatile ${tok.syntax}")
}
}
}
Loading

0 comments on commit 9f4626f

Please sign in to comment.