Skip to content

Commit

Permalink
Route a method whose name is a reserved word.
Browse files Browse the repository at this point in the history
  • Loading branch information
amvanbaren committed Aug 1, 2023
1 parent d21a74b commit 444c6c6
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 8 deletions.
Expand Up @@ -306,8 +306,11 @@ private[routes] class RoutesFileParser extends JavaTokenParsers {
// Since the Scala parser is greedy, we can't easily extract this out, so just parse at least 2
def absoluteMethod: Parser[List[String]] =
namedError(
ident ~ "." ~ rep1sep(ident, ".") ^^ {
case first ~ _ ~ rest => first :: rest
ident ~ "." ~ rep1sep(ident, ".") ~ opt(".`" ~> ident <~ "`") ^^ {
case first ~ _ ~ rest ~ None => first :: rest
case first ~ _ ~ rest ~ Some(tickedMethod) =>
val packageAndClass = first :: rest
packageAndClass :+ tickedMethod
},
"Controller method call expected"
)
Expand Down
Expand Up @@ -55,10 +55,11 @@ package object templates {
* Generate a controller method call for the given injected route
*/
def injectedControllerMethodCall(r: Route, ident: String, paramFormat: Parameter => String): String = {
val method = safeMethod(r.call.method)
val methodPart = if (r.call.instantiate) {
s"$ident.get.${r.call.method}"
s"$ident.get.${method}"
} else {
s"$ident.${r.call.method}"
s"$ident.${method}"
}
val paramPart = r.call.parameters
.map { params => params.map(paramFormat).mkString(", ") }
Expand Down Expand Up @@ -207,6 +208,16 @@ package object templates {
}
.getOrElse(keyword)

/**
* Ensure that the given method name doesn't clash with any of the keywords that Play is using, including Scala keywords.
*/
def safeMethod(method: String): String =
scalaReservedWords
.collectFirst {
case reserved if reserved == method => s"`$reserved`"
}
.getOrElse(method)

/**
* Calculate the parameters for the reverse route call for the given routes.
*/
Expand Down
Expand Up @@ -22,7 +22,7 @@ package @{packageName.map(_ + ".").getOrElse("")}javascript @ob
@for(((method, _), routes) <- groupRoutesByMethod(routes)) {@routes match {
case Seq(route: Route) => {
@markLines(route)
def @method: JavaScriptReverseRoute = JavaScriptReverseRoute(
def @{safeMethod(method)}: JavaScriptReverseRoute = JavaScriptReverseRoute(
"@{packageName.map(_ + ".").getOrElse("")}@(controller).@(method)",
@tq
function(@reverseParametersJavascript(routes).map(_._1.name).mkString(",")) @ob
Expand All @@ -33,7 +33,7 @@ package @{packageName.map(_ + ".").getOrElse("")}javascript @ob
}
case _ => {
@markLines(routes: _*)
def @method: JavaScriptReverseRoute = JavaScriptReverseRoute(
def @{safeMethod(method)}: JavaScriptReverseRoute = JavaScriptReverseRoute(
"@{packageName.map(_ + ".").getOrElse("")}@(controller).@(method)",
@tq
function(@reverseParametersJavascript(routes).map(_._1.name).mkString(",")) @ob
Expand Down
Expand Up @@ -21,14 +21,14 @@ import @if(!i.startsWith("_root_.")){_root_.}@i}
@for(((method, _), routes) <- groupRoutesByMethod(routes)) {@routes match {
case Seq(route: Route) => {
@markLines(route)
def @(method)@(reverseSignature(routes)): Call = @ob
def @{safeMethod(method)}@(reverseSignature(routes)): Call = @ob
@reverseRouteContext(route)
@reverseCall(route)
@cb
}
case _ => {
@markLines(routes: _*)
def @(method)@(reverseSignature(routes)): Call = @ob
def @{safeMethod(method)}@(reverseSignature(routes)): Call = @ob
@defining(reverseParameters(routes)) { params =>
(@reverseMatchParameters(params, true)) match @ob
@reverseUniqueConstraints(routes, params) { (route, parameters, parameterConstraints, localNames) =>
Expand Down
116 changes: 116 additions & 0 deletions dev-mode/routes-compiler/src/test/resources/reservedWords.routes
@@ -0,0 +1,116 @@
# Copyright (C) from 2022 The Play Framework Contributors <https://github.com/playframework>, 2011-2021 Lightbend Inc. <https://www.lightbend.com>

GET /as controllers.FooController.as()
GET /abstract controllers.FooController.abstract()
GET /case controllers.FooController.case()
GET /catch controllers.FooController.catch()
GET /class controllers.FooController.class()
GET /def controllers.FooController.def()
GET /derives controllers.FooController.derives()
GET /do controllers.FooController.do()
GET /else controllers.FooController.else()
GET /end controllers.FooController.end()
GET /enum controllers.FooController.enum()
GET /erased controllers.FooController.erased()
GET /extends controllers.FooController.extends()
GET /extension controllers.FooController.extension()
GET /export controllers.FooController.export()
GET /false controllers.FooController.false()
GET /final controllers.FooController.final()
GET /finally controllers.FooController.finally()
GET /for controllers.FooController.for()
GET /forSome controllers.FooController.forSome()
GET /given controllers.FooController.given()
GET /if controllers.FooController.if()
GET /implicit controllers.FooController.implicit()
GET /import controllers.FooController.import()
GET /infix controllers.FooController.infix()
GET /inline controllers.FooController.inline()
GET /lazy controllers.FooController.lazy()
GET /macro controllers.FooController.macro()
GET /match controllers.FooController.match()
GET /new controllers.FooController.new()
GET /null controllers.FooController.null()
GET /object controllers.FooController.object()
GET /opaque controllers.FooController.opaque()
GET /open controllers.FooController.open()
GET /override controllers.FooController.override()
GET /package controllers.FooController.package()
GET /private controllers.FooController.private()
GET /protected controllers.FooController.protected()
GET /return controllers.FooController.return()
GET /sealed controllers.FooController.sealed()
GET /super controllers.FooController.super()
GET /then controllers.FooController.then()
GET /this controllers.FooController.this()
GET /throw controllers.FooController.throw()
GET /throws controllers.FooController.throws()
GET /trait controllers.FooController.trait()
GET /transparent controllers.FooController.transparent()
GET /try controllers.FooController.try()
GET /true controllers.FooController.true()
GET /type controllers.FooController.type()
GET /using controllers.FooController.using()
GET /val controllers.FooController.val()
GET /var controllers.FooController.var()
GET /while controllers.FooController.while()
GET /with controllers.FooController.with()
GET /yield controllers.FooController.yield()
GET /queryString controllers.FooController.queryString()
GET /backticks/as controllers.FooController.`as`()
GET /backticks/abstract controllers.FooController.`abstract`()
GET /backticks/case controllers.FooController.`case`()
GET /backticks/catch controllers.FooController.`catch`()
GET /backticks/class controllers.FooController.`class`()
GET /backticks/def controllers.FooController.`def`()
GET /backticks/derives controllers.FooController.`derives`()
GET /backticks/do controllers.FooController.`do`()
GET /backticks/else controllers.FooController.`else`()
GET /backticks/end controllers.FooController.`end`()
GET /backticks/enum controllers.FooController.`enum`()
GET /backticks/erased controllers.FooController.`erased`()
GET /backticks/extends controllers.FooController.`extends`()
GET /backticks/extension controllers.FooController.`extension`()
GET /backticks/export controllers.FooController.`export`()
GET /backticks/false controllers.FooController.`false`()
GET /backticks/final controllers.FooController.`final`()
GET /backticks/finally controllers.FooController.`finally`()
GET /backticks/for controllers.FooController.`for`()
GET /backticks/forSome controllers.FooController.`forSome`()
GET /backticks/given controllers.FooController.`given`()
GET /backticks/if controllers.FooController.`if`()
GET /backticks/implicit controllers.FooController.`implicit`()
GET /backticks/import controllers.FooController.`import`()
GET /backticks/infix controllers.FooController.`infix`()
GET /backticks/inline controllers.FooController.`inline`()
GET /backticks/lazy controllers.FooController.`lazy`()
GET /backticks/macro controllers.FooController.`macro`()
GET /backticks/match controllers.FooController.`match`()
GET /backticks/new controllers.FooController.`new`()
GET /backticks/null controllers.FooController.`null`()
GET /backticks/object controllers.FooController.`object`()
GET /backticks/opaque controllers.FooController.`opaque`()
GET /backticks/open controllers.FooController.`open`()
GET /backticks/override controllers.FooController.`override`()
GET /backticks/package controllers.FooController.`package`()
GET /backticks/private controllers.FooController.`private`()
GET /backticks/protected controllers.FooController.`protected`()
GET /backticks/return controllers.FooController.`return`()
GET /backticks/sealed controllers.FooController.`sealed`()
GET /backticks/super controllers.FooController.`super`()
GET /backticks/then controllers.FooController.`then`()
GET /backticks/this controllers.FooController.`this`()
GET /backticks/throw controllers.FooController.`throw`()
GET /backticks/throws controllers.FooController.`throws`()
GET /backticks/trait controllers.FooController.`trait`()
GET /backticks/transparent controllers.FooController.`transparent`()
GET /backticks/try controllers.FooController.`try`()
GET /backticks/true controllers.FooController.`true`()
GET /backticks/type controllers.FooController.`type`()
GET /backticks/using controllers.FooController.`using`()
GET /backticks/val controllers.FooController.`val`()
GET /backticks/var controllers.FooController.`var`()
GET /backticks/while controllers.FooController.`while`()
GET /backticks/with controllers.FooController.`with`()
GET /backticks/yield controllers.FooController.`yield`()
GET /backticks/queryString controllers.FooController.`queryString`()
Expand Up @@ -69,5 +69,14 @@ class RoutesCompilerSpec extends Specification with FileMatchers {
tmp
) must beRight
}

"check if routes with reserved words as method name are compiled" in withTempDir { tmp =>
val file = new File(this.getClass.getClassLoader.getResource("reservedWords.routes").toURI)
RoutesCompiler.compile(
RoutesCompilerTask(file, Seq.empty, true, true, false),
InjectedRoutesGenerator,
tmp
) must beRight
}
}
}

0 comments on commit 444c6c6

Please sign in to comment.