From f113e4cf28c4cd19c3828065ab6d98009e34b78a Mon Sep 17 00:00:00 2001 From: amvanbaren Date: Mon, 21 Aug 2023 20:13:14 +0300 Subject: [PATCH] Route a method whose name is a reserved word. --- .../routes/compiler/RoutesFileParser.scala | 7 +- .../routes/compiler/templates/package.scala | 15 ++- .../javascriptReverseRouter.scala.twirl | 4 +- .../compiler/static/reverseRouter.scala.twirl | 4 +- .../src/test/resources/reservedWords.routes | 116 ++++++++++++++++++ .../routes/compiler/RoutesCompilerSpec.scala | 9 ++ 6 files changed, 147 insertions(+), 8 deletions(-) create mode 100644 dev-mode/play-routes-compiler/src/test/resources/reservedWords.routes diff --git a/dev-mode/play-routes-compiler/src/main/scala/play/routes/compiler/RoutesFileParser.scala b/dev-mode/play-routes-compiler/src/main/scala/play/routes/compiler/RoutesFileParser.scala index 7ceb9974362..5ce02f9598f 100644 --- a/dev-mode/play-routes-compiler/src/main/scala/play/routes/compiler/RoutesFileParser.scala +++ b/dev-mode/play-routes-compiler/src/main/scala/play/routes/compiler/RoutesFileParser.scala @@ -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" ) diff --git a/dev-mode/play-routes-compiler/src/main/scala/play/routes/compiler/templates/package.scala b/dev-mode/play-routes-compiler/src/main/scala/play/routes/compiler/templates/package.scala index 3700c157e55..ae1e7e8ae1c 100644 --- a/dev-mode/play-routes-compiler/src/main/scala/play/routes/compiler/templates/package.scala +++ b/dev-mode/play-routes-compiler/src/main/scala/play/routes/compiler/templates/package.scala @@ -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(", ") } @@ -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. */ diff --git a/dev-mode/play-routes-compiler/src/main/twirl/play/routes/compiler/static/javascriptReverseRouter.scala.twirl b/dev-mode/play-routes-compiler/src/main/twirl/play/routes/compiler/static/javascriptReverseRouter.scala.twirl index e9e48b88d89..2d01be367d3 100644 --- a/dev-mode/play-routes-compiler/src/main/twirl/play/routes/compiler/static/javascriptReverseRouter.scala.twirl +++ b/dev-mode/play-routes-compiler/src/main/twirl/play/routes/compiler/static/javascriptReverseRouter.scala.twirl @@ -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 @@ -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 diff --git a/dev-mode/play-routes-compiler/src/main/twirl/play/routes/compiler/static/reverseRouter.scala.twirl b/dev-mode/play-routes-compiler/src/main/twirl/play/routes/compiler/static/reverseRouter.scala.twirl index 3f67ca1a453..639698c366d 100644 --- a/dev-mode/play-routes-compiler/src/main/twirl/play/routes/compiler/static/reverseRouter.scala.twirl +++ b/dev-mode/play-routes-compiler/src/main/twirl/play/routes/compiler/static/reverseRouter.scala.twirl @@ -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) => diff --git a/dev-mode/play-routes-compiler/src/test/resources/reservedWords.routes b/dev-mode/play-routes-compiler/src/test/resources/reservedWords.routes new file mode 100644 index 00000000000..800acd94222 --- /dev/null +++ b/dev-mode/play-routes-compiler/src/test/resources/reservedWords.routes @@ -0,0 +1,116 @@ +# Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. + +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`() \ No newline at end of file diff --git a/dev-mode/play-routes-compiler/src/test/scala/play/routes/compiler/RoutesCompilerSpec.scala b/dev-mode/play-routes-compiler/src/test/scala/play/routes/compiler/RoutesCompilerSpec.scala index 860657abfc7..6be10a256fa 100644 --- a/dev-mode/play-routes-compiler/src/test/scala/play/routes/compiler/RoutesCompilerSpec.scala +++ b/dev-mode/play-routes-compiler/src/test/scala/play/routes/compiler/RoutesCompilerSpec.scala @@ -79,5 +79,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 + } } }