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

move parser into its own module #792

Merged
merged 11 commits into from
Nov 19, 2021
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ jobs:
run: sbt ++${{ matrix.scala }} test

- name: Compress target directories
run: tar cf targets.tar target modules/ast/target modules/core/target modules/benchmarks/target project/target
run: tar cf targets.tar modules/core/target modules/parser/target modules/benchmarks/target modules/ast/target target project/target

- name: Upload target directories
uses: actions/upload-artifact@v2
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
*~

# folders
.bsp/
.idea/
Expand Down
56 changes: 42 additions & 14 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import sbt.Developer
import sbt.Keys.{
crossScalaVersions,
developers,
organizationHomepage,
scalacOptions,
scmInfo,
startYear
}
import sbt.Keys._
import com.typesafe.tools.mima.core.{
DirectMissingMethodProblem,
IncompatibleMethTypeProblem,
Expand Down Expand Up @@ -351,13 +344,31 @@ ThisBuild / mimaBinaryIssueFilters ++= Seq(
ProblemFilters.exclude[DirectMissingMethodProblem](
"sangria.parser.QueryParser.parseInputDocumentWithVariables"),
ProblemFilters.exclude[DirectMissingMethodProblem](
"sangria.parser.QueryParser.parseInputDocumentWithVariables")
"sangria.parser.QueryParser.parseInputDocumentWithVariables"),

// move parser into module
ProblemFilters.exclude[MissingClassProblem]("sangria.parser.Directives"),
ProblemFilters.exclude[MissingClassProblem]("sangria.parser.Document"),
ProblemFilters.exclude[MissingClassProblem]("sangria.parser.Fragments"),
ProblemFilters.exclude[MissingClassProblem]("sangria.parser.Ignored"),
ProblemFilters.exclude[MissingClassProblem]("sangria.parser.Operations"),
ProblemFilters.exclude[MissingClassProblem]("sangria.parser.ParserConfig"),
ProblemFilters.exclude[MissingClassProblem]("sangria.parser.ParserConfig$"),
ProblemFilters.exclude[MissingClassProblem]("sangria.parser.PositionTracking"),
ProblemFilters.exclude[MissingClassProblem]("sangria.parser.QueryParser"),
ProblemFilters.exclude[MissingClassProblem]("sangria.parser.QueryParser$"),
ProblemFilters.exclude[MissingClassProblem]("sangria.parser.SyntaxError"),
ProblemFilters.exclude[MissingClassProblem]("sangria.parser.SyntaxError$"),
ProblemFilters.exclude[MissingClassProblem]("sangria.parser.Tokens"),
ProblemFilters.exclude[MissingClassProblem]("sangria.parser.TypeSystemDefinitions"),
ProblemFilters.exclude[MissingClassProblem]("sangria.parser.Types"),
ProblemFilters.exclude[MissingClassProblem]("sangria.parser.Values"),
)

lazy val root = project
.in(file("."))
.withId("sangria-root")
.aggregate(ast, core, benchmarks)
.aggregate(ast, parser, core, benchmarks)
.settings(inThisBuild(projectInfo))
.settings(
scalacSettings ++ shellSettings ++ noPublishSettings
Expand All @@ -374,19 +385,34 @@ lazy val ast = project
)
.disablePlugins(MimaPlugin)

lazy val parser = project
performantdata marked this conversation as resolved.
Show resolved Hide resolved
.in(file("modules/parser"))
.withId("sangria-parser")
.dependsOn(ast)
.settings(scalacSettings ++ shellSettings)
.settings(
name := "sangria-parser",
description := "Scala GraphQL parser",
libraryDependencies ++= Seq(
// AST Parser
"org.parboiled" %% "parboiled" % "2.3.0",

"org.scalatest" %% "scalatest" % "3.2.10" % Test,
),
)
.disablePlugins(MimaPlugin)

lazy val core = project
.in(file("modules/core"))
.withId("sangria-core")
.dependsOn(ast)
.dependsOn(parser)
.settings(scalacSettings ++ shellSettings)
.settings(
name := "sangria",
description := "Scala GraphQL implementation",
mimaPreviousArtifacts := Set("org.sangria-graphql" %% "sangria" % "2.1.3"),
Test / testOptions += Tests.Argument(TestFrameworks.ScalaTest, "-oF"),
libraryDependencies ++= Seq(
// AST Parser
"org.parboiled" %% "parboiled" % "2.3.0",
// AST Visitor
"org.sangria-graphql" %% "macro-visit" % "0.1.3",
// Marshalling
Expand Down Expand Up @@ -452,7 +478,9 @@ lazy val scalacSettings = Seq(
},
scalacOptions += "-target:jvm-1.8",
Compile / doc / scalacOptions ++= Seq( // scaladoc options
"-groups"),
"-groups",
"-doc-title", "Sangria",
),
javacOptions ++= Seq("-source", "8", "-target", "8")
)

