Skip to content

Commit c7d1846

Browse files
gabroolafurpg
authored andcommitted
Add NoExtendsApp rewrite (#188)
* Add NoExtendsApp rewrite * Assume indentation of two spaces This seems pretty much universal in Scala projects. * Make removeTokensBetween private * Use Symbol instead of String in removeParentFromTemplate * Use shorter syntax for rewrites configuration * Run scalafmt * Handle single-line blocks as body * Add explicit return type to main method * Remove redundant info from the docs. * Warn in case of empty or missing body.
1 parent 8641796 commit c7d1846

File tree

5 files changed

+242
-1
lines changed

5 files changed

+242
-1
lines changed

readme/Rewrites.scalatex

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,5 +197,26 @@
197197
@p
198198
The two syntaxes are equivalent and the presence of the @code{val} keyword has been deprecated since Scala 2.10.
199199

200+
@sect(NoExtendsApp.toString)
201+
@p
202+
Replaces usage of the @code{scala.App} trait with an explicit main function.
203+
204+
@hl.scala
205+
// before
206+
object Main extends App {
207+
println(s"Hello ${args(0)}")
208+
}
209+
210+
// after
211+
object Main {
212+
def main(args: Array[String]) = {
213+
println(s"Hello ${args(0)}")
214+
}
215+
}
216+
217+
@p
218+
The @code{scala.App} trait uses @code{DelayedInit}, which has been dropped in Dotty.
219+
More info @lnk("here", "https://github.com/lampepfl/dotty/blob/master/docs/docs/reference/dropped/delayed-init.md").
220+
200221
@sect{Planned rewrites...}
201222
See @lnk("here", "https://github.com/scalacenter/scalafix/labels/rewrite").
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package scalafix
2+
package rewrite
3+
4+
import scala.meta._
5+
import scalafix.syntax._
6+
import scalafix.util.{Whitespace => _, _}
7+
import scala.meta.contrib._
8+
import scala.meta.tokens.Token._
9+
10+
//TODO(gabro): move the helpers somewhere else
11+
import NoExtendsAppSyntax._
12+
13+
case class NoExtendsApp(mirror: Mirror) extends SemanticRewrite(mirror) {
14+
override def rewrite(ctx: RewriteCtx): Patch = {
15+
def wrapBodyInMain(template: Template) =
16+
ctx
17+
.templateBodyTokens(template) match {
18+
case None =>
19+
ctx.reporter.warn(
20+
"Missing or empty body for object that extends scala.App. See http://dotty.epfl.ch/docs/reference/dropped/delayed-init.html for possible workarounds.",
21+
template.pos
22+
)
23+
Patch.empty
24+
case Some(body) =>
25+
val open =
26+
ctx.addLeft(body.head,
27+
s"\n def main(args: Array[String]): Unit = {")
28+
val indentBody = ctx.indent(body)
29+
// this handles bodies on a single line like:
30+
// object Main extends App { println(args(0)) }
31+
val closingSpaces = if (indentBody.isDefined) " " * 2 else ""
32+
val close = ctx.addRight(body.last, s"$closingSpaces}\n")
33+
open + indentBody.asPatch + close
34+
}
35+
36+
ctx.tree.collect {
37+
case t: Defn.Object =>
38+
ctx.removeParentFromTemplate(Symbol("_root_.scala.App."), t.templ) +
39+
wrapBodyInMain(t.templ)
40+
}.asPatch
41+
}
42+
}
43+
44+
object NoExtendsAppSyntax {
45+
implicit class XtensionRewriteCtx(ctx: RewriteCtx) {
46+
47+
def templateBodyTokens(template: Template): Option[Tokens] = {
48+
val tokens = template.tokens
49+
val maybeTokens = for {
50+
close <- tokens.lastOption
51+
if close.is[RightBrace]
52+
open <- ctx.matching.open(close.asInstanceOf[RightBrace])
53+
} yield
54+
tokens
55+
.dropWhile(_.pos.start.offset <= open.pos.start.offset)
56+
.dropRight(1)
57+
maybeTokens.filterNot(_.isEmpty)
58+
}
59+
60+
def indent(tokens: Tokens, numberOfSpaces: Int = 2): Option[Patch] =
61+
tokens.dropRightWhile(t => !t.is[Newline]).lastOption.map {
62+
lastNewLine =>
63+
tokens.collect {
64+
case nl @ Newline() if nl != lastNewLine =>
65+
ctx.addRight(nl, " " * numberOfSpaces)
66+
}.asPatch
67+
}
68+
69+
private[scalafix] def removeTokensBetween(
70+
from: Token,
71+
to: Token,
72+
removeLeadingWhitespace: Boolean = true): Patch = {
73+
val toRemove = ctx.tokenList
74+
.slice(
75+
ctx.tokenList.prev(from),
76+
ctx.tokenList
77+
.next(to) // apply next twice to include the trailing space
78+
)
79+
val leadingToRemove =
80+
if (removeLeadingWhitespace)
81+
ctx.tokenList.leading(from).takeWhile(_.is[Whitespace])
82+
else
83+
Nil
84+
85+
(toRemove ++ leadingToRemove)
86+
.map(ctx.removeToken)
87+
.asPatch
88+
}
89+
90+
def removeParentFromTemplate(normalized: Symbol, template: Template)(
91+
implicit m: Mirror): Patch = {
92+
val maybePatch = for {
93+
treeToRemove <- template.parents
94+
.collect { case c: Ctor.Ref => c }
95+
.find(_.symbolOpt.map(_.normalized) == Some(normalized))
96+
nameToRemove <- treeToRemove.tokens.headOption
97+
leadingExtendsOrWithToken <- ctx.tokenList
98+
.leading(nameToRemove)
99+
.find(t => t.is[KwExtends] || t.is[KwWith])
100+
} yield
101+
(template.parents.length, leadingExtendsOrWithToken) match {
102+
// 1) object Foo extends ToRemove { ... }
103+
// or
104+
// 2) object Foo extends Something with ToRemove { ... }
105+
case (1, _) | (_, KwWith()) =>
106+
ctx.removeTokensBetween(leadingExtendsOrWithToken, nameToRemove)
107+
108+
// 3) object Foo extends ToRemove with Something
109+
case (_, KwExtends()) =>
110+
(for {
111+
trailingWith <- ctx.tokenList
112+
.trailing(nameToRemove)
113+
.find(t => t.is[KwWith])
114+
} yield
115+
ctx.removeTokensBetween(nameToRemove, trailingWith)).asPatch
116+
}
117+
maybePatch.asPatch
118+
}
119+
}
120+
}

scalafix-core/src/main/scala/scalafix/rewrite/ScalafixRewrites.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ object ScalafixRewrites {
1515
ExplicitReturnTypes(mirror),
1616
RemoveUnusedImports(mirror),
1717
Xor2Either(mirror),
18-
NoAutoTupling(mirror)
18+
NoAutoTupling(mirror),
19+
NoExtendsApp(mirror)
1920
)
2021
def all(mirror: Mirror): List[Rewrite] =
2122
syntax ++ semantic(mirror)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
rewrites = NoExtendsApp
3+
*/
4+
package test
5+
6+
object Main extends App {
7+
println(s"Hello, ${args(0)}")
8+
println(s"Hello 2, ${args(0)}")
9+
}
10+
11+
trait Something
12+
object Main2 extends Something with App{
13+
println(s"Hello, ${args(0)}")
14+
println(s"Hello 2, ${args(0)}")
15+
}
16+
17+
object Main3 extends App with Something{
18+
println(s"Hello, ${args(0)}")
19+
println(s"Hello 2, ${args(0)}")
20+
}
21+
22+
object Main4 extends App with Something
23+
24+
object Main5 extends App with Something {}
25+
26+
object Main6
27+
extends App
28+
with Something
29+
30+
object Main7
31+
extends Something
32+
with App
33+
34+
object Main8
35+
extends Something with
36+
App
37+
38+
object Main9 extends
39+
App with
40+
Something
41+
42+
import scala.{ App => Ppa }
43+
object Main10 extends Ppa with Something {
44+
println(s"Hello, ${args(0)}")
45+
println(s"Hello 2, ${args(0)}")
46+
}
47+
48+
object Main11 extends App { println(args(0)) }
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package test
2+
3+
object Main {
4+
def main(args: Array[String]): Unit = {
5+
println(s"Hello, ${args(0)}")
6+
println(s"Hello 2, ${args(0)}")
7+
}
8+
}
9+
10+
trait Something
11+
object Main2 extends Something{
12+
def main(args: Array[String]): Unit = {
13+
println(s"Hello, ${args(0)}")
14+
println(s"Hello 2, ${args(0)}")
15+
}
16+
}
17+
18+
object Main3 extends Something{
19+
def main(args: Array[String]): Unit = {
20+
println(s"Hello, ${args(0)}")
21+
println(s"Hello 2, ${args(0)}")
22+
}
23+
}
24+
25+
object Main4 extends Something
26+
27+
object Main5 extends Something {}
28+
29+
object Main6
30+
extends Something
31+
32+
object Main7
33+
extends Something
34+
35+
object Main8
36+
extends Something
37+
38+
object Main9 extends
39+
Something
40+
41+
import scala.{ App => Ppa }
42+
object Main10 extends Something {
43+
def main(args: Array[String]): Unit = {
44+
println(s"Hello, ${args(0)}")
45+
println(s"Hello 2, ${args(0)}")
46+
}
47+
}
48+
49+
object Main11 {
50+
def main(args: Array[String]): Unit = { println(args(0)) }
51+
}

0 commit comments

Comments
 (0)