Expand Down
6 changes: 5 additions & 1 deletion modules/core/src/main/scala/sangria/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@
* @see
* [[https://sangria-graphql.github.io/ the Sangria home page]]
*/
package object sangria {}
package object sangria {

/** "Since" field for 3.0.0 deprecations. */
private[sangria] final val since3_0_0 = "Sangria 3.0.0"
}
10 changes: 6 additions & 4 deletions modules/core/src/main/scala/sangria/util/StringUtil.scala
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
package sangria.util

import java.util.Locale
import sangria.since3_0_0

import java.util.Locale
import scala.collection.mutable.ListBuffer

object StringUtil {
private val camelToUpper = "_*([A-Z][a-z\\d]+)".r

def camelCaseToUnderscore(name: String) =
def camelCaseToUnderscore(name: String): String =
camelToUpper.findAllMatchIn(name).map(_.group(1).toLowerCase).mkString("_")

def orList(items: Seq[String], limit: Int = 5) =
def orList(items: Seq[String], limit: Int = 5): String =
if (items.isEmpty)
throw new IllegalArgumentException("List is empty")
else {
Expand Down Expand Up @@ -80,7 +81,7 @@ object StringUtil {
d(a.length)(b.length)
}

def escapeBlockString(str: String) = str.replaceAll("\"\"\"", "\\\\\"\"\"")
def escapeBlockString(str: String): String = str.replaceAll("\"\"\"", "\\\\\"\"\"")

def escapeString(str: String): String =
str
Expand Down Expand Up @@ -114,6 +115,7 @@ object StringUtil {
*
* This implements the GraphQL spec's BlockStringValue() static algorithm.
*/
@deprecated("This method will be removed.", since3_0_0)
def blockStringValue(rawString: String): String = {
val lines = rawString.split("""\r\n|[\n\r]""")
val lineSizes = lines.map(l => l -> leadingWhitespace(l))
Expand Down
115 changes: 0 additions & 115 deletions modules/core/src/test/scala/sangria/util/StringUtilSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -55,119 +55,4 @@ class StringUtilSpec extends AnyWordSpec with Matchers with StringMatchers {
suggestionList("descritpion", options) should be(Seq("description"))
}
}

"blockStringValue" should {
"removes uniform indentation from a string" in {
blockStringValue(
"""
| Hello,
| World!
|
| Yours,
| GraphQL.
|""".stripMargin
) should equal(
"""Hello,
| World!
|
|Yours,
| GraphQL.""".stripMargin
)(after.being(strippedOfCarriageReturns))
}

"removes empty leading and trailing lines" in {
blockStringValue(
"""
|
| Hello,
| World!
|
| Yours,
| GraphQL.
|
|""".stripMargin
) should equal(
"""Hello,
| World!
|
|Yours,
| GraphQL.""".stripMargin
)(after.being(strippedOfCarriageReturns))
}

"removes blank leading and trailing lines" in {
val spaces = " " * 10

blockStringValue(
s"""
|$spaces
| Hello,
| World!
|
| Yours,
| GraphQL.
|$spaces
|""".stripMargin
) should equal(
"""Hello,
| World!
|
|Yours,
| GraphQL.""".stripMargin
)(after.being(strippedOfCarriageReturns))
}

"retains indentation from first line" in {
blockStringValue(
""" Hello,
| World!
|
| Yours,
| GraphQL.
|""".stripMargin
) should equal(
""" Hello,
| World!
|
|Yours,
| GraphQL.""".stripMargin
)(after.being(strippedOfCarriageReturns))
}

"does not alter trailing spaces" in {
val spaces = " " * 10

blockStringValue(
s"""
| Hello,$spaces
| World!$spaces
| $spaces
| Yours,$spaces
| GraphQL.$spaces
|""".stripMargin
) should equal(
s"""Hello,$spaces
| World!$spaces
|$spaces
|Yours,$spaces
| GraphQL.$spaces""".stripMargin
)(after.being(strippedOfCarriageReturns))
}

"handles empty strings" in {
val spaces = " " * 10

blockStringValue(
s"""
|$spaces
|
|$spaces
|""".stripMargin
) should equal(
"".stripMargin
)(after.being(strippedOfCarriageReturns))

blockStringValue("") should equal("")
}
}
}
38 changes: 38 additions & 0 deletions modules/parser/src/main/scala/sangria/parser/Lexical.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package sangria.parser

/** Lexical analysis tools described in the GraphQL specification. */
private[parser] object Lexical {

/** Regex that matches line termination sequences. */
private[this] val newlineRegex = """\r\n|[\n\r]""".r

/** Produces the value of a block string from its parsed raw value, similar to Coffeescript's
* block string, Python's docstring trim or Ruby's strip_heredoc.
*
* This implements the GraphQL spec's BlockStringValue() static algorithm.
*
* @see
* [[https://spec.graphql.org/October2021/#BlockStringValue()]]
*/
def blockStringValue(rawValue: String): String = {
val lines = newlineRegex.split(rawValue)
val lineSizes = lines.map(l => l -> leadingWhitespace(l))
val commonIndentLines =
lineSizes.drop(1).collect { case (line, size) if size != line.length => size }
val strippedLines = if (commonIndentLines.nonEmpty) {
val commonIndent = commonIndentLines.min
lines.take(1) ++ lines.drop(1).map(_.drop(commonIndent))
} else lines

val trimmedLines = strippedLines.reverse.dropWhile(isBlank).reverse.dropWhile(isBlank)
trimmedLines.mkString("\n")
}

private[this] def leadingWhitespace(str: String) = {
var i = 0
while (i < str.length && (str.charAt(i) == ' ' || str.charAt(i) == '\t')) i += 1
i
}

private[this] def isBlank(str: String) = leadingWhitespace(str) == str.length
}
Loading