[
  {
    "path": ".gitignore",
    "content": "node_modules\n*.iml\n*.ipr\n*.iws\n.idea/\ntarget/\ndata/\n*.log\n.DS_Store\nlocal.sbt\n"
  },
  {
    "path": ".scalafmt.conf",
    "content": "style = defaultWithAlign\nmaxColumn = 120\nalign.openParenCallSite = false\nalign.openParenDefnSite = false\ndanglingParentheses = true\n\nrewrite.rules = [RedundantBraces, RedundantParens, SortImports, PreferCurlyFors]\nrewrite.redundantBraces.includeUnitMethods = true\nrewrite.redundantBraces.stringInterpolation = true\n"
  },
  {
    "path": ".travis.yml",
    "content": "sudo: false\nlanguage: scala\njdk:\n- oraclejdk8\nscala:\n- 2.11.8\ninstall:\n- \". $HOME/.nvm/nvm.sh\"\n- nvm install stable\n- nvm use stable\n- npm install\nscript:\n- sbt test\n- '[ \"${TRAVIS_PULL_REQUEST}\" = \"false\" ] && sbt updateImpactSubmit || true'\nnotifications:\n  slack:\n    secure: BLLFmdz5G6098EfMgw+CXNqqnwQPDHhaXp0Bg3GWkUoAh3/BoWFhI6iTRgJW3q85IBLa6A2el0wLY8mK1lyD4mRg5dLTPWCILqb8eaF6Shop/FQi0O3pyACvfYUitW9EBalug9VVZzceKsHDaW9pbuqDxCWv1qWyBlTv7aSfVnyYlh6LC+3YqsWFeMHA7/W/lSAklvHkk7GTYIp3A30DsYup2K/EHMJ4ZxuA0CZOOrE4jOfEOzZgm787asPT8o12yb3CWEBZai3Fxs4s6I25w1t6Y5JTPXnYi01OIug6mDtZRDAh1ZJHUdyhOXbxqib6QbCrayg1ou3yde8pKm3KgnMMAJtu6wB8XCMlCDCJ81qkfOCY7TtUfax2spTD5xyIkoQxHPSqOnKyaBYXMKaQ4gMMGiJQToZ8hPtQTYDZTMK5VD5CS1kNMqJBI4V7YlFnKchbv1NUBTKA18QouTCVu369hBb2gANzI1IR4AoP+LRgFjCqEyAAcqoa1jkuENyF1KZykTa3NrkENBlzhEJofYwIi9TP8JUY8mCJp2dnLqnWU/PlJJBTEkMd/00My92znleo2S4gaDgd79quD7iJuZotJt5pkad9auh18ZFQcgmgo9dc3nS3yCk+/vVfPvYKP0pHRWe5RmPkaVbCZ6kCP79LIVlMcA1oYhpNRiwXe5o=\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2013-2016 Softwaremill\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# Scala clippy\n\n[![Join the chat at https://gitter.im/softwaremill/scala-clippy](https://badges.gitter.im/softwaremill/scala-clippy.svg)](https://gitter.im/softwaremill/scala-clippy?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)\n[![Build Status](https://travis-ci.org/softwaremill/scala-clippy.svg?branch=master)](https://travis-ci.org/softwaremill/scala-clippy)\n[![Dependencies](https://app.updateimpact.com/badge/634276070333485056/clippy.svg?config=compile)](https://app.updateimpact.com/latest/634276070333485056/clippy)\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.softwaremill.clippy/plugin_2.11/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.softwaremill.clippy/plugin_2.11)\n\nEnrich your Scala compiler error output with additional advices and colors!\n\n![enriched error example](ui/app/assets/img/clippy-akka-err-rich.png \"\")\n\n# Documentation\n\nRead the detailed [documentation](https://scala-clippy.org).\n\n# Contributing to the project\n\nYou can also help developing the plugin and/or the UI for submitting new advices! The module structure is:\n\n* `model` - code shared between the UI and the plugin. Contains basic model case classes, such as `CompilationError` + parser\n* `plugin` - the compiler plugin which actually displays the advices and matches errors agains the database of known errors\n* `tests` - tests for the compiler plugin. Must be a separate project, as it requires the plugin jar to be ready\n* `ui` - the ui server project in Play\n* `ui-client` - the Scala.JS client-side code\n* `ui-shared` - code shared between the UI server and UI client (but not needed for the plugin)\n\nFor examples on how to write tests for advice to ensure it does not go out of date see [CompileTests.scala](./tests/src/test/scala/org/softwaremill/clippy/CompileTests.scala).\nIf you want to write your own tests with compilation using `mkToolbox`, remember to add a `-P:clippy:testmode=true`\ncompiler option. It ensures that a correct reporter replacement mechanism is used, which needs to be different\nspecifically for tests. See [CompileTests.scala](tests/src/test/scala/org/softwaremill/clippy/CompileTests.scala) for reference.\n\n\nTo publish locally append \"-SNAPSHOT\" to the version number then run\n````scala\nsbt \"project plugin\" \"+ publishLocal\"\n````\n\nRun advice tests with\n````scala\nsbt tests/test\n````\n\n# Heroku deployment\n\nLocally:\n\n* Install the Heroku Toolbelt\n* link the local git repository with the Heroku application: `heroku git:remote -a scala-clippy`\n* run `sbt deployHeroku` to deploy the current code as a fat-jar\n\nCurrently deployed on `https://www.scala-clippy.org`\n\n# Credits\n\nClippy contributors:\n\n* [Krzysztof Ciesielski](https://github.com/kciesielski)\n* [Łukasz Żuchowski](https://github.com/Zuchos)\n* [Shane Delmore](https://github.com/ShaneDelmore)\n* [Adam Warski](https://github.com/adamw)\n\nSyntax highlighting code is copied from [Ammonite](http://www.lihaoyi.com/Ammonite/).\n"
  },
  {
    "path": "build.sbt",
    "content": "import sbt._\nimport Keys._\nimport sbtassembly.AssemblyKeys\n\nimport scala.xml.transform.RuleTransformer\nimport scala.xml.transform.RewriteRule\nimport scala.xml.{Node => XNode}\nimport scala.xml.{NodeSeq => XNodeSeq}\nimport scala.xml.{Elem => XElem}\n\nval slickVersion = \"3.1.1\"\n\nval json4s = \"org.json4s\" %% \"json4s-native\" % \"3.5.0\"\n\n// testing\nval scalatest  = \"org.scalatest\"  %% \"scalatest\"  % \"3.0.1\"  % \"test\"\nval scalacheck = \"org.scalacheck\" %% \"scalacheck\" % \"1.13.4\" % \"test\"\n\nname := \"clippy\"\n\n// factor out common settings into a sequence\nlazy val commonSettingsNoScalaVersion = Seq(\n  organization := \"com.softwaremill.clippy\",\n  version := \"0.6.1\",\n  scalacOptions ++= Seq(\"-unchecked\", \"-deprecation\"),\n  parallelExecution := false,\n  // Sonatype OSS deployment\n  publishTo := {\n    val nexus = \"https://oss.sonatype.org/\"\n    if (version.value.trim.endsWith(\"SNAPSHOT\"))\n      Some(\"snapshots\" at nexus + \"content/repositories/snapshots\")\n    else\n      Some(\"releases\" at nexus + \"service/local/staging/deploy/maven2\")\n  },\n  credentials += Credentials(Path.userHome / \".ivy2\" / \".credentials\"),\n  publishMavenStyle := true,\n  publishArtifact in Test := false,\n  pomIncludeRepository := { _ =>\n    false\n  },\n  pomExtra :=\n    <scm>\n      <url>git@github.com:softwaremill/scala-clippy.git</url>\n      <connection>scm:git:git@github.com:softwaremill/scala-clippy.git</connection>\n    </scm>\n      <developers>\n        <developer>\n          <id>adamw</id>\n          <name>Adam Warski</name>\n          <url>http://www.warski.org</url>\n        </developer>\n      </developers>,\n  licenses := (\"Apache2\", new java.net.URL(\"http://www.apache.org/licenses/LICENSE-2.0.txt\")) :: Nil,\n  homepage := Some(new java.net.URL(\"http://www.softwaremill.com\")),\n  com.updateimpact.Plugin.apiKey in ThisBuild := sys.env\n    .getOrElse(\"UPDATEIMPACT_API_KEY\", (com.updateimpact.Plugin.apiKey in ThisBuild).value)\n)\n\nlazy val commonSettings = commonSettingsNoScalaVersion ++ Seq(\n  scalaVersion := \"2.11.11\"\n)\n\nlazy val sbt10CompatSettings = Seq(\n  sbtVersion in Global := (if (scalaVersion.value startsWith \"2.12.\") \"1.1.6\" else \"0.13.15\"),\n  scalaCompilerBridgeSource := (\"org.scala-sbt\" % \"compiler-interface\" % \"0.13.15\" % \"component\").sources\n)\n\nlazy val clippy = (project in file(\".\"))\n  .settings(commonSettings)\n  .settings(\n    publishArtifact := false,\n    // heroku\n    herokuFatJar in Compile := Some((assemblyOutputPath in ui in assembly).value),\n    deployHeroku in Compile := (deployHeroku in Compile).dependsOn(assembly in ui).value\n  )\n  .aggregate(modelJvm, plugin, pluginSbt, tests, ui)\n\nlazy val model = (crossProject.crossType(CrossType.Pure) in file(\"model\"))\n  .settings(commonSettings: _*)\n  .settings(\n    libraryDependencies ++= Seq(scalatest, scalacheck, json4s)\n  )\n\nlazy val modelJvm = model.jvm.settings(name := \"modelJvm\")\nlazy val modelJs  = model.js.settings(name := \"modelJs\")\n\ndef removeDep(groupId: String, artifactId: String) = new RewriteRule {\n  override def transform(n: XNode): XNodeSeq = n match {\n    case e: XElem if (e \\ \"groupId\").text == groupId && (e \\ \"artifactId\").text.startsWith(artifactId) =>\n      XNodeSeq.Empty\n    case _ => n\n  }\n}\n\nlazy val plugin = (project in file(\"plugin\"))\n  .enablePlugins(BuildInfoPlugin)\n  .settings(commonSettings)\n  .settings(\n    crossScalaVersions := Seq(scalaVersion.value, \"2.12.1\", \"2.10.6\"),\n    libraryDependencies ++= Seq(\n      \"org.scala-lang\" % \"scala-compiler\" % scalaVersion.value % \"provided\",\n      \"com.lihaoyi\"    %% \"scalaparse\"    % \"0.4.2\",\n      \"com.lihaoyi\"    %% \"fansi\"         % \"0.2.3\",\n      scalatest,\n      scalacheck,\n      json4s\n    ),\n    // this is needed for fastparse to work on 2.10\n    libraryDependencies ++= (if (scalaVersion.value startsWith \"2.10.\")\n                               Seq(compilerPlugin(\"org.scalamacros\" % s\"paradise\" % \"2.1.0\" cross CrossVersion.full))\n                             else Seq()),\n    pomPostProcess := { (node: XNode) =>\n      new RuleTransformer(removeDep(\"org.json4s\", \"json4s-native\")).transform(node).head\n    },\n    buildInfoPackage := \"com.softwaremill.clippy\",\n    buildInfoObject := \"ClippyBuildInfo\",\n    artifact in (Compile, assembly) := {\n      val art = (artifact in (Compile, assembly)).value\n      art.copy(`classifier` = Some(\"bundle\"))\n    },\n    assemblyOption in assembly := (assemblyOption in assembly).value.copy(includeScala = false),\n    // including the model classes for re-compilation, as for some reason depending on modelJvm doesn't work\n    unmanagedSourceDirectories in Compile ++= (sourceDirectories in (modelJvm, Compile)).value\n  )\n  .settings(sbt10CompatSettings)\n  .settings(addArtifact(artifact in (Compile, assembly), assembly))\n\nlazy val pluginJar = AssemblyKeys.`assembly` in (plugin, Compile)\n\nlazy val pluginSbt = (project in file(\"plugin-sbt\"))\n  .enablePlugins(BuildInfoPlugin)\n  .settings(commonSettingsNoScalaVersion)\n  .settings(\n    sbtPlugin := true,\n    name := \"plugin-sbt\",\n    buildInfoPackage := \"com.softwaremill.clippy\",\n    buildInfoObject := \"ClippyBuildInfo\",\n    scalaVersion := \"2.10.6\",\n    crossSbtVersions := Vector(\"0.13.16\", \"1.0.0\")\n  )\n  .settings(sbt10CompatSettings)\n\nlazy val tests = (project in file(\"tests\"))\n  .settings(commonSettings)\n  .settings(\n    publishArtifact := false,\n    libraryDependencies ++= Seq(\n      json4s,\n      scalatest,\n      \"com.typesafe.akka\"        %% \"akka-http\" % \"10.0.0\",\n      \"com.softwaremill.macwire\" %% \"macros\"    % \"2.2.2\" % \"provided\",\n      \"com.typesafe.slick\"       %% \"slick\"     % slickVersion\n    ),\n    // during tests, read from the local repository, if at all available\n    scalacOptions ++= List(\n      s\"-Xplugin:${pluginJar.value.getAbsolutePath}\",\n      \"-P:clippy:url=http://localhost:9000\",\n      \"-P:clippy:colors=true\"\n    ),\n    envVars in Test := (envVars in Test).value + (\"CLIPPY_PLUGIN_PATH\" -> pluginJar.value.getAbsolutePath),\n    fork in Test := true\n  )\n  .dependsOn(modelJvm)\n\nlazy val ui: Project = (project in file(\"ui\"))\n  .enablePlugins(BuildInfoPlugin)\n  .settings(commonSettings)\n  .settings(\n    libraryDependencies ++= Seq(\n      \"com.h2database\" % \"h2\" % \"1.4.190\", // % \"test\",\n      scalatest,\n      \"org.webjars\"             %% \"webjars-play\"         % \"2.4.0-1\",\n      \"org.webjars\"             % \"bootstrap\"             % \"3.3.6\",\n      \"org.webjars\"             % \"jquery\"                % \"1.11.3\",\n      \"com.vmunier\"             %% \"play-scalajs-scripts\" % \"0.3.0\",\n      \"com.softwaremill.common\" %% \"id-generator\"         % \"1.1.0\",\n      \"com.sendgrid\"            % \"sendgrid-java\"         % \"2.2.2\" exclude (\"commons-logging\", \"commons-logging\"),\n      \"org.postgresql\"          % \"postgresql\"            % \"9.4.1207\",\n      \"com.typesafe.slick\"      %% \"slick\"                % slickVersion,\n      \"com.typesafe.slick\"      %% \"slick-hikaricp\"       % slickVersion,\n      \"org.flywaydb\"            % \"flyway-core\"           % \"3.2.1\"\n    ),\n    scalaJSProjects := Seq(uiClient),\n    pipelineStages in Assets := Seq(scalaJSProd),\n    routesGenerator := InjectedRoutesGenerator,\n    // heroku & fat-jar\n    assemblyJarName in assembly := \"app.jar\",\n    mainClass in assembly := Some(\"play.core.server.ProdServerStart\"),\n    fullClasspath in assembly += Attributed.blank(PlayKeys.playPackageAssets.value),\n    buildInfoPackage := \"util\",\n    buildInfoObject := \"ClippyBuildInfo\",\n    assemblyMergeStrategy in assembly := {\n      // anything in public/lib is copied from webjars and causes duplicate resources exceptions\n      case PathList(\"public\", \"lib\", xs @ _ *) => MergeStrategy.discard\n      case \"JS_DEPENDENCIES\"                   => MergeStrategy.discard\n      case x =>\n        val oldStrategy = (assemblyMergeStrategy in assembly).value\n        oldStrategy(x)\n    }\n  )\n  .enablePlugins(PlayScala)\n  .aggregate(uiClient)\n  .dependsOn(uiSharedJvm)\n\nval scalaJsReactVersion = \"0.11.3\"\n\nlazy val uiClient: Project = (project in file(\"ui-client\"))\n  .settings(commonSettings)\n  .settings(name := \"uiClient\")\n  .settings(\n    scalaJSUseMainModuleInitializer := true,\n    scalaJSUseMainModuleInitializer in Test := false,\n    addCompilerPlugin(compilerPlugin(\"org.scalamacros\" % \"paradise\" % \"2.1.0\" cross CrossVersion.full)), // for @Lenses\n    libraryDependencies ++= Seq(\n      \"org.scala-js\"                      %%% \"scalajs-dom\"    % \"0.9.1\",\n      \"be.doeraene\"                       %%% \"scalajs-jquery\" % \"0.9.1\",\n      \"com.github.japgolly.scalajs-react\" %%% \"core\"           % scalaJsReactVersion,\n      \"com.github.japgolly.scalajs-react\" %%% \"ext-monocle\"    % scalaJsReactVersion,\n      \"com.github.japgolly.fork.monocle\"  %%% \"monocle-macro\"  % \"1.2.0\"\n    ),\n    jsDependencies ++= Seq(\n      RuntimeDOM          % \"test\",\n      \"org.webjars.bower\" % \"react\" % \"15.3.2\" / \"react-with-addons.js\" minified \"react-with-addons.min.js\" commonJSName \"React\",\n      \"org.webjars.bower\" % \"react\" % \"15.3.2\" / \"react-dom.js\" minified \"react-dom.min.js\" dependsOn \"react-with-addons.js\" commonJSName \"ReactDOM\"\n    )\n  )\n  .enablePlugins(ScalaJSPlugin, ScalaJSWeb)\n  .dependsOn(uiSharedJs)\n\nlazy val uiShared = (crossProject.crossType(CrossType.Pure) in file(\"ui-shared\"))\n  .settings(commonSettings: _*)\n  .settings(\n    name := \"uiShared\",\n    libraryDependencies ++= Seq(\n      \"com.lihaoyi\" %%% \"autowire\" % \"0.2.5\",\n      \"com.lihaoyi\" %%% \"upickle\"  % \"0.3.6\"\n    )\n  )\n  .jsConfigure(_ enablePlugins ScalaJSWeb)\n  .dependsOn(model)\n\nlazy val uiSharedJvm = uiShared.jvm.settings(name := \"uiSharedJvm\")\nlazy val uiSharedJs  = uiShared.js.settings(name := \"uiSharedJs\")\n"
  },
  {
    "path": "model/src/main/scala/com/softwaremill/clippy/Advice.scala",
    "content": "package com.softwaremill.clippy\n\nimport org.json4s.JsonAST._\n\nfinal case class Advice(compilationError: CompilationError[RegexT], advice: String, library: Library) {\n  def errMatching: PartialFunction[CompilationError[ExactT], String] = {\n    case ce if compilationError.matches(ce) => advice\n  }\n\n  def toJson: JValue = JObject(\n    \"error\"   -> compilationError.toJson,\n    \"text\"    -> JString(advice),\n    \"library\" -> library.toJson\n  )\n}\n\nobject Advice {\n  def fromJson(jvalue: JValue): Option[Advice] =\n    (for {\n      JObject(fields)               <- jvalue\n      JField(\"error\", errorJV)      <- fields\n      error                         <- CompilationError.fromJson(errorJV).toList\n      JField(\"text\", JString(text)) <- fields\n      JField(\"library\", libraryJV)  <- fields\n      library                       <- Library.fromJson(libraryJV).toList\n    } yield Advice(error, text, library)).headOption\n}\n"
  },
  {
    "path": "model/src/main/scala/com/softwaremill/clippy/Clippy.scala",
    "content": "package com.softwaremill.clippy\n\nimport org.json4s.JsonAST._\n\ncase class Clippy(version: String, advices: List[Advice], fatalWarnings: List[Warning]) {\n  def checkPluginVersion(ourVersion: String, logInfo: String => Unit) =\n    if (version != ourVersion) {\n      logInfo(s\"New version of clippy plugin available: $version. Please update!\")\n    }\n\n  def toJson: JValue = JObject(\n    \"version\"       -> JString(version),\n    \"advices\"       -> JArray(advices.map(_.toJson)),\n    \"fatalWarnings\" -> JArray(fatalWarnings.map(_.toJson))\n  )\n}\n\nobject Clippy {\n  def fromJson(jvalue: JValue): Option[Clippy] =\n    (for {\n      JObject(fields)                     <- jvalue\n      JField(\"version\", JString(version)) <- fields\n    } yield {\n      val advices = jvalue.findField {\n        case JField(\"advices\", _) => true\n        case _                    => false\n      } match {\n        case Some((_, JArray(advicesJV))) => advicesJV.flatMap(Advice.fromJson)\n        case _                            => Nil\n      }\n      val fatalWarnings = fields.find { tpl =>\n        tpl._1 == \"fatalWarnings\"\n      } match {\n        case Some((_, JArray(fatalWarningsJV))) => fatalWarningsJV.flatMap(Warning.fromJson)\n        case _                                  => Nil\n      }\n      Clippy(version, advices, fatalWarnings)\n    }).headOption\n}\n"
  },
  {
    "path": "model/src/main/scala/com/softwaremill/clippy/CompilationError.scala",
    "content": "package com.softwaremill.clippy\n\nimport org.json4s.JValue\nimport org.json4s.JsonAST.{JArray, JField, JObject, JString}\nimport org.json4s.native.JsonMethods._\nimport CompilationError._\n\nsealed trait CompilationError[T <: Template] {\n  def toJson: JValue\n  def toJsonString: String = compact(render(toJson))\n  def matches(other: CompilationError[ExactT])(implicit ev: T =:= RegexT): Boolean\n  def asRegex(implicit ev: T =:= ExactT): CompilationError[RegexT]\n}\n\ncase class TypeMismatchError[T <: Template](\n    found: T,\n    foundExpandsTo: Option[T],\n    required: T,\n    requiredExpandsTo: Option[T],\n    notes: Option[String]\n) extends CompilationError[T] {\n\n  def notesAfterNewline = notes.fold(\"\")(n => \"\\n\" + n)\n\n  override def toString = {\n    def expandsTo(et: Option[T]): String = et.fold(\"\")(e => s\" (expands to: $e)\")\n    s\"Type mismatch error.\\nFound: $found${expandsTo(foundExpandsTo)},\\nrequired: $required${expandsTo(requiredExpandsTo)}$notesAfterNewline\"\n  }\n\n  override def toJson =\n    JObject(\n      List(TypeField -> JString(\"typeMismatch\"), \"found\" -> JString(found.v), \"required\" -> JString(required.v))\n        ++ foundExpandsTo.fold[List[JField]](Nil)(e => List(\"foundExpandsTo\"       -> JString(e.v)))\n        ++ requiredExpandsTo.fold[List[JField]](Nil)(e => List(\"requiredExpandsTo\" -> JString(e.v)))\n    )\n\n  override def matches(other: CompilationError[ExactT])(implicit ev: T =:= RegexT) = other match {\n    case TypeMismatchError(f, fe, r, re, _) =>\n      def optMatches(t: Option[T], v: Option[ExactT]) =\n        (for {\n          tt <- t\n          vv <- v\n        } yield tt.matches(vv)).getOrElse(true)\n\n      found.matches(f) && optMatches(foundExpandsTo, fe) && required.matches(r) && optMatches(requiredExpandsTo, re)\n\n    case _ =>\n      false\n  }\n\n  def hasExpands: Boolean = foundExpandsTo.nonEmpty || requiredExpandsTo.nonEmpty\n\n  override def asRegex(implicit ev: T =:= ExactT) =\n    TypeMismatchError(\n      RegexT.fromPattern(found.v),\n      foundExpandsTo.map(fe => RegexT.fromPattern(fe.v)),\n      RegexT.fromPattern(required.v),\n      requiredExpandsTo.map(re => RegexT.fromPattern(re.v)),\n      notes\n    )\n}\n\ncase class NotFoundError[T <: Template](what: T) extends CompilationError[T] {\n\n  override def toString = s\"Not found error: $what\"\n\n  override def toJson =\n    JObject(TypeField -> JString(\"notFound\"), \"what\" -> JString(what.v))\n\n  override def matches(other: CompilationError[ExactT])(implicit ev: T =:= RegexT) = other match {\n    case NotFoundError(w) => what.matches(w)\n    case _                => false\n  }\n\n  override def asRegex(implicit ev: T =:= ExactT) = NotFoundError(RegexT.fromPattern(what.v))\n}\n\ncase class NotAMemberError[T <: Template](what: T, notAMemberOf: T) extends CompilationError[T] {\n\n  override def toString = s\"Not a member error: $what isn't a member of $notAMemberOf\"\n\n  override def toJson =\n    JObject(\n      TypeField      -> JString(\"notAMember\"),\n      \"what\"         -> JString(what.v),\n      \"notAMemberOf\" -> JString(notAMemberOf.v)\n    )\n\n  override def matches(other: CompilationError[ExactT])(implicit ev: T =:= RegexT) = other match {\n    case NotAMemberError(w, n) => what.matches(w) && notAMemberOf.matches(n)\n    case _                     => false\n  }\n\n  override def asRegex(implicit ev: T =:= ExactT) =\n    NotAMemberError(RegexT.fromPattern(what.v), RegexT.fromPattern(notAMemberOf.v))\n}\n\ncase class ImplicitNotFoundError[T <: Template](parameter: T, implicitType: T) extends CompilationError[T] {\n\n  override def toString = s\"Implicit not found error: for parameter $parameter of type $implicitType\"\n\n  override def toJson =\n    JObject(\n      TypeField      -> JString(\"implicitNotFound\"),\n      \"parameter\"    -> JString(parameter.v),\n      \"implicitType\" -> JString(implicitType.v)\n    )\n\n  override def matches(other: CompilationError[ExactT])(implicit ev: T =:= RegexT) = other match {\n    case ImplicitNotFoundError(p, i) => parameter.matches(p) && implicitType.matches(i)\n    case _                           => false\n  }\n\n  override def asRegex(implicit ev: T =:= ExactT) =\n    ImplicitNotFoundError(RegexT.fromPattern(parameter.v), RegexT.fromPattern(implicitType.v))\n}\n\ncase class TypeclassNotFoundError[T <: Template](typeclass: T, forType: T) extends CompilationError[T] {\n\n  override def toString = s\"Implicit $typeclass typeclass not found error: for type $forType\"\n\n  override def toJson =\n    JObject(\n      TypeField   -> JString(\"typeclassNotFound\"),\n      \"typeclass\" -> JString(typeclass.v),\n      \"forType\"   -> JString(forType.v)\n    )\n\n  override def matches(other: CompilationError[ExactT])(implicit ev: T =:= RegexT) = other match {\n    case TypeclassNotFoundError(p, i) => typeclass.matches(p) && forType.matches(i)\n    case _                            => false\n  }\n\n  override def asRegex(implicit ev: T =:= ExactT) =\n    TypeclassNotFoundError(RegexT.fromPattern(typeclass.v), RegexT.fromPattern(forType.v))\n}\n\ncase class DivergingImplicitExpansionError[T <: Template](forType: T, startingWith: T, in: T)\n    extends CompilationError[T] {\n\n  override def toString = s\"Diverging implicit expansion error: for type $forType starting with $startingWith in $in\"\n\n  override def toJson =\n    JObject(\n      TypeField      -> JString(\"divergingImplicitExpansion\"),\n      \"forType\"      -> JString(forType.v),\n      \"startingWith\" -> JString(startingWith.v),\n      \"in\"           -> JString(in.v)\n    )\n\n  override def matches(other: CompilationError[ExactT])(implicit ev: T =:= RegexT) = other match {\n    case DivergingImplicitExpansionError(f, s, i) => forType.matches(f) && startingWith.matches(s) && in.matches(i)\n    case _                                        => false\n  }\n\n  override def asRegex(implicit ev: T =:= ExactT) =\n    DivergingImplicitExpansionError(\n      RegexT.fromPattern(forType.v),\n      RegexT.fromPattern(startingWith.v),\n      RegexT.fromPattern(in.v)\n    )\n}\n\ncase class TypeArgumentsDoNotConformToOverloadedBoundsError[T <: Template](\n    typeArgs: T,\n    alternativesOf: T,\n    alternatives: Set[T]\n) extends CompilationError[T] {\n\n  override def toString =\n    s\"Type arguments: $typeArgs for overloaded: $alternativesOf do not conform to any bounds: ${alternatives.map(_.toString).mkString(\" <or> \")}\"\n\n  override def toJson =\n    JObject(\n      TypeField        -> JString(\"typeArgumentsDoNotConformToOverloadedBounds\"),\n      \"typeArgs\"       -> JString(typeArgs.v),\n      \"alternativesOf\" -> JString(alternativesOf.v),\n      \"alternatives\"   -> JArray(alternatives.map(a => JString(a.v)).toList)\n    )\n\n  override def matches(other: CompilationError[ExactT])(implicit ev: T =:= RegexT) = other match {\n    case TypeArgumentsDoNotConformToOverloadedBoundsError(t, af, a) =>\n      typeArgs.matches(t) &&\n        alternativesOf.matches(af) && RegexT.setMatches(alternatives.map(ev.apply), a)\n    case _ => false\n  }\n\n  override def asRegex(implicit ev: T =:= ExactT) =\n    TypeArgumentsDoNotConformToOverloadedBoundsError(\n      RegexT.fromPattern(typeArgs.v),\n      RegexT.fromPattern(alternativesOf.v),\n      alternatives.map(a => RegexT.fromPattern(a.v))\n    )\n}\n\nobject CompilationError {\n  val TypeField = \"type\"\n\n  def fromJsonString(s: String): Option[CompilationError[RegexT]] = fromJson(parse(s))\n\n  def fromJson(jvalue: JValue): Option[CompilationError[RegexT]] = {\n\n    def regexTFromJson(fields: List[JField], name: String): Option[RegexT] =\n      (for {\n        JField(`name`, JString(v)) <- fields\n      } yield RegexT.fromRegex(v)).headOption\n\n    def multipleRegexTFromJson(fields: List[JField], name: String): Option[Set[RegexT]] =\n      (for {\n        JField(`name`, JArray(vv)) <- fields\n      } yield vv.collect { case JString(v) => RegexT.fromRegex(v) }.toSet).headOption\n\n    def extractWithType(typeValue: String, fields: List[JField]): Option[CompilationError[RegexT]] = typeValue match {\n      case \"typeMismatch\" =>\n        for {\n          found <- regexTFromJson(fields, \"found\")\n          foundExpandsTo = regexTFromJson(fields, \"foundExpandsTo\")\n          required <- regexTFromJson(fields, \"required\")\n          requiredExpandsTo = regexTFromJson(fields, \"requiredExpandsTo\")\n        } yield TypeMismatchError(found, foundExpandsTo, required, requiredExpandsTo, None)\n\n      case \"notFound\" =>\n        for {\n          what <- regexTFromJson(fields, \"what\")\n        } yield NotFoundError(what)\n\n      case \"notAMember\" =>\n        for {\n          what         <- regexTFromJson(fields, \"what\")\n          notAMemberOf <- regexTFromJson(fields, \"notAMemberOf\")\n        } yield NotAMemberError(what, notAMemberOf)\n\n      case \"implicitNotFound\" =>\n        for {\n          parameter    <- regexTFromJson(fields, \"parameter\")\n          implicitType <- regexTFromJson(fields, \"implicitType\")\n        } yield ImplicitNotFoundError(parameter, implicitType)\n\n      case \"divergingImplicitExpansion\" =>\n        for {\n          forType      <- regexTFromJson(fields, \"forType\")\n          startingWith <- regexTFromJson(fields, \"startingWith\")\n          in           <- regexTFromJson(fields, \"in\")\n        } yield DivergingImplicitExpansionError(forType, startingWith, in)\n\n      case \"typeArgumentsDoNotConformToOverloadedBounds\" =>\n        for {\n          typeArgs       <- regexTFromJson(fields, \"typeArgs\")\n          alternativesOf <- regexTFromJson(fields, \"alternativesOf\")\n          alternatives   <- multipleRegexTFromJson(fields, \"alternatives\")\n        } yield TypeArgumentsDoNotConformToOverloadedBoundsError(typeArgs, alternativesOf, alternatives)\n\n      case \"typeclassNotFound\" =>\n        for {\n          typeclass <- regexTFromJson(fields, \"typeclass\")\n          forType   <- regexTFromJson(fields, \"forType\")\n        } yield TypeclassNotFoundError(typeclass, forType)\n\n      case _ => None\n    }\n\n    (for {\n      JObject(fields)                       <- jvalue\n      JField(TypeField, JString(typeValue)) <- fields\n      v                                     <- extractWithType(typeValue, fields).toList\n    } yield v).headOption\n  }\n}\n"
  },
  {
    "path": "model/src/main/scala/com/softwaremill/clippy/CompilationErrorParser.scala",
    "content": "package com.softwaremill.clippy\n\nimport java.util.regex.Pattern\n\nobject CompilationErrorParser {\n  private val FoundRegexp            = \"\"\"found\\s*:\\s*([^\\n]+)\"\"\".r\n  private val RequiredPrefixRegexp   = \"\"\"required\\s*:\"\"\".r\n  private val AfterRequiredRegexp    = \"\"\"required\\s*:\\s*([^\\n]+)\"\"\".r\n  private val WhichExpandsToRegexp   = \"\"\"\\s*\\(which expands to\\)\\s*([^\\n]+)\"\"\".r\n  private val NotFoundRegexp         = \"\"\"not found\\s*:\\s*([^\\n]+)\"\"\".r\n  private val NotAMemberRegexp       = \"\"\":?\\s*([^\\n:]+) is not a member of\"\"\".r\n  private val NotAMemberOfRegexp     = \"\"\"is not a member of\\s*([^\\n]+)\"\"\".r\n  private val ImplicitNotFoundRegexp = \"\"\"could not find implicit value for parameter\\s*([^:]+):\\s*([^\\n]+)\"\"\".r\n  private val DivergingImplicitExpansionRegexp =\n    \"\"\"diverging implicit expansion for type\\s*([^\\s]+)\\s*.*\\s*starting with method\\s*([^\\s]+)\\s*in\\s*([^\\n]+)\"\"\".r\n  private val TypeArgumentsDoNotConformToOverloadedBoundsRegexp =\n    \"\"\"type arguments \\[([^\\]]+)\\] conform to the bounds of none of the overloaded alternatives of\\s*([^:\\n]+)[^:]*: ([^\\n]+)\"\"\".r\n  private val TypeclassNotFoundRegexp = \"\"\"No implicit (.*) defined for ([^\\n]+)\"\"\".r\n\n  def parse(e: String): Option[CompilationError[ExactT]] = {\n    val error = e.replaceAll(Pattern.quote(\"[error]\"), \"\")\n    if (error.contains(\"type mismatch\")) {\n      RequiredPrefixRegexp.split(error).toList match {\n        case List(beforeReq, afterReq) =>\n          for {\n            found <- FoundRegexp.findFirstMatchIn(beforeReq)\n            foundExpandsTo = WhichExpandsToRegexp.findFirstMatchIn(beforeReq)\n            required <- AfterRequiredRegexp.findFirstMatchIn(error)\n            requiredExpandsTo = WhichExpandsToRegexp.findFirstMatchIn(afterReq)\n          } yield {\n            val notes = requiredExpandsTo match {\n              case Some(et) => getNotesFromIndex(afterReq, et.end)\n              case None     => getNotesFromIndex(error, required.end)\n            }\n\n            TypeMismatchError[ExactT](\n              ExactT(found.group(1)),\n              foundExpandsTo.map(m => ExactT(m.group(1))),\n              ExactT(required.group(1)),\n              requiredExpandsTo.map(m => ExactT(m.group(1))),\n              notes\n            )\n          }\n\n        case _ =>\n          None\n      }\n    } else if (error.contains(\"not found\")) {\n      for {\n        what <- NotFoundRegexp.findFirstMatchIn(error)\n      } yield NotFoundError[ExactT](ExactT(what.group(1)))\n    } else if (error.contains(\"is not a member of\")) {\n      for {\n        what         <- NotAMemberRegexp.findFirstMatchIn(error)\n        notAMemberOf <- NotAMemberOfRegexp.findFirstMatchIn(error)\n      } yield NotAMemberError[ExactT](ExactT(what.group(1)), ExactT(notAMemberOf.group(1)))\n    } else if (error.contains(\"could not find implicit value for parameter\")) {\n      for {\n        inf <- ImplicitNotFoundRegexp.findFirstMatchIn(error)\n      } yield ImplicitNotFoundError[ExactT](ExactT(inf.group(1)), ExactT(inf.group(2)))\n    } else if (error.contains(\"diverging implicit expansion for type\")) {\n      for {\n        inf <- DivergingImplicitExpansionRegexp.findFirstMatchIn(error)\n      } yield DivergingImplicitExpansionError[ExactT](ExactT(inf.group(1)), ExactT(inf.group(2)), ExactT(inf.group(3)))\n    } else if (error.contains(\"conform to the bounds of none of the overloaded alternatives\")) {\n      for {\n        inf <- TypeArgumentsDoNotConformToOverloadedBoundsRegexp.findFirstMatchIn(error)\n      } yield\n        TypeArgumentsDoNotConformToOverloadedBoundsError[ExactT](\n          ExactT(inf.group(1)),\n          ExactT(inf.group(2)),\n          inf.group(3).split(Pattern.quote(\" <and> \")).toSet.map(ExactT.apply)\n        )\n    } else if (error.contains(\"No implicit\")) {\n      for {\n        inf <- TypeclassNotFoundRegexp.findFirstMatchIn(error)\n        group2 = inf.group(2)\n      } yield\n        TypeclassNotFoundError(\n          ExactT(inf.group(1)),\n          ExactT(if (group2.endsWith(\".\")) group2.substring(0, group2.length - 1) else group2)\n        )\n    } else None\n  }\n\n  private def getNotesFromIndex(msg: String, afterIdx: Int): Option[String] = {\n    val fromIdx = afterIdx + 1\n    if (msg.length >= fromIdx + 1) {\n      val notes = msg.substring(fromIdx).trim\n      if (notes == \"\") None else Some(notes)\n    } else None\n  }\n}\n"
  },
  {
    "path": "model/src/main/scala/com/softwaremill/clippy/Library.scala",
    "content": "package com.softwaremill.clippy\n\nimport org.json4s.JsonAST.{JField, JObject, JString, JValue}\n\ncase class Library(groupId: String, artifactId: String, version: String) {\n  def toJson: JValue = JObject(\n    \"groupId\"    -> JString(groupId),\n    \"artifactId\" -> JString(artifactId),\n    \"version\"    -> JString(version)\n  )\n\n  override def toString = s\"$groupId:$artifactId:$version\"\n}\n\nobject Library {\n  def fromJson(jvalue: JValue): Option[Library] =\n    (for {\n      JObject(fields)                           <- jvalue\n      JField(\"groupId\", JString(groupId))       <- fields\n      JField(\"artifactId\", JString(artifactId)) <- fields\n      JField(\"version\", JString(version))       <- fields\n    } yield Library(groupId, artifactId, version)).headOption\n}\n"
  },
  {
    "path": "model/src/main/scala/com/softwaremill/clippy/StringDiff.scala",
    "content": "package com.softwaremill.clippy\n\nobject StringDiff {\n  val separators                       = List(' ', ',', '(', ')', '[', ']', '#', '#', '=', '>', '{', '.')\n  def isSeparator(char: Char): Boolean = separators.contains(char)\n}\n\nclass StringDiff(expected: String, actual: String, color: String => String) {\n  import StringDiff._\n  def diff(message: String): String =\n    if (this.expected == this.actual) format(message, this.expected, this.actual)\n    else format(message, markDiff(expected), markDiff(actual))\n\n  private def format(msg: String, expected: String, actual: String) = msg.format(expected, actual)\n\n  private def markDiff(source: String) = {\n    val prefix = findCommonPrefix()\n    val suffix = findCommonSuffix()\n    if (overlappingPrefixSuffix(source, prefix, suffix))\n      source\n    else {\n      val diff = color(source.substring(prefix.length, source.length - suffix.length))\n      prefix + diff + suffix\n    }\n  }\n\n  private def overlappingPrefixSuffix(source: String, prefix: String, suffix: String) =\n    prefix.length + suffix.length >= source.length\n\n  def findCommonPrefix(expectedStr: String = expected, actualStr: String = actual): String = {\n    val prefixChars = expectedStr.zip(actualStr).takeWhile(Function.tupled(_ == _)).map(_._1)\n\n    val lastSeparatorIndex = prefixChars.lastIndexWhere(isSeparator)\n    val prefixEndIndex     = if (lastSeparatorIndex == -1) 0 else lastSeparatorIndex + 1\n\n    if (prefixChars.nonEmpty && prefixEndIndex < prefixChars.length)\n      prefixChars.mkString.substring(0, prefixEndIndex)\n    else {\n      prefixChars.mkString\n    }\n  }\n\n  def findCommonSuffix(): String =\n    findCommonPrefix(expected.reverse, actual.reverse).reverse\n}\n"
  },
  {
    "path": "model/src/main/scala/com/softwaremill/clippy/Template.scala",
    "content": "package com.softwaremill.clippy\n\nimport java.util.regex.Pattern\n\nimport scala.util.Try\nimport scala.util.matching.Regex\n\nsealed trait Template {\n  def v: String\n}\n\ncase class ExactT(v: String) extends Template {\n  override def toString = v\n}\n\ncase class RegexT(v: String) extends Template {\n  lazy val regex                  = Try(new Regex(v)).getOrElse(new Regex(\"^$\"))\n  def matches(e: ExactT): Boolean = regex.pattern.matcher(e.v).matches()\n  override def toString           = v\n}\nobject RegexT {\n\n  /**\n    * Patterns can include wildcards (`*`)\n    */\n  def fromPattern(pattern: String): RegexT = {\n    val regexp = pattern\n      .split(\"\\\\*\", -1)\n      .map(el => if (el != \"\") Pattern.quote(el) else el)\n      .flatMap(el => List(\".*\", el))\n      .tail\n      .filter(_.nonEmpty)\n      .mkString(\"\")\n\n    RegexT.fromRegex(regexp)\n  }\n\n  def fromRegex(v: String): RegexT =\n    new RegexT(v)\n\n  def setMatches(rr: Set[RegexT], ee: Set[ExactT]): Boolean =\n    if (rr.size != ee.size) false\n    else {\n      rr.toList.forall { r =>\n        ee.exists(r.matches)\n      }\n    }\n}\n"
  },
  {
    "path": "model/src/main/scala/com/softwaremill/clippy/Warning.scala",
    "content": "package com.softwaremill.clippy\n\nimport org.json4s.JsonAST._\n\nfinal case class Warning(pattern: RegexT, text: Option[String]) {\n  def toJson: JValue =\n    JObject(\n      \"pattern\" -> JString(pattern.toString)\n    ) ++ text.map(t => JObject(\"text\" -> JString(t))).getOrElse(JNothing)\n}\n\nobject Warning {\n  def fromJson(jvalue: JValue): Option[Warning] =\n    (for {\n      JObject(fields)                        <- jvalue\n      JField(\"pattern\", JString(patternStr)) <- fields\n      pattern = RegexT.fromRegex(patternStr)\n\n    } yield {\n      val text = jvalue.findField {\n        case ((\"text\", _)) => true\n        case _             => false\n      } match {\n        case Some((_, JString(textStr))) => Some(textStr)\n        case _                           => None\n      }\n      Warning(pattern, text)\n    }).headOption\n}\n"
  },
  {
    "path": "model/src/test/scala/com/softwaremill/clippy/CompilationErrorParserTest.scala",
    "content": "package com.softwaremill.clippy\n\nimport org.scalatest.{FlatSpec, Matchers}\n\nclass CompilationErrorParserTest extends FlatSpec with Matchers {\n  it should \"parse akka's route error message\" in {\n    val e =\n      \"\"\"type mismatch;\n        | found   : akka.http.scaladsl.server.StandardRoute\n        | required: akka.stream.scaladsl.Flow[akka.http.scaladsl.model.HttpRequest,akka.http.scaladsl.model.HttpResponse,Any]\"\"\".stripMargin\n\n    CompilationErrorParser.parse(e) should be(\n      Some(\n        TypeMismatchError(\n          ExactT(\"akka.http.scaladsl.server.StandardRoute\"),\n          None,\n          ExactT(\n            \"akka.stream.scaladsl.Flow[akka.http.scaladsl.model.HttpRequest,akka.http.scaladsl.model.HttpResponse,Any]\"\n          ),\n          None,\n          None\n        )\n      )\n    )\n  }\n\n  it should \"parse an error message with [error] prefix\" in {\n    val e =\n      \"\"\"[error] /Users/adamw/projects/clippy/tests/src/main/scala/com/softwaremill/clippy/Working.scala:16: type mismatch;\n        |[error]  found   : akka.http.scaladsl.server.StandardRoute\n        |[error]  required: akka.stream.scaladsl.Flow[akka.http.scaladsl.model.HttpRequest,akka.http.scaladsl.model.HttpResponse,Any]\"\"\".stripMargin\n\n    CompilationErrorParser.parse(e) should be(\n      Some(\n        TypeMismatchError(\n          ExactT(\"akka.http.scaladsl.server.StandardRoute\"),\n          None,\n          ExactT(\n            \"akka.stream.scaladsl.Flow[akka.http.scaladsl.model.HttpRequest,akka.http.scaladsl.model.HttpResponse,Any]\"\n          ),\n          None,\n          None\n        )\n      )\n    )\n  }\n\n  it should \"parse a type mismatch error with a single expands to section\" in {\n    val e =\n      \"\"\"type mismatch;\n        |found   : japgolly.scalajs.react.CompState.ReadCallbackWriteCallbackOps[com.softwaremill.clippy.Contribute.Step2.State]#This[com.softwaremill.clippy.FormField]\n        |required: japgolly.scalajs.react.CompState.AccessRD[?]\n        |   (which expands to)  japgolly.scalajs.react.CompState.ReadDirectWriteCallbackOps[?]\"\"\".stripMargin\n\n    CompilationErrorParser.parse(e) should be(\n      Some(\n        TypeMismatchError(\n          ExactT(\n            \"japgolly.scalajs.react.CompState.ReadCallbackWriteCallbackOps[com.softwaremill.clippy.Contribute.Step2.State]#This[com.softwaremill.clippy.FormField]\"\n          ),\n          None,\n          ExactT(\"japgolly.scalajs.react.CompState.AccessRD[?]\"),\n          Some(ExactT(\"japgolly.scalajs.react.CompState.ReadDirectWriteCallbackOps[?]\")),\n          None\n        )\n      )\n    )\n  }\n\n  it should \"parse a type mismatch error with two expands to sections\" in {\n    val e =\n      \"\"\"type mismatch;\n        |found   : japgolly.scalajs.react.CompState.ReadCallbackWriteCallbackOps[com.softwaremill.clippy.Contribute.Step2.State]#This[com.softwaremill.clippy.FormField]\n        |   (which expands to)  japgolly.scalajs.react.CompState.ReadCallbackWriteCallbackOps[com.softwaremill.clippy.FormField]\n        |required: japgolly.scalajs.react.CompState.AccessRD[?]\n        |   (which expands to)  japgolly.scalajs.react.CompState.ReadDirectWriteCallbackOps[?]\"\"\".stripMargin\n\n    CompilationErrorParser.parse(e) should be(\n      Some(\n        TypeMismatchError(\n          ExactT(\n            \"japgolly.scalajs.react.CompState.ReadCallbackWriteCallbackOps[com.softwaremill.clippy.Contribute.Step2.State]#This[com.softwaremill.clippy.FormField]\"\n          ),\n          Some(\n            ExactT(\"japgolly.scalajs.react.CompState.ReadCallbackWriteCallbackOps[com.softwaremill.clippy.FormField]\")\n          ),\n          ExactT(\"japgolly.scalajs.react.CompState.AccessRD[?]\"),\n          Some(ExactT(\"japgolly.scalajs.react.CompState.ReadDirectWriteCallbackOps[?]\")),\n          None\n        )\n      )\n    )\n  }\n\n  it should \"parse macwire's wire not found error message\" in {\n    val e = \"not found: value wire\"\n\n    CompilationErrorParser.parse(e) should be(Some(NotFoundError(ExactT(\"value wire\"))))\n  }\n\n  it should \"parse not a member of message\" in {\n    val e = \"value call is not a member of scala.concurrent.Future[Unit]\"\n\n    CompilationErrorParser.parse(e) should be(\n      Some(NotAMemberError(ExactT(\"value call\"), ExactT(\"scala.concurrent.Future[Unit]\")))\n    )\n  }\n\n  it should \"parse not a member of message with extra text\" in {\n    val e =\n      \"[error] /Users/adamw/projects/clippy/ui-client/src/main/scala/com/softwaremill/clippy/Listing.scala:33: value call is not a member of scala.concurrent.Future[Unit]\"\n\n    CompilationErrorParser.parse(e) should be(\n      Some(NotAMemberError(ExactT(\"value call\"), ExactT(\"scala.concurrent.Future[Unit]\")))\n    )\n  }\n\n  it should \"parse an implicit not found\" in {\n    val e =\n      \"could not find implicit value for parameter marshaller: spray.httpx.marshalling.ToResponseMarshaller[scala.concurrent.Future[String]]\"\n\n    CompilationErrorParser.parse(e) should be(\n      Some(\n        ImplicitNotFoundError(\n          ExactT(\"marshaller\"),\n          ExactT(\"spray.httpx.marshalling.ToResponseMarshaller[scala.concurrent.Future[String]]\")\n        )\n      )\n    )\n  }\n\n  it should \"parse a diverging implicit error \" in {\n    val e =\n      \"diverging implicit expansion for type io.circe.Decoder.Secondary[this.Out] starting with method decodeCaseClass in trait GenericInstances\"\n\n    CompilationErrorParser.parse(e) should be(\n      Some(\n        DivergingImplicitExpansionError(\n          ExactT(\"io.circe.Decoder.Secondary[this.Out]\"),\n          ExactT(\"decodeCaseClass\"),\n          ExactT(\"trait GenericInstances\")\n        )\n      )\n    )\n  }\n\n  it should \"parse a diverging implicit error with extra text\" in {\n    val e =\n      \"\"\"\n        |[error] /home/src/main/scala/Routes.scala:19: diverging implicit expansion for type io.circe.Decoder.Secondary[this.Out]\n        |[error] starting with method decodeCaseClass in trait GenericInstances\n      \"\"\".stripMargin\n\n    CompilationErrorParser.parse(e) should be(\n      Some(\n        DivergingImplicitExpansionError(\n          ExactT(\"io.circe.Decoder.Secondary[this.Out]\"),\n          ExactT(\"decodeCaseClass\"),\n          ExactT(\"trait GenericInstances\")\n        )\n      )\n    )\n  }\n\n  it should \"parse a type arguments do not conform to any overloaded bounds error\" in {\n    val e =\n      \"\"\"\n        |[error]  clippy/Working.scala:32: type arguments [org.softwaremill.clippy.User] conform to the bounds of none of the overloaded alternatives of\n        |value apply: [E <: slick.lifted.AbstractTable[_]]=> slick.lifted.TableQuery[E] <and> [E <: slick.lifted.AbstractTable[_]](cons: slick.lifted.Tag => E)slick.lifted.TableQuery[E]\n        |protected val users = TableQuery[User]\n      \"\"\".stripMargin\n\n    CompilationErrorParser.parse(e) should be(\n      Some(\n        TypeArgumentsDoNotConformToOverloadedBoundsError(\n          ExactT(\"org.softwaremill.clippy.User\"),\n          ExactT(\"value apply\"),\n          Set(\n            ExactT(\"[E <: slick.lifted.AbstractTable[_]]=> slick.lifted.TableQuery[E]\"),\n            ExactT(\"[E <: slick.lifted.AbstractTable[_]](cons: slick.lifted.Tag => E)slick.lifted.TableQuery[E]\")\n          )\n        )\n      )\n    )\n  }\n\n  it should \"parse a no implicit defined for\" in {\n    val e =\n      \"\"\"\n        |[error] /Users/clippy/model/src/main/scala/com/softwaremill/clippy/CompilationErrorParser.scala:18: No implicit Ordering defined for java.time.LocalDate.\n        |[error]   Seq(java.time.LocalDate.MIN, java.time.LocalDate.MAX).sorted\n      \"\"\".stripMargin\n\n    CompilationErrorParser.parse(e) should be(\n      Some(TypeclassNotFoundError(ExactT(\"Ordering\"), ExactT(\"java.time.LocalDate\")))\n    )\n  }\n\n  it should \"parse an error with notes\" in {\n    val e =\n      \"\"\"\n        |type mismatch;\n        | found   : org.softwaremill.clippy.ImplicitResolutionDiamond.C\n        | required: Array[String]\n        |Note that implicit conversions are not applicable because they are ambiguous:\n        | both method toMessage in object B of type (b: org.softwaremill.clippy.ImplicitResolutionDiamond.B)Array[String]\n        | and method toMessage in object A of type (a: org.softwaremill.clippy.ImplicitResolutionDiamond.A)Array[String]\n        | are possible conversion functions from org.softwaremill.clippy.ImplicitResolutionDiamond.C to Array[String]\n      \"\"\".stripMargin\n\n    CompilationErrorParser.parse(e) should be(\n      Some(\n        TypeMismatchError(\n          ExactT(\"org.softwaremill.clippy.ImplicitResolutionDiamond.C\"),\n          None,\n          ExactT(\"Array[String]\"),\n          None,\n          Some(\n            \"\"\"Note that implicit conversions are not applicable because they are ambiguous:\n             | both method toMessage in object B of type (b: org.softwaremill.clippy.ImplicitResolutionDiamond.B)Array[String]\n             | and method toMessage in object A of type (a: org.softwaremill.clippy.ImplicitResolutionDiamond.A)Array[String]\n             | are possible conversion functions from org.softwaremill.clippy.ImplicitResolutionDiamond.C to Array[String]\"\"\".stripMargin\n          )\n        )\n      )\n    )\n  }\n\n  it should \"parse an error with expands to & notes\" in {\n    val e =\n      \"\"\"\n        |type mismatch;\n        | found   : org.softwaremill.clippy.ImplicitResolutionDiamond.C\n        | required: Array[String]\n        |   (which expands to)  japgolly.scalajs.react.CompState.ReadDirectWriteCallbackOps[?]\n        |Note that implicit conversions are not applicable because they are ambiguous:\n        | both method toMessage in object B of type (b: org.softwaremill.clippy.ImplicitResolutionDiamond.B)Array[String]\n        | and method toMessage in object A of type (a: org.softwaremill.clippy.ImplicitResolutionDiamond.A)Array[String]\n        | are possible conversion functions from org.softwaremill.clippy.ImplicitResolutionDiamond.C to Array[String]\n      \"\"\".stripMargin\n\n    CompilationErrorParser.parse(e) should be(\n      Some(\n        TypeMismatchError(\n          ExactT(\"org.softwaremill.clippy.ImplicitResolutionDiamond.C\"),\n          None,\n          ExactT(\"Array[String]\"),\n          Some(ExactT(\"japgolly.scalajs.react.CompState.ReadDirectWriteCallbackOps[?]\")),\n          Some(\n            \"\"\"Note that implicit conversions are not applicable because they are ambiguous:\n             | both method toMessage in object B of type (b: org.softwaremill.clippy.ImplicitResolutionDiamond.B)Array[String]\n             | and method toMessage in object A of type (a: org.softwaremill.clippy.ImplicitResolutionDiamond.A)Array[String]\n             | are possible conversion functions from org.softwaremill.clippy.ImplicitResolutionDiamond.C to Array[String]\"\"\".stripMargin\n          )\n        )\n      )\n    )\n  }\n}\n"
  },
  {
    "path": "model/src/test/scala/com/softwaremill/clippy/CompilationErrorTest.scala",
    "content": "package com.softwaremill.clippy\n\nimport org.scalacheck.Prop._\nimport org.scalacheck.Properties\nimport org.scalatest.{FlatSpec, Matchers}\n\nclass CompilationErrorTest extends FlatSpec with Matchers {\n  it should \"do not match different exact not found errors\" in {\n    // given\n    val err = NotFoundError(RegexT.fromPattern(\"value wire[]\"))\n    val nonMatchingErrs = List(\n      NotFoundError(ExactT(\"value wirex\")),\n      NotFoundError(ExactT(\"value wir\")),\n      NotFoundError(ExactT(\"avalue wire\")),\n      NotFoundError(ExactT(\"hakuna matata\"))\n    )\n\n    // then\n    nonMatchingErrs.foreach(err.matches(_) should be(false))\n  }\n\n  it should \"match regex in not found errors\" in {\n    // given\n    val err = NotFoundError(RegexT.fromPattern(\"value wi*\"))\n    val matchingErrs = List(\n      NotFoundError(ExactT(\"value wire\")),\n      NotFoundError(ExactT(\"value wirex\")),\n      NotFoundError(ExactT(\"value wi\")),\n      NotFoundError(ExactT(\"value wire55\"))\n    )\n    val nonMatchingErrs = List(\n      NotFoundError(ExactT(\"avalue wire\")),\n      NotFoundError(ExactT(\"avalue w5\"))\n    )\n\n    // then\n    matchingErrs.foreach(err.matches(_) should be(true))\n    nonMatchingErrs.foreach(err.matches(_) should be(false))\n  }\n\n  it should \"not match different exact type mismatch errors\" in {\n    // given\n    val err = TypeMismatchError(\n      RegexT.fromPattern(\"com.softwaremill.String\"),\n      None,\n      RegexT.fromPattern(\"com.softwaremill.RequiredType[String]\"),\n      None,\n      None\n    )\n\n    val nonMatchingErrs = List(\n      TypeMismatchError(ExactT(\"com.softwaremill.String\"), None, ExactT(\"com.softwaremill.OtherType\"), None, None),\n      TypeMismatchError(ExactT(\"com.softwaremill.Int\"), None, ExactT(\"com.softwaremill.OtherType\"), None, None),\n      TypeMismatchError(\n        ExactT(\"com.softwaremill.Int\"),\n        None,\n        ExactT(\"com.softwaremill.RequiredType[String]\"),\n        None,\n        None\n      )\n    )\n\n    // then\n    nonMatchingErrs.foreach(err.matches(_) should be(false))\n  }\n\n  it should \"match regex in type mismatch errors\" in {\n    // given\n    val err = TypeMismatchError(\n      RegexT.fromPattern(\"slick.dbio.DBIOAction[*]\"),\n      None,\n      RegexT.fromPattern(\"slick.lifted.Rep[Option[*]]\"),\n      None,\n      None\n    )\n    val matchingErrs = List(\n      TypeMismatchError(\n        ExactT(\"slick.dbio.DBIOAction[Unit,slick.dbio.NoStream,slick.dbio.Effect.Write]\"),\n        None,\n        ExactT(\"slick.lifted.Rep[Option[?]]\"),\n        None,\n        Some(\"notes\")\n      ),\n      TypeMismatchError(\n        ExactT(\"slick.dbio.DBIOAction[String,slick.dbio.NoStream,slick.dbio.Effect.Read]\"),\n        None,\n        ExactT(\"slick.lifted.Rep[Option[Int]]\"),\n        None,\n        None\n      ),\n      TypeMismatchError(\n        ExactT(\"slick.dbio.DBIOAction[Unit,slick.dbio.NoStream,slick.dbio.Effect.Read]\"),\n        None,\n        ExactT(\"slick.lifted.Rep[Option[Option[Int]]\"),\n        None,\n        None\n      )\n    )\n    val nonMatchingErrs = List(\n      TypeMismatchError(\n        ExactT(\"slick.dbio.DBIOAction[Unit,slick.dbio.NoStream,slick.dbio.Effect.Read]\"),\n        None,\n        ExactT(\"String\"),\n        None,\n        Some(\"notes\")\n      ),\n      TypeMismatchError(ExactT(\"String\"), None, ExactT(\"slick.lifted.Rep[Option[?]]\"), None, None),\n      TypeMismatchError(ExactT(\"String\"), None, ExactT(\"com.softwaremill.AweSomeType\"), None, None)\n    )\n\n    // then\n    matchingErrs.foreach(err.matches(_) should be(true))\n    nonMatchingErrs.foreach(err.matches(_) should be(false))\n  }\n}\n\nclass CompilationErrorProperties extends Properties(\"CompilationError\") {\n  property(\"obj -> json -> obj works for type mismatch error\") = forAll {\n    (found: String, foundExpandsTo: Option[String], required: String, requiredExpandsTo: Option[String]) =>\n      val e = TypeMismatchError(\n        RegexT.fromPattern(found),\n        foundExpandsTo.map(RegexT.fromPattern),\n        RegexT.fromPattern(required),\n        requiredExpandsTo.map(RegexT.fromPattern),\n        None\n      )\n      CompilationError.fromJson(e.toJson).contains(e)\n  }\n\n  property(\"obj -> json -> obj works for not found error\") = forAll { (what: String) =>\n    val e = NotFoundError(RegexT.fromPattern(what))\n    CompilationError.fromJson(e.toJson).contains(e)\n  }\n\n  property(\"obj -> json -> obj works for not a member error\") = forAll { (what: String, notAMemberOf: String) =>\n    val e = NotAMemberError(RegexT.fromPattern(what), RegexT.fromPattern(notAMemberOf))\n    CompilationError.fromJson(e.toJson).contains(e)\n  }\n\n  property(\"obj -> json -> obj works for implicit not found\") = forAll { (parameter: String, implicitType: String) =>\n    val e = ImplicitNotFoundError(RegexT.fromPattern(parameter), RegexT.fromPattern(implicitType))\n    CompilationError.fromJson(e.toJson).contains(e)\n  }\n\n  property(\"obj -> json -> obj works for diverging implicit expansions\") = forAll {\n    (forType: String, startingWith: String, in: String) =>\n      val e = DivergingImplicitExpansionError(\n        RegexT.fromPattern(forType),\n        RegexT.fromPattern(startingWith),\n        RegexT.fromPattern(in)\n      )\n      CompilationError.fromJson(e.toJson).contains(e)\n  }\n\n  property(\"obj -> json -> obj works for type arguments do not conform to overloaded bounds\") = forAll {\n    (typeArgs: String, alternativesOf: String, alternatives: Set[String]) =>\n      val e = TypeArgumentsDoNotConformToOverloadedBoundsError(\n        RegexT.fromPattern(typeArgs),\n        RegexT.fromPattern(alternativesOf),\n        alternatives.map(a => RegexT.fromPattern(a))\n      )\n      CompilationError.fromJson(e.toJson).contains(e)\n  }\n\n  property(\"match identical not found error\") = forAll { (what: String) =>\n    NotFoundError(RegexT.fromPattern(what)).matches(NotFoundError(ExactT(what)))\n  }\n\n  property(\"matches identical type mismatch error\") = forAll {\n    (found: String, required: String, notes: Option[String]) =>\n      TypeMismatchError(RegexT.fromPattern(found), None, RegexT.fromPattern(required), None, None)\n        .matches(TypeMismatchError(ExactT(found), None, ExactT(required), None, notes))\n  }\n}\n"
  },
  {
    "path": "model/src/test/scala/com/softwaremill/clippy/LibraryProperties.scala",
    "content": "package com.softwaremill.clippy\n\nimport org.scalacheck.Prop._\nimport org.scalacheck.Properties\n\nclass LibraryProperties extends Properties(\"Library\") {\n  property(\"obj -> json -> obj\") = forAll { (gid: String, aid: String, v: String) =>\n    val l = Library(gid, aid, v)\n    Library.fromJson(l.toJson).contains(l)\n  }\n}\n"
  },
  {
    "path": "model/src/test/scala/com/softwaremill/clippy/RegexTTest.scala",
    "content": "package com.softwaremill.clippy\n\nimport org.scalatest.{FlatSpec, Matchers}\n\nclass RegexTTest extends FlatSpec with Matchers {\n  val matchingTests = List(\n    (\"slick.dbio.DBIOAction[*]\", \"slick.dbio.DBIOAction[Unit,slick.dbio.NoStream,slick.dbio.Effect.Write]\"),\n    (\"slick.dbio.DBIOAction[Unit,*,*]\", \"slick.dbio.DBIOAction[Unit,slick.dbio.NoStream,slick.dbio.Effect.Write]\"),\n    (\n      \"slick.dbio.DBIOAction[*,slick.dbio.NoStream,*]\",\n      \"slick.dbio.DBIOAction[Unit,slick.dbio.NoStream,slick.dbio.Effect.Write]\"\n    )\n  )\n\n  val nonMatchingTests = List(\n    (\"slick.dbio.DBIOAction[*]\", \"scala.concurrent.Future[Unit]\"),\n    (\"slick.dbio.DBIOAction[Unit,*,*]\", \"slick.dbio.DBIOAction[String,slick.dbio.NoStream,slick.dbio.Effect.Write]\"),\n    (\n      \"slick.dbio.DBIOAction[*,slick.dbio.NoStream,*]\",\n      \"slick.dbio.DBIOAction[Unit,slick.dbio.Stream,slick.dbio.Effect.Write]\"\n    )\n  )\n\n  for ((pattern, test) <- matchingTests) {\n    pattern should s\"match $test\" in {\n      RegexT.fromPattern(pattern).matches(ExactT(test)) should be(true)\n    }\n  }\n\n  for ((pattern, test) <- nonMatchingTests) {\n    pattern should s\"not match $test\" in {\n      RegexT.fromPattern(pattern).matches(ExactT(test)) should be(false)\n    }\n  }\n}\n"
  },
  {
    "path": "model/src/test/scala/com/softwaremill/clippy/StringDiffSpecification.scala",
    "content": "package com.softwaremill.clippy\n\nimport org.scalacheck.Prop.forAll\nimport org.scalacheck.Properties\n\nclass StringDiffSpecification extends Properties(\"StringDiff\") with TypeNamesGenerators {\n\n  val S     = \"S\"\n  val E     = \"E\"\n  val AddSE = (s: String) => S + s + E\n\n  def innerTypeDiffsCorrectly(fourTypes: List[String]): Boolean = {\n    val List(x, y, v, z) = fourTypes\n    val expected         = s\"$x[$y[$z]]\"\n    val actual           = s\"$x[$v[$z]]\"\n    val msg              = new StringDiff(expected, actual, AddSE).diff(\"expected: %s actual: %s\")\n    msg == s\"\"\"expected: $x[$S$y$E[$z]] actual: $x[$S$v$E[$z]]\"\"\"\n  }\n\n  def twoTypesAreFullyDiff(twoTypes: List[String]): Boolean = {\n    val List(x, y) = twoTypes\n    new StringDiff(x, y, AddSE).diff(\"expected: %s actual: %s\") == s\"\"\"expected: $S$x$E actual: $S$y$E\"\"\"\n  }\n\n  property(\"X[Y[Z]] vs X[V[Z]] always gives X[<diff>[Z]] excluding packages\") =\n    forAll(different(singleTypeName)(4))(innerTypeDiffsCorrectly)\n\n  property(\"X[Y[Z]] vs X[V[Z]] always gives X[<diff>[Z]] if Y and V have common prefix\") =\n    forAll(typesWithCommonPrefix(4))(innerTypeDiffsCorrectly)\n\n  property(\"X[Y[Z]] vs X[V[Z]] always gives X[<diff>[Z]] if Y and V have common suffix\") =\n    forAll(typesWithCommonSuffix(4))(innerTypeDiffsCorrectly)\n\n  property(\"A[X] vs B[X] always marks outer as diff for A != B when A and B have common prefix\") =\n    forAll(typesWithCommonPrefix(2), complexTypeName(maxDepth = 3)) { (outerTypes, x) =>\n      val List(a, b) = outerTypes\n      val expected   = s\"$a[$x]\"\n      val actual     = s\"$b[$x]\"\n      val msg        = new StringDiff(expected, actual, AddSE).diff(\"expected: %s actual: %s\")\n      msg == s\"\"\"expected: $S$a$E[$x] actual: $S$b$E[$x]\"\"\"\n    }\n\n  property(\"package.A[X] vs package.B[X] always gives package.<diff>A</diff>[X]\") =\n    forAll(javaPackage, different(singleTypeName)(2), complexTypeName(maxDepth = 3)) { (pkg, outerTypes, x) =>\n      val List(a, b) = outerTypes\n      val expected   = s\"$pkg$a[$x]\"\n      val actual     = s\"$pkg$b[$x]\"\n      val msg        = new StringDiff(expected, actual, AddSE).diff(\"expected: %s actual: %s\")\n      msg == s\"\"\"expected: $pkg$S$a$E[$x] actual: $pkg$S$b$E[$x]\"\"\"\n    }\n\n  property(\"any complex X vs Y is a full diff when X and Y don't have common suffix nor prefix\") =\n    forAll(different(complexTypeName(maxDepth = 4))(2).suchThat(noCommonPrefixSuffix))(twoTypesAreFullyDiff)\n\n  property(\"any single X vs Y is a full diff\") = forAll(different(singleTypeName)(2))(twoTypesAreFullyDiff)\n\n  def noCommonPrefixSuffix(twoTypes: List[String]): Boolean = {\n    val List(x, y) = twoTypes\n    x.head != y.head && x.last != y.last\n  }\n\n}\n"
  },
  {
    "path": "model/src/test/scala/com/softwaremill/clippy/StringDiffTest.scala",
    "content": "package com.softwaremill.clippy\n\nimport org.scalatest.{FlatSpec, Matchers}\n\nclass StringDiffTest extends FlatSpec with Matchers {\n\n  val S     = \"S\"\n  val E     = \"E\"\n  val AddSE = (s: String) => S + s + E\n\n  val testData = List(\n    (\n      \"Super[String, String]\",\n      \"Super[Option[String], String]\",\n      \"expected Super[\" + S + \"String\" + E + \", String] but was Super[\" + S + \"Option[String]\" + E + \", String]\"\n    ),\n    (\n      \"Cool[String, String]\",\n      \"Super[Option[String], String]\",\n      \"expected \" + S + \"Cool[String\" + E + \", String] but was \" + S + \"Super[Option[String]\" + E + \", String]\"\n    ),\n    (\n      \"(String, String)\",\n      \"Super[Option[String], String]\",\n      \"expected \" + S + \"(String, String)\" + E + \" but was \" + S + \"Super[Option[String], String]\" + E\n    ),\n    (\n      \"Map[Long, Double]\",\n      \"Map[String, Double]\",\n      \"expected Map[\" + S + \"Long\" + E + \", Double] but was Map[\" + S + \"String\" + E + \", Double]\"\n    ),\n    (\n      \"(Int, Int, Float, Int, Char)\",\n      \"(Int, Int, Int, Char)\",\n      \"expected (Int, Int, \" + S + \"Float\" + E + \", Int, Char) but was (Int, Int, Int, Char)\"\n    )\n  )\n\n  \"StringDiff\" should \"diff\" in {\n    for ((expected, actual, expectedDiff) <- testData) {\n\n      val diff = new StringDiff(expected, actual, AddSE).diff(\"expected %s but was %s\")\n\n      diff should be(expectedDiff)\n    }\n  }\n\n  it should \"find common prefix\" in {\n    new StringDiff(\"Map[Long, Double]\", \"Map[String, Double]\", AddSE).findCommonPrefix() should be(\"Map[\")\n  }\n\n  it should \"find common suffix\" in {\n    new StringDiff(\"Map[Long, Double]\", \"Map[String, Double]\", AddSE).findCommonSuffix() should be(\", Double]\")\n  }\n}\n"
  },
  {
    "path": "model/src/test/scala/com/softwaremill/clippy/TypeNamesGenerators.scala",
    "content": "package com.softwaremill.clippy\n\nimport org.scalacheck.Gen\n\ntrait TypeNamesGenerators {\n\n  import Gen._\n\n  def typeNameChar = frequency((1, numChar), (3, Gen.const('_')), (14, alphaChar))\n\n  val specialTypeName = Gen.oneOf(List(\"Option\", \"Operation\", \"Op\", \"String\", \"Long\", \"Set\", \"Aux\"))\n\n  def javaPackage: Gen[String] =\n    for {\n      depth <- Gen.choose(0, 3)\n      names <- Gen.listOfN(depth, packageIdentifer)\n    } yield {\n      val str = names.filter(_.nonEmpty).mkString(\".\")\n      if (str.nonEmpty)\n        str + \".\"\n      else\n        str\n    }\n\n  def packageIdentifer: Gen[String] =\n    for {\n      c  <- alphaLowerChar\n      cs <- Gen.resize(7, listOf(alphaNumChar))\n    } yield {\n      (c :: cs).mkString\n    }\n\n  def randomTypeWithPackage: Gen[String] =\n    for {\n      p <- javaPackage\n      t <- randomTypeWithPackage\n    } yield p + t\n\n  def randomTypeName: Gen[String] =\n    for {\n      c  <- alphaChar\n      cs <- Gen.resize(7, listOf(typeNameChar))\n    } yield (c :: cs).mkString\n\n  def singleTypeName = frequency((3, randomTypeName), (7, specialTypeName))\n\n  def functionalTypeName(maxResultDepth: Int): Gen[String] =\n    for {\n      argsMemberCount <- Gen.choose(2, 4)\n      args            <- Gen.oneOf(singleTypeName, tupleTypeName(depth = 0, argsMemberCount))\n      result          <- complexTypeName(maxResultDepth)\n    } yield {\n      s\"$args => $result\"\n    }\n\n  def genericTypeName(depth: Int, memberCount: Int): Gen[String] =\n    for {\n      name       <- singleTypeName\n      innerNames <- Gen.listOfN(memberCount, if (depth == 0) singleTypeName else complexTypeName(depth - 1))\n    } yield s\"$name[${innerNames.mkString(\", \")}]\"\n\n  def tupleTypeName(depth: Int, memberCount: Int): Gen[String] =\n    for {\n      innerNames <- Gen.listOfN(memberCount, if (depth == 0) singleTypeName else complexTypeName(depth - 1))\n    } yield s\"(${innerNames.mkString(\", \")})\"\n\n  def different[T](gen: Gen[T]) =\n    (count: Int) =>\n      Gen.listOfN(count, gen).suchThat { list =>\n        list.distinct == list\n    }\n\n  def typesWithCommonPrefix =\n    (count: Int) =>\n      for {\n        types        <- different(singleTypeName)(count)\n        commonPrefix <- singleTypeName\n      } yield types.map(commonPrefix + _)\n\n  def typesWithCommonSuffix =\n    (count: Int) =>\n      for {\n        types        <- different(singleTypeName)(count)\n        commonSuffix <- singleTypeName\n      } yield types.map(_ + commonSuffix)\n\n  def flatTypeIdentifier =\n    for {\n      tupleMemberCount <- Gen.choose(2, 4)\n      generator <- Gen.oneOf(\n        Seq(singleTypeName, tupleTypeName(0, tupleMemberCount), innerTypeName(0), functionalTypeName(0))\n      )\n      typeStr <- generator\n    } yield typeStr\n\n  def deepTypeName(maxDepth: Int): Gen[String] =\n    for {\n      genericMemberCount <- Gen.choose(1, 3)\n      tupleMemberCount   <- Gen.choose(2, 4)\n      generator <- Gen.oneOf(\n        Seq(\n          singleTypeName,\n          tupleTypeName(maxDepth, tupleMemberCount),\n          genericTypeName(maxDepth, genericMemberCount),\n          innerTypeName(maxDepth),\n          functionalTypeName(maxDepth)\n        )\n      )\n      typeStr <- generator\n    } yield typeStr\n\n  def complexTypeName(maxDepth: Int): Gen[String] =\n    if (maxDepth == 0)\n      flatTypeIdentifier\n    else\n      deepTypeName(maxDepth)\n\n  def innerTypeName(maxDepth: Int): Gen[String] =\n    for {\n      outerName <- singleTypeName\n      innerType <- complexTypeName(maxDepth)\n    } yield s\"$outerName#$innerType\"\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"scala-clippy-site\",\n    \"dependencies\": {\n      \"jsdom\": \"9.12.0\"\n    }\n\n}\n"
  },
  {
    "path": "plugin/src/main/resources/scalac-plugin.xml",
    "content": "<plugin>\n    <name>clippy</name>\n    <classname>com.softwaremill.clippy.ClippyPlugin</classname>\n</plugin>"
  },
  {
    "path": "plugin/src/main/scala/com/softwaremill/clippy/AdviceLoader.scala",
    "content": "package com.softwaremill.clippy\n\nimport java.io._\nimport java.net.{HttpURLConnection, URL}\nimport java.util.zip.GZIPInputStream\n\nimport com.softwaremill.clippy.Utils._\n\nimport scala.concurrent.{ExecutionContext, Future}\nimport scala.io.Source\nimport scala.tools.nsc.Global\nimport scala.util.{Failure, Success, Try}\nimport scala.collection.JavaConverters._\n\nclass AdviceLoader(\n    global: Global,\n    url: String,\n    localStoreDir: File,\n    projectAdviceFile: Option[File],\n    localAdviceFiles: List[URL]\n)(implicit ec: ExecutionContext) {\n  private val OneDayMillis = 1000L * 60 * 60 * 24\n\n  private val localStore = new File(localStoreDir, \"clippy.json.gz\")\n\n  private lazy val resourcesAdvice: AdvicesAndWarnings =\n    localAdviceFiles.map(loadAdviceFromUrL).reduceOption(_ ++ _).getOrElse(AdvicesAndWarnings.empty)\n\n  private def loadAdviceFromUrL(url: URL): AdvicesAndWarnings =\n    TryWith(url.openStream())(inputStreamToClippy(_)) match {\n      case Success(clippyData) => AdvicesAndWarnings(clippyData.advices, clippyData.fatalWarnings)\n      case Failure(_) =>\n        global.inform(s\"Cannot load advice from ${url.getPath} : Ignoring.\")\n        AdvicesAndWarnings.empty\n    }\n\n  private lazy val projectAdvice: AdvicesAndWarnings =\n    projectAdviceFile.map(file => loadAdviceFromUrL(file.toURI.toURL)).getOrElse(AdvicesAndWarnings.empty)\n\n  def load(): Future[Clippy] = {\n    val localClippy = if (!localStore.exists()) {\n      fetchStoreParse()\n    } else {\n      val needsUpdate = System.currentTimeMillis() - localStore.lastModified() > OneDayMillis\n\n      // fetching in the background\n      val runningFetch = if (needsUpdate) {\n        Some(fetchStoreParseInBackground())\n      } else None\n\n      val localLoad = Try(loadLocally()) match {\n        case Success(v) => Future.successful(v)\n        case Failure(t) => Future.failed(t)\n      }\n\n      localLoad.map(bytes => inputStreamToClippy(decodeZippedBytes(bytes))).recoverWith {\n        case e: Exception =>\n          global.warning(s\"Cannot load advice from local store: $localStore. Trying to fetch from server\")\n          runningFetch.getOrElse(fetchStoreParse())\n      }\n    }\n\n    // Add in advice found in resources and project root\n    localClippy.map(\n      clippy =>\n        clippy.copy(\n          advices = (projectAdvice.advices ++ resourcesAdvice.advices ++ clippy.advices).distinct,\n          fatalWarnings =\n            (projectAdvice.fatalWarnings ++ resourcesAdvice.fatalWarnings ++ clippy.fatalWarnings).distinct\n      )\n    )\n  }\n\n  private def fetchStoreParse(): Future[Clippy] =\n    fetchCompressedJson()\n      .map { bytes =>\n        storeLocallyInBackground(bytes)\n        bytes\n      }\n      .map(bytes => inputStreamToClippy(decodeZippedBytes(bytes)))\n      .recover {\n        case e: Exception =>\n          global.inform(s\"Unable to load/store local Clippy advice due to: ${e.getMessage}\")\n          Clippy(ClippyBuildInfo.version, Nil, Nil)\n      }\n      .andThen { case Success(v) => v.checkPluginVersion(ClippyBuildInfo.version, println) }\n\n  private def fetchStoreParseInBackground(): Future[Clippy] = {\n    val f = fetchStoreParse()\n    f.onFailure {\n      case e: Exception => global.inform(s\"Cannot fetch data from $url due to: $e\")\n    }\n    f\n  }\n\n  private def fetchCompressedJson(): Future[Array[Byte]] = Future {\n    val u    = new URL(url)\n    val conn = u.openConnection().asInstanceOf[HttpURLConnection]\n\n    try {\n      conn.setRequestMethod(\"GET\")\n      inputStreamToBytes(conn.getInputStream)\n    } finally conn.disconnect()\n  }\n\n  private def decodeZippedBytes(bytes: Array[Byte]): GZIPInputStream = new GZIPInputStream(decodeUtf8Bytes(bytes))\n\n  private def decodeUtf8Bytes(bytes: Array[Byte]): ByteArrayInputStream = new ByteArrayInputStream(bytes)\n\n  private def inputStreamToClippy(byteStream: InputStream): Clippy = {\n    import org.json4s.native.JsonMethods._\n    val data = Source.fromInputStream(byteStream, \"UTF-8\").getLines().mkString(\"\\n\")\n    Clippy\n      .fromJson(parse(data))\n      .getOrElse(throw new IllegalArgumentException(\"Cannot deserialize Clippy data\"))\n  }\n\n  private def storeLocally(bytes: Array[Byte]): Unit = {\n    if (!localStoreDir.isDirectory && !localStoreDir.mkdir()) {\n      throw new IOException(s\"Cannot create directory $localStoreDir\")\n    }\n    TryWith(new FileOutputStream(localStore))(_.write(bytes)).get\n  }\n\n  private def storeLocallyInBackground(bytes: Array[Byte]): Unit =\n    Future {\n      runNonDaemon {\n        storeLocally(bytes)\n      }\n    }.onFailure {\n      case e: Exception => global.inform(s\"Cannot store data at $localStore due to: $e\")\n    }\n\n  private def loadLocally(source: File = localStore): Array[Byte] = inputStreamToBytes(new FileInputStream(source))\n}\n\nobject AdviceLoader {\n  val localFile = \"clippy.json.gz\"\n}\n"
  },
  {
    "path": "plugin/src/main/scala/com/softwaremill/clippy/ClippyPlugin.scala",
    "content": "package com.softwaremill.clippy\n\nimport java.io.File\nimport java.net.{URL, URLClassLoader}\nimport java.util.concurrent.TimeoutException\n\nimport scala.collection.JavaConverters._\nimport scala.concurrent.Await\nimport scala.concurrent.duration._\nimport scala.reflect.internal.util.Position\nimport scala.tools.nsc.Global\nimport scala.tools.nsc.plugins.Plugin\nimport scala.tools.nsc.plugins.PluginComponent\nimport scala.tools.util.PathResolver\n\nfinal case class AdvicesAndWarnings(advices: List[Advice], fatalWarnings: List[Warning]) {\n  def ++(other: AdvicesAndWarnings): AdvicesAndWarnings =\n    copy(advices = advices ++ other.advices, fatalWarnings = fatalWarnings ++ other.fatalWarnings)\n}\n\nobject AdvicesAndWarnings {\n  def empty: AdvicesAndWarnings = AdvicesAndWarnings(Nil, Nil)\n}\n\nclass ClippyPlugin(val global: Global) extends Plugin {\n\n  override val name: String = \"clippy\"\n\n  override val description: String = \"gives good advice\"\n\n  var url: String                         = \"\"\n  var colorsConfig: ColorsConfig          = ColorsConfig.Disabled\n  var testMode                            = false\n  val DefaultStoreDir                     = new File(System.getProperty(\"user.home\"), \".clippy\")\n  var localStoreDir                       = DefaultStoreDir\n  var projectRoot: Option[File]           = None\n  var initialFatalWarnings: List[Warning] = Nil\n\n  lazy val localAdviceFiles = {\n    val classPathURLs = new PathResolver(global.settings).result.asURLs\n    val classLoader   = new URLClassLoader(classPathURLs.toArray, getClass.getClassLoader)\n    classLoader.getResources(\"clippy.json\").asScala.toList\n  }\n\n  lazy val advicesAndWarnings =\n    loadAdvicesAndWarnings(url, localStoreDir, projectRoot, localAdviceFiles)\n\n  def getFatalWarningAdvice(warningText: String): Option[Warning] =\n    advicesAndWarnings.fatalWarnings.find(warning => warning.pattern.matches(ExactT(warningText)))\n\n  def handleError(pos: Position, msg: String): String = {\n    val parsedMsg = CompilationErrorParser.parse(msg)\n    val matchers  = advicesAndWarnings.advices.map(_.errMatching.lift)\n    val matches   = matchers.flatMap(pf => parsedMsg.flatMap(pf)).distinct\n\n    matches.size match {\n      case 0 =>\n        (parsedMsg, colorsConfig) match {\n          case (Some(tme: TypeMismatchError[ExactT]), cc: ColorsConfig.Enabled) =>\n            prettyPrintTypeMismatchError(tme, cc)\n          case _ => msg\n        }\n      case 1 =>\n        matches.mkString(s\"$msg\\n Clippy advises: \", \"\", \"\")\n      case _ =>\n        matches.mkString(s\"$msg\\n Clippy advises you to try one of these solutions: \\n   \", \"\\n or\\n   \", \"\")\n    }\n  }\n\n  override def processOptions(options: List[String], error: (String) => Unit): Unit = {\n    colorsConfig = colorsFromOptions(options)\n    url = urlFromOptions(options)\n    testMode = testModeFromOptions(options)\n    localStoreDir = localStoreDirFromOptions(options)\n    projectRoot = projectRootFromOptions(options)\n    initialFatalWarnings = initialFatalWarningsFromOptions(options)\n    if (testMode) {\n      val r = global.reporter\n      global.reporter = new FailOnWarningsReporter(\n        new DelegatingReporter(r, handleError, colorsConfig),\n        getFatalWarningAdvice,\n        colorsConfig\n      )\n    }\n  }\n\n  override val components: List[PluginComponent] = {\n\n    List(\n      new InjectReporter(handleError, getFatalWarningAdvice, global) {\n        override def colorsConfig = ClippyPlugin.this.colorsConfig\n        override def isEnabled    = !testMode\n      },\n      new RestoreReporter(global) {\n        override def isEnabled = !testMode\n      }\n    )\n  }\n\n  private def prettyPrintTypeMismatchError(tme: TypeMismatchError[ExactT], colors: ColorsConfig.Enabled): String = {\n    val colorDiff = (s: String) => colors.diff(s).toString\n    val plain     = new StringDiff(tme.found.toString, tme.required.toString, colorDiff)\n\n    val expandsMsg = if (tme.hasExpands) {\n      val reqExpandsTo   = tme.requiredExpandsTo.getOrElse(tme.required)\n      val foundExpandsTo = tme.foundExpandsTo.getOrElse(tme.found)\n      val expands        = new StringDiff(foundExpandsTo.toString, reqExpandsTo.toString, colorDiff)\n      s\"\"\"${expands.diff(\"\\nExpanded types:\\nfound   : %s\\nrequired: %s\\\"\")}\"\"\"\n    } else\n      \"\"\n\n    s\"\"\" type mismatch;\n         | Clippy advises, pay attention to the marked parts:\n         | ${plain.diff(\"found   : %s\\n required: %s\")}$expandsMsg${tme.notesAfterNewline}\"\"\".stripMargin\n  }\n\n  private def urlFromOptions(options: List[String]): String =\n    options.find(_.startsWith(\"url=\")).map(_.substring(4)).getOrElse(\"https://www.scala-clippy.org\") + \"/api/advices\"\n\n  private def colorsFromOptions(options: List[String]): ColorsConfig =\n    if (boolFromOptions(options, \"colors\")) {\n\n      def colorToFansi(color: String): fansi.Attrs = color match {\n        case \"black\"         => fansi.Color.Black\n        case \"light-gray\"    => fansi.Color.LightGray\n        case \"dark-gray\"     => fansi.Color.DarkGray\n        case \"red\"           => fansi.Color.Red\n        case \"light-red\"     => fansi.Color.LightRed\n        case \"green\"         => fansi.Color.Green\n        case \"light-green\"   => fansi.Color.LightGreen\n        case \"yellow\"        => fansi.Color.Yellow\n        case \"light-yellow\"  => fansi.Color.LightYellow\n        case \"blue\"          => fansi.Color.Blue\n        case \"light-blue\"    => fansi.Color.LightBlue\n        case \"magenta\"       => fansi.Color.Magenta\n        case \"light-magenta\" => fansi.Color.LightMagenta\n        case \"cyan\"          => fansi.Color.Cyan\n        case \"light-cyan\"    => fansi.Color.LightCyan\n        case \"white\"         => fansi.Color.White\n        case \"none\"          => fansi.Attrs.Empty\n        case x =>\n          global.warning(\"Unknown color: \" + x)\n          fansi.Attrs.Empty\n      }\n\n      val partColorPattern = \"colors-(.*)=(.*)\".r\n      options.filter(_.startsWith(\"colors-\")).foldLeft(ColorsConfig.defaultEnabled) {\n        case (current, partAndColor) =>\n          val partColorPattern(part, colorStr) = partAndColor\n          val color                            = colorToFansi(colorStr.trim.toLowerCase())\n          part.trim.toLowerCase match {\n            case \"diff\"    => current.copy(diff = color)\n            case \"comment\" => current.copy(comment = color)\n            case \"type\"    => current.copy(`type` = color)\n            case \"literal\" => current.copy(literal = color)\n            case \"keyword\" => current.copy(keyword = color)\n            case \"reset\"   => current.copy(reset = color)\n            case x =>\n              global.warning(\"Unknown colored part: \" + x)\n              current\n          }\n      }\n    } else ColorsConfig.Disabled\n\n  private def testModeFromOptions(options: List[String]): Boolean = boolFromOptions(options, \"testmode\")\n\n  private def boolFromOptions(options: List[String], option: String): Boolean =\n    options\n      .find(_.startsWith(s\"$option=\"))\n      .map(_.substring(option.length + 1))\n      .getOrElse(\"false\")\n      .toBoolean\n\n  private def projectRootFromOptions(options: List[String]): Option[File] =\n    options\n      .find(_.startsWith(\"projectRoot=\"))\n      .map(_.substring(12))\n      .map(new File(_, \".clippy.json\"))\n      .filter(_.exists())\n\n  private def initialFatalWarningsFromOptions(options: List[String]): List[Warning] =\n    options\n      .find(_.startsWith(\"fatalWarnings=\"))\n      .map(_.substring(14))\n      .map { str =>\n        str.split('|').toList.map(str => Warning(RegexT(str), text = None))\n      }\n      .getOrElse(Nil)\n\n  private def localStoreDirFromOptions(options: List[String]): File =\n    options.find(_.startsWith(\"store=\")).map(_.substring(6)).map(new File(_)).getOrElse(DefaultStoreDir)\n\n  private def loadAdvicesAndWarnings(\n      url: String,\n      localStoreDir: File,\n      projectAdviceFile: Option[File],\n      localAdviceFiles: List[URL]\n  ): AdvicesAndWarnings = {\n    implicit val ec = scala.concurrent.ExecutionContext.Implicits.global\n\n    try {\n      val clippyData = Await\n        .result(\n          new AdviceLoader(global, url, localStoreDir, projectAdviceFile, localAdviceFiles).load(),\n          10.seconds\n        )\n      AdvicesAndWarnings(clippyData.advices, clippyData.fatalWarnings ++ initialFatalWarnings)\n    } catch {\n      case e: TimeoutException =>\n        global.warning(s\"Unable to read advices from $url and store to $localStoreDir within 10 seconds.\")\n        AdvicesAndWarnings.empty\n      case e: Exception =>\n        global.warning(s\"Exception when reading advices from $url and storing to $localStoreDir: $e\")\n        AdvicesAndWarnings.empty\n    }\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/scala/com/softwaremill/clippy/ColorsConfig.scala",
    "content": "package com.softwaremill.clippy\n\nsealed trait ColorsConfig\n\nobject ColorsConfig {\n  case object Disabled extends ColorsConfig\n\n  case class Enabled(\n      diff: fansi.Attrs,\n      comment: fansi.Attrs,\n      `type`: fansi.Attrs,\n      literal: fansi.Attrs,\n      keyword: fansi.Attrs,\n      reset: fansi.Attrs\n  ) extends ColorsConfig\n\n  val defaultEnabled = Enabled(\n    fansi.Color.Red,\n    fansi.Color.Blue,\n    fansi.Color.Green,\n    fansi.Color.Magenta,\n    fansi.Color.Yellow,\n    fansi.Attr.Reset\n  )\n}\n"
  },
  {
    "path": "plugin/src/main/scala/com/softwaremill/clippy/FailOnWarningsReporter.scala",
    "content": "package com.softwaremill.clippy\n\nimport scala.reflect.internal.util.Position\nimport scala.tools.nsc.reporters.Reporter\n\nclass FailOnWarningsReporter(r: Reporter, warningMatcher: String => Option[Warning], colorsConfig: ColorsConfig)\n    extends Reporter {\n  override protected def info0(pos: Position, msg: String, severity: Severity, force: Boolean) = {\n    val wrapped = DelegatingPosition.wrap(pos, colorsConfig)\n\n    // cannot delegate to info0 as it's protected, hence special-casing on the possible severity values\n    if (severity == INFO) {\n      r.info(wrapped, msg, force)\n    } else if (severity == WARNING) {\n      warning(wrapped, msg)\n    } else if (severity == ERROR) {\n      error(wrapped, msg)\n    } else {\n      error(wrapped, s\"UNKNOWN SEVERITY: $severity\\n$msg\")\n    }\n  }\n\n  override def echo(msg: String)                   = r.echo(msg)\n  override def comment(pos: Position, msg: String) = r.comment(DelegatingPosition.wrap(pos, colorsConfig), msg)\n  override def hasErrors                           = r.hasErrors || cancelled\n  override def reset() = {\n    cancelled = false\n    r.reset()\n  }\n\n  //\n\n  override def echo(pos: Position, msg: String)     = r.echo(DelegatingPosition.wrap(pos, colorsConfig), msg)\n  override def errorCount                           = r.errorCount\n  override def warningCount                         = r.warningCount\n  override def hasWarnings                          = r.hasWarnings\n  override def flush()                              = r.flush()\n  override def count(severity: Severity): Int       = r.count(conv(severity))\n  override def resetCount(severity: Severity): Unit = r.resetCount(conv(severity))\n\n  //\n\n  private def conv(s: Severity): r.Severity = s match {\n    case INFO    => r.INFO\n    case WARNING => r.WARNING\n    case ERROR   => r.ERROR\n  }\n\n  //\n  override def warning(pos: Position, msg: String) = {\n    val wrapped = DelegatingPosition.wrap(pos, colorsConfig)\n    warningMatcher(msg) match {\n      case Some(Warning(_, adviceOpt)) =>\n        val finalMsg = adviceOpt.map(advice => msg + s\"\\nClippy advises: $advice\").getOrElse(msg)\n        r.error(wrapped, finalMsg)\n      case None =>\n        r.warning(wrapped, msg)\n    }\n  }\n\n  override def error(pos: Position, msg: String) = {\n    val wrapped = DelegatingPosition.wrap(pos, colorsConfig)\n    r.error(wrapped, msg)\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/scala/com/softwaremill/clippy/Highlighter.scala",
    "content": "package com.softwaremill.clippy\n\nimport fastparse.all._\n\nimport scalaparse.Scala._\nimport scalaparse.syntax.Identifiers._\n\n/**\n  * Copied from https://github.com/lihaoyi/Ammonite/blob/master/amm/repl/src/main/scala/ammonite/repl/Highlighter.scala\n  */\nobject Highlighter {\n\n  object BackTicked {\n    def unapplySeq(s: Any): Option[List[String]] =\n      \"`([^`]+)`\".r.unapplySeq(s.toString)\n  }\n\n  def flattenIndices(\n      boundedIndices: Seq[(Int, fansi.Attrs, Boolean)],\n      buffer: Vector[Char]\n  ) =\n    boundedIndices\n      .sliding(2)\n      .map {\n        case Seq((s, c1, _), (e, c2, _)) =>\n          assert(e >= s, s\"s: $s e: $e\")\n          c1(fansi.Str(buffer.slice(s, e), errorMode = fansi.ErrorMode.Sanitize))\n      }\n      .reduce(_ ++ _)\n      .render\n      .toVector\n\n  def defaultHighlight(\n      buffer: Vector[Char],\n      comment: fansi.Attrs,\n      `type`: fansi.Attrs,\n      literal: fansi.Attrs,\n      keyword: fansi.Attrs,\n      reset: fansi.Attrs\n  ) = {\n    val boundedIndices = defaultHighlightIndices(buffer, comment, `type`, literal, keyword, reset)\n    flattenIndices(boundedIndices, buffer)\n  }\n  def defaultHighlightIndices(\n      buffer: Vector[Char],\n      comment: fansi.Attrs,\n      `type`: fansi.Attrs,\n      literal: fansi.Attrs,\n      keyword: fansi.Attrs,\n      reset: fansi.Attrs\n  ) = Highlighter.highlightIndices(\n    Parsers.Splitter,\n    buffer, {\n      case Literals.Expr.Interp | Literals.Pat.Interp       => reset\n      case Literals.Comment                                 => comment\n      case ExprLiteral                                      => literal\n      case TypeId                                           => `type`\n      case BackTicked(body) if alphaKeywords.contains(body) => keyword\n    },\n    reset\n  )\n  def highlightIndices[T](\n      parser: Parser[_],\n      buffer: Vector[Char],\n      ruleColors: PartialFunction[Parser[_], T],\n      endColor: T\n  ): Seq[(Int, T, Boolean)] = {\n    val indices = {\n      var indices = collection.mutable.Buffer((0, endColor, false))\n      var done    = false\n      val input   = buffer.mkString\n      parser.parse(\n        input,\n        instrument = (rule, idx, res) => {\n          for (color <- ruleColors.lift(rule)) {\n            val closeColor = indices.last._2\n            val startIndex = indices.length\n            indices += ((idx, color, true))\n\n            res() match {\n              case s: Parsed.Success[_] =>\n                val prev = indices(startIndex - 1)._1\n\n                if (idx < prev && s.index <= prev) {\n                  indices.remove(startIndex, indices.length - startIndex)\n\n                }\n                while (idx < indices.last._1 && s.index <= indices.last._1) {\n                  indices.remove(indices.length - 1)\n                }\n                indices += ((s.index, closeColor, false))\n                if (s.index == buffer.length) done = true\n              case f: Parsed.Failure\n                  if f.index == buffer.length\n                    && (WL ~ End).parse(input, idx).isInstanceOf[Parsed.Failure] =>\n                // EOF, stop all further parsing\n                done = true\n              case _ => // hard failure, or parsed nothing. Discard all progress\n                indices.remove(startIndex, indices.length - startIndex)\n            }\n          }\n        }\n      )\n      indices\n    }\n    // Make sure there's an index right at the start and right at the end! This\n    // resets the colors at the snippet's end so they don't bleed into later output\n    indices ++ Seq((999999999, endColor, false))\n  }\n  def highlight(\n      parser: Parser[_],\n      buffer: Vector[Char],\n      ruleColors: PartialFunction[Parser[_], fansi.Attrs],\n      endColor: fansi.Attrs\n  ) = {\n    val boundedIndices = highlightIndices(parser, buffer, ruleColors, endColor)\n    flattenIndices(boundedIndices, buffer)\n  }\n\n}\n\nobject Parsers {\n  import fastparse.noApi._\n\n  import scalaparse.Scala._\n  import WhitespaceApi._\n\n  val Prelude = P((Annot ~ OneNLMax).rep ~ (Mod ~/ Pass).rep)\n  val Statement =\n    P(scalaparse.Scala.TopPkgSeq | scalaparse.Scala.Import | Prelude ~ BlockDef | StatCtx.Expr)\n  def StatementBlock(blockSep: P0) =\n    P(Semis.? ~ (!blockSep ~ Statement ~~ WS ~~ (Semis | End)).!.repX)\n  val Splitter = P(StatementBlock(Fail) ~ WL ~ End)\n}\n"
  },
  {
    "path": "plugin/src/main/scala/com/softwaremill/clippy/InjectReporter.scala",
    "content": "package com.softwaremill.clippy\n\nimport scala.reflect.internal.util.Position\nimport scala.tools.nsc.plugins.PluginComponent\nimport scala.tools.nsc.{Global, Phase}\n\n/**\n  * Responsible for replacing the global reporter with our custom Clippy reporter after the first phase of compilation.\n  */\nabstract class InjectReporter(\n    handleError: (Position, String) => String,\n    getFatalWarningAdvice: String => Option[Warning],\n    superGlobal: Global\n) extends PluginComponent {\n\n  override val global = superGlobal\n  def colorsConfig: ColorsConfig\n  def isEnabled: Boolean\n  override val runsAfter  = List[String](\"parser\")\n  override val runsBefore = List[String](\"namer\")\n  override val phaseName  = \"inject-clippy-reporter\"\n\n  override def newPhase(prev: Phase) = new Phase(prev) {\n\n    override def name = phaseName\n\n    override def description = \"Switches the reporter to Clippy's reporter chain\"\n\n    override def run(): Unit =\n      if (isEnabled) {\n        val r = global.reporter\n        global.reporter = new FailOnWarningsReporter(\n          new DelegatingReporter(r, handleError, colorsConfig),\n          getFatalWarningAdvice,\n          colorsConfig\n        )\n      }\n  }\n\n}\n"
  },
  {
    "path": "plugin/src/main/scala/com/softwaremill/clippy/RestoreReporter.scala",
    "content": "package com.softwaremill.clippy\n\nimport scala.tools.nsc.plugins.PluginComponent\nimport scala.tools.nsc.{Global, Phase}\n\n/**\n  * Replaces global reporter back with the original global reporter. Sbt uses its own xsbt.DelegatingReporter\n  * which we cannot replace outside of Scala compilation phases. This component makes sure that before the compilation\n  * is over, original reporter gets reassigned to the global field.\n  */\nclass RestoreReporter(val global: Global) extends PluginComponent {\n\n  val originalReporter    = global.reporter\n  def isEnabled: Boolean  = true\n  override val runsAfter  = List[String](\"jvm\")\n  override val runsBefore = List[String](\"terminal\")\n  override val phaseName  = \"restore-original-reporter\"\n\n  override def newPhase(prev: Phase) = new Phase(prev) {\n\n    override def name = phaseName\n\n    override def description = \"Switches the reporter from Clippy's DelegatingReporter back to original one\"\n\n    override def run(): Unit =\n      if (isEnabled)\n        global.reporter = originalReporter\n  }\n\n}\n"
  },
  {
    "path": "plugin/src/main/scala/com/softwaremill/clippy/Utils.scala",
    "content": "package com.softwaremill.clippy\n\nimport java.io.{ByteArrayOutputStream, InputStream}\nimport java.io.Closeable\nimport scala.util.control.NonFatal\nimport scala.util.{Failure, Try}\n\nobject Utils {\n\n  /**\n    * All future callbacks will be running on a daemon thread pool which can be interrupted at any time if the JVM\n    * exits, if the compiler finished its job.\n    *\n    * Here we are trying to make as sure as possible (unless the JVM crashes) that we'll run the given code.\n    */\n  def runNonDaemon(t: => Unit) = {\n    val shutdownHook = new Thread() {\n      private val lock             = new Object\n      @volatile private var didRun = false\n\n      override def run() =\n        lock.synchronized {\n          if (!didRun) {\n            t\n            didRun = true\n          }\n        }\n    }\n\n    Runtime.getRuntime.addShutdownHook(shutdownHook)\n    try shutdownHook.run()\n    finally Runtime.getRuntime.removeShutdownHook(shutdownHook)\n  }\n\n  def inputStreamToBytes(is: InputStream): Array[Byte] =\n    try {\n      val baos = new ByteArrayOutputStream()\n      val buf  = new Array[Byte](512)\n      var read = 0\n      while ({ read = is.read(buf, 0, buf.length); read } != -1) {\n        baos.write(buf, 0, read)\n      }\n      baos.toByteArray\n    } finally is.close()\n\n  object TryWith {\n    def apply[C <: Closeable, R](resource: => C)(f: C => R): Try[R] =\n      Try(resource).flatMap(resourceInstance => {\n        try {\n          val returnValue = f(resourceInstance)\n          Try(resourceInstance.close()).map(_ => returnValue)\n        } catch {\n          case NonFatal(exceptionInFunction) =>\n            try {\n              resourceInstance.close()\n              Failure(exceptionInFunction)\n            } catch {\n              case NonFatal(exceptionInClose) =>\n                exceptionInFunction.addSuppressed(exceptionInClose)\n                Failure(exceptionInFunction)\n            }\n        }\n      })\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/scala-2.11/com/softwaremill/clippy/DelegatingPosition.scala",
    "content": "package com.softwaremill.clippy\n\nimport scala.reflect.internal.util.{NoPosition, Position, SourceFile}\nimport scala.reflect.macros.Attachments\n\nclass DelegatingPosition(delegate: Position, colorsConfig: ColorsConfig) extends Position {\n\n  // used by scalac to report errors\n  override def showError(msg: String): String = highlight(delegate.showError(msg))\n\n  // used by sbt\n  override def lineContent: String = highlight(delegate.lineContent)\n\n  def highlight(str: String): String = colorsConfig match {\n    case e: ColorsConfig.Enabled =>\n      Highlighter\n        .defaultHighlight(\n          str.toVector,\n          e.comment,\n          e.`type`,\n          e.literal,\n          e.keyword,\n          e.reset\n        )\n        .mkString\n    case _ => str\n  }\n\n  // impl copied\n\n  override def fail(what: String): Nothing = throw new UnsupportedOperationException(s\"Position.$what on $this\")\n\n  // simple delegates with position wrapping when position is returned\n\n  @scala.deprecated(\"use `point`\")\n  override def offset: Option[Int] = delegate.offset\n\n  override def all: Set[Any] = delegate.all\n\n  @scala.deprecated(\"use `focus`\")\n  override def toSingleLine: Position = DelegatingPosition.wrap(delegate.toSingleLine, colorsConfig)\n\n  override def pos: Position = DelegatingPosition.wrap(delegate.pos, colorsConfig)\n\n  override def get[T](implicit evidence$2: ClassManifest[T]): Option[T] = delegate.get(evidence$2)\n\n  override def finalPosition: Pos = DelegatingPosition.wrap(delegate.finalPosition, colorsConfig)\n\n  override def withPos(newPos: Position): Attachments { type Pos = DelegatingPosition.this.Pos } =\n    delegate.withPos(newPos)\n\n  @scala.deprecated(\"use `line`\")\n  override def safeLine: Int = delegate.safeLine\n\n  override def isTransparent: Boolean = delegate.isTransparent\n\n  override def contains[T](implicit evidence$3: ClassManifest[T]): Boolean = delegate.contains(evidence$3)\n\n  override def isOffset: Boolean = delegate.isOffset\n\n  @scala.deprecated(\"use `showDebug`\")\n  override def dbgString: String = delegate.dbgString\n\n  override def isOpaqueRange: Boolean = delegate.isOpaqueRange\n\n  override def update[T](attachment: T)(\n      implicit evidence$4: ClassManifest[T]\n  ): Attachments { type Pos = DelegatingPosition.this.Pos } = delegate.update(attachment)(evidence$4)\n\n  override def pointOrElse(alt: Int): Int = delegate.pointOrElse(alt)\n\n  @scala.deprecated(\"use `finalPosition`\")\n  override def inUltimateSource(source: SourceFile): Position =\n    DelegatingPosition.wrap(delegate.inUltimateSource(source), colorsConfig)\n\n  override def isDefined: Boolean = delegate.isDefined\n\n  override def makeTransparent: Position = DelegatingPosition.wrap(delegate.makeTransparent, colorsConfig)\n\n  override def isRange: Boolean = delegate.isRange\n\n  override def source: SourceFile = delegate.source\n\n  override def remove[T](\n      implicit evidence$5: ClassManifest[T]\n  ): Attachments { type Pos = DelegatingPosition.this.Pos } = delegate.remove(evidence$5)\n\n  override def withStart(start: Int): Position = DelegatingPosition.wrap(delegate.withStart(start), colorsConfig)\n\n  @scala.deprecated(\"use `lineCaret`\")\n  override def lineWithCarat(maxWidth: Int): (String, String) = delegate.lineWithCarat(maxWidth)\n\n  override def start: Int = delegate.start\n\n  override def withPoint(point: Int): Position = DelegatingPosition.wrap(delegate.withPoint(point), colorsConfig)\n\n  override def point: Int = delegate.point\n\n  override def isEmpty: Boolean = delegate.isEmpty\n\n  override def end: Int = delegate.end\n\n  override def withEnd(end: Int): Position = DelegatingPosition.wrap(delegate.withEnd(end), colorsConfig)\n\n  @scala.deprecated(\"Use `withSource(source)` and `withShift`\")\n  override def withSource(source: SourceFile, shift: Int): Position =\n    DelegatingPosition.wrap(delegate.withSource(source, shift), colorsConfig)\n\n  override def withSource(source: SourceFile): Position =\n    DelegatingPosition.wrap(delegate.withSource(source), colorsConfig)\n\n  @scala.deprecated(\"Use `start` instead\")\n  override def startOrPoint: Int = delegate.startOrPoint\n\n  override def withShift(shift: Int): Position = DelegatingPosition.wrap(delegate.withShift(shift), colorsConfig)\n\n  @scala.deprecated(\"Use `end` instead\")\n  override def endOrPoint: Int = delegate.endOrPoint\n\n  override def focusStart: Position = DelegatingPosition.wrap(delegate.focusStart, colorsConfig)\n\n  override def focus: Position = DelegatingPosition.wrap(delegate.focus, colorsConfig)\n\n  override def focusEnd: Position = DelegatingPosition.wrap(delegate.focusEnd, colorsConfig)\n\n  override def |(that: Position, poses: Position*): Position =\n    DelegatingPosition.wrap(delegate.|(that, poses: _*), colorsConfig)\n\n  override def |(that: Position): Position = DelegatingPosition.wrap(delegate.|(that), colorsConfig)\n\n  override def ^(point: Int): Position = DelegatingPosition.wrap(delegate.^(point), colorsConfig)\n\n  override def |^(that: Position): Position = DelegatingPosition.wrap(delegate.|^(that), colorsConfig)\n\n  override def ^|(that: Position): Position = DelegatingPosition.wrap(delegate.^|(that), colorsConfig)\n\n  override def union(pos: Position): Position = DelegatingPosition.wrap(delegate.union(pos), colorsConfig)\n\n  override def includes(pos: Position): Boolean = delegate.includes(pos)\n\n  override def properlyIncludes(pos: Position): Boolean = delegate.properlyIncludes(pos)\n\n  override def precedes(pos: Position): Boolean = delegate.precedes(pos)\n\n  override def properlyPrecedes(pos: Position): Boolean = delegate.properlyPrecedes(pos)\n\n  override def sameRange(pos: Position): Boolean = delegate.sameRange(pos)\n\n  override def overlaps(pos: Position): Boolean = delegate.overlaps(pos)\n\n  override def line: Int = delegate.line\n\n  override def column: Int = delegate.column\n\n  override def lineCaret: String = delegate.lineCaret\n\n  @scala.deprecated(\"use `lineCaret`\")\n  override def lineCarat: String = delegate.lineCarat\n\n  override def showDebug: String = delegate.showDebug\n\n  override def show: String = delegate.show\n}\n\nobject DelegatingPosition {\n  def wrap(pos: Position, colorsConfig: ColorsConfig): Position =\n    pos match {\n      case NoPosition                  => pos\n      case wrapped: DelegatingPosition => wrapped\n      case _                           => new DelegatingPosition(pos, colorsConfig)\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/scala-2.11/com/softwaremill/clippy/DelegatingReporter.scala",
    "content": "package com.softwaremill.clippy\n\nimport scala.reflect.internal.util.Position\nimport scala.tools.nsc.reporters.Reporter\n\nclass DelegatingReporter(r: Reporter, handleError: (Position, String) => String, colorsConfig: ColorsConfig)\n    extends Reporter {\n  override protected def info0(pos: Position, msg: String, severity: Severity, force: Boolean) = {\n    val wrapped = DelegatingPosition.wrap(pos, colorsConfig)\n\n    // cannot delegate to info0 as it's protected, hence special-casing on the possible severity values\n    if (severity == INFO) {\n      r.info(wrapped, msg, force)\n    } else if (severity == WARNING) {\n      warning(wrapped, msg)\n    } else if (severity == ERROR) {\n      error(wrapped, msg)\n    } else {\n      error(wrapped, s\"UNKNOWN SEVERITY: $severity\\n$msg\")\n    }\n  }\n\n  override def echo(msg: String)                   = r.echo(msg)\n  override def comment(pos: Position, msg: String) = r.comment(DelegatingPosition.wrap(pos, colorsConfig), msg)\n  override def hasErrors                           = r.hasErrors || cancelled\n  override def reset() = {\n    cancelled = false\n    r.reset()\n  }\n\n  //\n\n  override def echo(pos: Position, msg: String)     = r.echo(DelegatingPosition.wrap(pos, colorsConfig), msg)\n  override def warning(pos: Position, msg: String)  = r.warning(DelegatingPosition.wrap(pos, colorsConfig), msg)\n  override def errorCount                           = r.errorCount\n  override def warningCount                         = r.warningCount\n  override def hasWarnings                          = r.hasWarnings\n  override def flush()                              = r.flush()\n  override def count(severity: Severity): Int       = r.count(conv(severity))\n  override def resetCount(severity: Severity): Unit = r.resetCount(conv(severity))\n\n  //\n\n  private def conv(s: Severity): r.Severity = s match {\n    case INFO    => r.INFO\n    case WARNING => r.WARNING\n    case ERROR   => r.ERROR\n  }\n\n  //\n\n  override def error(pos: Position, msg: String) = {\n    val wrapped = DelegatingPosition.wrap(pos, colorsConfig)\n    r.error(wrapped, handleError(wrapped, msg))\n  }\n}\n"
  },
  {
    "path": "plugin/src/main/scala-2.12/com/softwaremill/clippy/DelegatingPosition.scala",
    "content": "package com.softwaremill.clippy\n\nimport scala.reflect.internal.util.{NoPosition, Position, SourceFile}\nimport scala.reflect.macros.Attachments\n\nclass DelegatingPosition(delegate: Position, colorsConfig: ColorsConfig) extends Position {\n\n  // used by scalac to report errors\n  override def showError(msg: String): String = highlight(delegate.showError(msg))\n\n  // used by sbt\n  override def lineContent: String = highlight(delegate.lineContent)\n\n  def highlight(str: String): String = colorsConfig match {\n    case e: ColorsConfig.Enabled =>\n      Highlighter\n        .defaultHighlight(\n          str.toVector,\n          e.comment,\n          e.`type`,\n          e.literal,\n          e.keyword,\n          e.reset\n        )\n        .mkString\n    case _ => str\n  }\n\n  // impl copied\n\n  override def fail(what: String): Nothing = throw new UnsupportedOperationException(s\"Position.$what on $this\")\n\n  // simple delegates with position wrapping when position is returned\n\n  @scala.deprecated(\"use `point`\")\n  override def offset: Option[Int] = delegate.offset\n\n  override def all: Set[Any] = delegate.all\n\n  @scala.deprecated(\"use `focus`\")\n  override def toSingleLine: Position = DelegatingPosition.wrap(delegate.toSingleLine, colorsConfig)\n\n  override def pos: Position = DelegatingPosition.wrap(delegate.pos, colorsConfig)\n\n  override def get[T](implicit evidence$2: ClassManifest[T]): Option[T] = delegate.get(evidence$2)\n\n  override def finalPosition: Pos = DelegatingPosition.wrap(delegate.finalPosition, colorsConfig)\n\n  override def withPos(newPos: Position): Attachments { type Pos = DelegatingPosition.this.Pos } =\n    delegate.withPos(newPos)\n\n  @scala.deprecated(\"use `line`\")\n  override def safeLine: Int = delegate.safeLine\n\n  override def isTransparent: Boolean = delegate.isTransparent\n\n  override def contains[T](implicit evidence$3: ClassManifest[T]): Boolean = delegate.contains(evidence$3)\n\n  override def isOffset: Boolean = delegate.isOffset\n\n  @scala.deprecated(\"use `showDebug`\")\n  override def dbgString: String = delegate.dbgString\n\n  override def isOpaqueRange: Boolean = delegate.isOpaqueRange\n\n  override def update[T](attachment: T)(\n      implicit evidence$4: ClassManifest[T]\n  ): Attachments { type Pos = DelegatingPosition.this.Pos } = delegate.update(attachment)(evidence$4)\n\n  override def pointOrElse(alt: Int): Int = delegate.pointOrElse(alt)\n\n  @scala.deprecated(\"use `finalPosition`\")\n  override def inUltimateSource(source: SourceFile): Position =\n    DelegatingPosition.wrap(delegate.inUltimateSource(source), colorsConfig)\n\n  override def isDefined: Boolean = delegate.isDefined\n\n  override def makeTransparent: Position = DelegatingPosition.wrap(delegate.makeTransparent, colorsConfig)\n\n  override def isRange: Boolean = delegate.isRange\n\n  override def source: SourceFile = delegate.source\n\n  override def remove[T](\n      implicit evidence$5: ClassManifest[T]\n  ): Attachments { type Pos = DelegatingPosition.this.Pos } = delegate.remove(evidence$5)\n\n  override def withStart(start: Int): Position = DelegatingPosition.wrap(delegate.withStart(start), colorsConfig)\n\n  @scala.deprecated(\"use `lineCaret`\")\n  override def lineWithCarat(maxWidth: Int): (String, String) = delegate.lineWithCarat(maxWidth)\n\n  override def start: Int = delegate.start\n\n  override def withPoint(point: Int): Position = DelegatingPosition.wrap(delegate.withPoint(point), colorsConfig)\n\n  override def point: Int = delegate.point\n\n  override def isEmpty: Boolean = delegate.isEmpty\n\n  override def end: Int = delegate.end\n\n  override def withEnd(end: Int): Position = DelegatingPosition.wrap(delegate.withEnd(end), colorsConfig)\n\n  @scala.deprecated(\"Use `withSource(source)` and `withShift`\")\n  override def withSource(source: SourceFile, shift: Int): Position =\n    DelegatingPosition.wrap(delegate.withSource(source, shift), colorsConfig)\n\n  override def withSource(source: SourceFile): Position =\n    DelegatingPosition.wrap(delegate.withSource(source), colorsConfig)\n\n  @scala.deprecated(\"Use `start` instead\")\n  override def startOrPoint: Int = delegate.startOrPoint\n\n  override def withShift(shift: Int): Position = DelegatingPosition.wrap(delegate.withShift(shift), colorsConfig)\n\n  @scala.deprecated(\"Use `end` instead\")\n  override def endOrPoint: Int = delegate.endOrPoint\n\n  override def focusStart: Position = DelegatingPosition.wrap(delegate.focusStart, colorsConfig)\n\n  override def focus: Position = DelegatingPosition.wrap(delegate.focus, colorsConfig)\n\n  override def focusEnd: Position = DelegatingPosition.wrap(delegate.focusEnd, colorsConfig)\n\n  override def |(that: Position, poses: Position*): Position =\n    DelegatingPosition.wrap(delegate.|(that, poses: _*), colorsConfig)\n\n  override def |(that: Position): Position = DelegatingPosition.wrap(delegate.|(that), colorsConfig)\n\n  override def ^(point: Int): Position = DelegatingPosition.wrap(delegate.^(point), colorsConfig)\n\n  override def |^(that: Position): Position = DelegatingPosition.wrap(delegate.|^(that), colorsConfig)\n\n  override def ^|(that: Position): Position = DelegatingPosition.wrap(delegate.^|(that), colorsConfig)\n\n  override def union(pos: Position): Position = DelegatingPosition.wrap(delegate.union(pos), colorsConfig)\n\n  override def includes(pos: Position): Boolean = delegate.includes(pos)\n\n  override def properlyIncludes(pos: Position): Boolean = delegate.properlyIncludes(pos)\n\n  override def precedes(pos: Position): Boolean = delegate.precedes(pos)\n\n  override def properlyPrecedes(pos: Position): Boolean = delegate.properlyPrecedes(pos)\n\n  override def sameRange(pos: Position): Boolean = delegate.sameRange(pos)\n\n  override def overlaps(pos: Position): Boolean = delegate.overlaps(pos)\n\n  override def line: Int = delegate.line\n\n  override def column: Int = delegate.column\n\n  override def lineCaret: String = delegate.lineCaret\n\n  @scala.deprecated(\"use `lineCaret`\")\n  override def lineCarat: String = delegate.lineCarat\n\n  override def showDebug: String = delegate.showDebug\n\n  override def show: String = delegate.show\n}\n\nobject DelegatingPosition {\n  def wrap(pos: Position, colorsConfig: ColorsConfig): Position =\n    pos match {\n      case NoPosition                  => pos\n      case wrapped: DelegatingPosition => wrapped\n      case _                           => new DelegatingPosition(pos, colorsConfig)\n    }\n}\n"
  },
  {
    "path": "plugin/src/main/scala-2.12/com/softwaremill/clippy/DelegatingReporter.scala",
    "content": "package com.softwaremill.clippy\n\nimport scala.reflect.internal.util.Position\nimport scala.tools.nsc.reporters.Reporter\n\nclass DelegatingReporter(r: Reporter, handleError: (Position, String) => String, colorsConfig: ColorsConfig)\n    extends Reporter {\n  override protected def info0(pos: Position, msg: String, severity: Severity, force: Boolean) = {\n    val wrapped = DelegatingPosition.wrap(pos, colorsConfig)\n\n    // cannot delegate to info0 as it's protected, hence special-casing on the possible severity values\n    if (severity == INFO) {\n      r.info(wrapped, msg, force)\n    } else if (severity == WARNING) {\n      warning(wrapped, msg)\n    } else if (severity == ERROR) {\n      error(wrapped, msg)\n    } else {\n      error(wrapped, s\"UNKNOWN SEVERITY: $severity\\n$msg\")\n    }\n  }\n\n  override def echo(msg: String)                   = r.echo(msg)\n  override def comment(pos: Position, msg: String) = r.comment(DelegatingPosition.wrap(pos, colorsConfig), msg)\n  override def hasErrors                           = r.hasErrors || cancelled\n  override def reset() = {\n    cancelled = false\n    r.reset()\n  }\n\n  //\n\n  override def echo(pos: Position, msg: String)     = r.echo(DelegatingPosition.wrap(pos, colorsConfig), msg)\n  override def warning(pos: Position, msg: String)  = r.warning(DelegatingPosition.wrap(pos, colorsConfig), msg)\n  override def errorCount                           = r.errorCount\n  override def warningCount                         = r.warningCount\n  override def hasWarnings                          = r.hasWarnings\n  override def flush()                              = r.flush()\n  override def count(severity: Severity): Int       = r.count(conv(severity))\n  override def resetCount(severity: Severity): Unit = r.resetCount(conv(severity))\n\n  //\n\n  private def conv(s: Severity): r.Severity = s match {\n    case INFO    => r.INFO\n    case WARNING => r.WARNING\n    case ERROR   => r.ERROR\n  }\n\n  //\n\n  override def error(pos: Position, msg: String) = {\n    val wrapped = DelegatingPosition.wrap(pos, colorsConfig)\n    r.error(wrapped, handleError(wrapped, msg))\n  }\n}\n"
  },
  {
    "path": "plugin-sbt/src/main/scala/com/softwaremill/clippy/ClippySbtPlugin.scala",
    "content": "package com.softwaremill.clippy\n\nimport sbt._\nimport sbt.Keys._\n\nimport scala.collection.mutable.ListBuffer\n\nobject ClippySbtPlugin extends AutoPlugin {\n  object ClippyColor extends Enumeration {\n    val Black        = Value(\"black\")\n    val LightGray    = Value(\"light-gray\")\n    val DarkGray     = Value(\"dark-gray\")\n    val Red          = Value(\"red\")\n    val LightRed     = Value(\"light-red\")\n    val Green        = Value(\"green\")\n    val LightGreen   = Value(\"light-green\")\n    val Yellow       = Value(\"yellow\")\n    val LightYellow  = Value(\"light-yellow\")\n    val Blue         = Value(\"blue\")\n    val LightBlue    = Value(\"light-blue\")\n    val Magenta      = Value(\"magenta\")\n    val LightMagenta = Value(\"light-magenta\")\n    val Cyan         = Value(\"cyan\")\n    val LightCyan    = Value(\"light-cyan\")\n    val White        = Value(\"white\")\n    val None         = Value(\"none\")\n  }\n\n  object WarningPatterns {\n    val NonExhaustiveMatch = \"match may not be exhaustive[\\\\s\\\\S]*\"\n  }\n\n  object autoImport {\n    val clippyColorsEnabled = settingKey[Boolean](\"Should Clippy color type mismatch diffs and highlight syntax\")\n    val clippyColorDiff     = settingKey[Option[ClippyColor.Value]](\"The color to use for diffs, if other than default\")\n    val clippyColorComment =\n      settingKey[Option[ClippyColor.Value]](\"The color to use for comments, if other than default\")\n    val clippyColorType = settingKey[Option[ClippyColor.Value]](\"The color to use for types, if other than default\")\n    val clippyColorLiteral =\n      settingKey[Option[ClippyColor.Value]](\"The color to use for literals, if other than default\")\n    val clippyColorKeyword =\n      settingKey[Option[ClippyColor.Value]](\"The color to use for keywords, if other than default\")\n    val clippyColorReset =\n      settingKey[Option[ClippyColor.Value]](\"The color to use for resetting to neutral, if other than default\")\n    val clippyUrl = settingKey[Option[String]](\"Url from which to fetch advice, if other than default\")\n    val clippyLocalStoreDir =\n      settingKey[Option[String]](\"Directory where cached advice data should be stored, if other than default\")\n    val clippyProjectRoot =\n      settingKey[Option[String]](\"Project root in which project-specific advice is stored, if any\")\n    val clippyFatalWarnings =\n      settingKey[List[String]](\"Regular expressions of warning messages which should fail compilation\")\n    val NonExhaustiveMatch = \"match may not be exhaustive[\\\\s\\\\S]*\"\n  }\n\n  // in ~/.sbt auto import doesn't work, so providing aliases here for convenience\n  val clippyColorsEnabled = autoImport.clippyColorsEnabled\n  val clippyColorDiff     = autoImport.clippyColorDiff\n  val clippyColorComment  = autoImport.clippyColorComment\n  val clippyColorType     = autoImport.clippyColorType\n  val clippyColorLiteral  = autoImport.clippyColorLiteral\n  val clippyColorKeyword  = autoImport.clippyColorKeyword\n  val clippyColorReset    = autoImport.clippyColorReset\n  val clippyUrl           = autoImport.clippyUrl\n  val clippyLocalStoreDir = autoImport.clippyLocalStoreDir\n  val clippyProjectRoot   = autoImport.clippyProjectRoot\n  val clippyFatalWarnings = autoImport.clippyFatalWarnings\n\n  override def projectSettings = Seq(\n    clippyColorsEnabled := false,\n    clippyColorDiff := None,\n    clippyColorComment := None,\n    clippyColorType := None,\n    clippyColorLiteral := None,\n    clippyColorKeyword := None,\n    clippyColorReset := None,\n    clippyUrl := None,\n    clippyLocalStoreDir := None,\n    clippyProjectRoot := None,\n    clippyFatalWarnings := Nil,\n    addCompilerPlugin(\"com.softwaremill.clippy\" %% \"plugin\" % ClippyBuildInfo.version classifier \"bundle\"),\n    scalacOptions := {\n      val result = ListBuffer(scalacOptions.value: _*)\n      if (clippyColorsEnabled.value) result += \"-P:clippy:colors=true\"\n      clippyColorDiff.value.foreach(c => result += s\"-P:clippy:colors-diff=$c\")\n      clippyColorComment.value.foreach(c => result += s\"-P:clippy:colors-comment=$c\")\n      clippyColorType.value.foreach(c => result += s\"-P:clippy:colors-type=$c\")\n      clippyColorLiteral.value.foreach(c => result += s\"-P:clippy:colors-literal=$c\")\n      clippyColorKeyword.value.foreach(c => result += s\"-P:clippy:colors-keyword=$c\")\n      clippyColorReset.value.foreach(c => result += s\"-P:clippy:colors-reset=$c\")\n      clippyUrl.value.foreach(c => result += s\"-P:clippy:url=$c\")\n      clippyLocalStoreDir.value.foreach(c => result += s\"-P:clippy:store=$c\")\n      clippyProjectRoot.value.foreach(c => result += s\"-P:clippy:projectRoot=$c\")\n      if (clippyFatalWarnings.value.nonEmpty)\n        result += s\"-P:clippy:fatalWarnings=${clippyFatalWarnings.value.mkString(\"|\")}\"\n      result.toList\n    }\n  )\n\n  override def trigger = allRequirements\n}\n"
  },
  {
    "path": "project/build.properties",
    "content": "sbt.version=0.13.17\n"
  },
  {
    "path": "project/plugins.sbt",
    "content": "resolvers += Resolver.typesafeRepo(\"releases\")\n// Workaround for the bug: https://github.com/sbt/sbt-assembly/issues/236\nresolvers += \"JBoss\" at \"https://repository.jboss.org\"\n\naddSbtPlugin(\"org.scalariform\" % \"sbt-scalariform\" % \"1.4.0\")\n\naddSbtPlugin(\"com.updateimpact\" % \"updateimpact-sbt-plugin\" % \"2.1.1\")\n\naddSbtPlugin(\"com.eed3si9n\" % \"sbt-buildinfo\" % \"0.4.0\")\n\naddSbtPlugin(\"com.eed3si9n\" % \"sbt-assembly\" % \"0.14.1\")\n\naddSbtPlugin(\"com.heroku\" % \"sbt-heroku\" % \"0.5.4\")\n\naddSbtPlugin(\"com.geirsson\" % \"sbt-scalafmt\" % \"0.6.8\")\n\n// The Play plugin\naddSbtPlugin(\"com.typesafe.play\" % \"sbt-plugin\" % \"2.4.6\")\n\n// web plugins\n\naddSbtPlugin(\"com.typesafe.sbt\" % \"sbt-less\"   % \"1.1.0\")\naddSbtPlugin(\"com.typesafe.sbt\" % \"sbt-jshint\" % \"1.0.3\")\naddSbtPlugin(\"com.typesafe.sbt\" % \"sbt-digest\" % \"1.1.0\")\naddSbtPlugin(\"com.typesafe.sbt\" % \"sbt-mocha\"  % \"1.1.0\")\n\n// scalajs\n\naddSbtPlugin(\"com.vmunier\"  % \"sbt-web-scalajs\" % \"1.0.3\")\naddSbtPlugin(\"org.scala-js\" % \"sbt-scalajs\"     % \"0.6.15\")\n"
  },
  {
    "path": "tests/src/test/scala/org/softwaremill/clippy/CompileTests.scala",
    "content": "package org.softwaremill.clippy\n\nimport java.io.{File, FileOutputStream}\nimport java.util.zip.GZIPOutputStream\nimport scala.reflect.runtime.currentMirror\nimport com.softwaremill.clippy._\nimport org.scalatest.{BeforeAndAfterAll, FlatSpec, Matchers}\nimport scala.tools.reflect.ToolBox\nimport scala.tools.reflect.ToolBoxError\n\nclass CompileTests extends FlatSpec with Matchers with BeforeAndAfterAll {\n\n  val localStoreDir = new File(System.getProperty(\"user.home\"), \".clippy\")\n  val localStore    = new File(localStoreDir, \"clippy.json.gz\")\n  val localStore2   = new File(localStoreDir, \"clippy2.json.gz\")\n\n  /**\n    * Writing test json data to where the plugin will expect to have it cached.\n    */\n  override protected def beforeAll() = {\n    super.beforeAll()\n    localStoreDir.mkdirs()\n    if (localStore.exists()) {\n      localStore.renameTo(localStore2)\n    }\n\n    val advices = List(\n      Advice(\n        TypeMismatchError(ExactT(\"slick.dbio.DBIOAction[*]\"), None, ExactT(\"slick.lifted.Rep[Option[*]]\"), None, None).asRegex,\n        \"Perhaps you forgot to call .result on your Rep[]? This will give you a DBIOAction that you can compose with other DBIOActions.\",\n        Library(\"com.typesafe.slick\", \"slick\", \"3.1.0\")\n      ),\n      Advice(\n        TypeMismatchError(\n          ExactT(\"akka.http.scaladsl.server.StandardRoute\"),\n          None,\n          ExactT(\n            \"akka.stream.scaladsl.Flow[akka.http.scaladsl.model.HttpRequest,akka.http.scaladsl.model.HttpResponse,Any]\"\n          ),\n          None,\n          None\n        ).asRegex,\n        \"did you forget to define an implicit akka.stream.ActorMaterializer? It allows routes to be converted into a flow. You can read more at http://doc.akka.io/docs/akka-stream-and-http-experimental/2.0/scala/http/routing-dsl/index.html\",\n        Library(\"com.typesafe.akka\", \"akka-http-experimental\", \"2.0.0\")\n      ),\n      Advice(\n        NotFoundError(ExactT(\"value wire\")).asRegex,\n        \"you need to import com.softwaremill.macwire._\",\n        Library(\"com.softwaremill.macwire\", \"macros\", \"2.0.0\")\n      ),\n      Advice(\n        NotFoundError(ExactT(\"value wire\")).asRegex,\n        \"If you need further help check out the macwire readme at https://github.com/adamw/macwire\",\n        Library(\"com.softwaremill.macwire\", \"macros\", \"2.0.0\")\n      ),\n      Advice(\n        TypeArgumentsDoNotConformToOverloadedBoundsError(\n          ExactT(\"*\"),\n          ExactT(\"value apply\"),\n          Set(\n            ExactT(\"[E <: slick.lifted.AbstractTable[_]]=> slick.lifted.TableQuery[E]\"),\n            ExactT(\"[E <: slick.lifted.AbstractTable[_]](cons: slick.lifted.Tag => E)slick.lifted.TableQuery[E]\")\n          )\n        ).asRegex,\n        \"incorrect class name passed to TableQuery\",\n        Library(\"com.typesafe.slick\", \"slick\", \"3.1.1\")\n      ),\n      Advice(\n        TypeclassNotFoundError(\n          ExactT(\"Ordering\"),\n          ExactT(\"java.time.LocalDate\")\n        ).asRegex,\n        \"implicit val localDateOrdering: Ordering[java.time.LocalDate] = Ordering.by(_.toEpochDay)\",\n        Library(\"java-lang\", \"time\", \"8+\")\n      )\n    )\n\n    import org.json4s.native.JsonMethods._\n    val data = compact(render(Clippy(\"0.1\", advices, Nil).toJson))\n\n    val os = new GZIPOutputStream(new FileOutputStream(localStore))\n    try os.write(data.getBytes(\"UTF-8\"))\n    finally os.close()\n  }\n\n  override protected def afterAll() = {\n    localStore.delete()\n    if (localStore2.exists()) {\n      localStore2.renameTo(localStore)\n    }\n\n    super.afterAll()\n  }\n\n  val snippets = Map(\n    \"akka http\" ->\n      \"\"\"\n        |import akka.actor.ActorSystem\n        |import akka.http.scaladsl.Http\n        |\n        |import akka.http.scaladsl.server.Directives._\n        |\n        |implicit val system = ActorSystem()\n        |\n        |val r = complete(\"ok\")\n        |\n        |Http().bindAndHandle(r, \"localhost\", 8080)\n      \"\"\".stripMargin,\n    \"macwire\" ->\n      \"\"\"\n        |class A()\n        |val a = wire[A]\n      \"\"\".stripMargin,\n    \"slick\" ->\n      \"\"\"\n        |case class User(id1: Long, id2: Long)\n        |trait TestSchema {\n        |\n        |  val db: slick.jdbc.JdbcBackend#DatabaseDef\n        |  val driver: slick.driver.JdbcProfile\n        |\n        |  import driver.api._\n        |\n        |  protected val users = TableQuery[User]\n        |\n        |  protected class Users(tag: Tag) extends Table[User](tag, \"users\") {\n        |    def id1 = column[Long](\"id\")\n        |    def id2 = column[Long](\"id\")\n        |\n        |    def * = (id1, id2) <> (User.tupled, User.unapply)\n        |  }\n        |}\n      \"\"\".stripMargin,\n    \"Type mismatch pretty diff\" ->\n      \"\"\"\n        |class Test {\n        |\n        |  type Cool = (String, String, Int, Option[String], Long)\n        |  type Bool = (String, String, Int, String, Long)\n        |\n        |  def test(cool: Cool): Bool = cool\n        |\n        |}\n      \"\"\".stripMargin\n  )\n\n  val tb = {\n    val cpp = sys.env(\"CLIPPY_PLUGIN_PATH\")\n    currentMirror.mkToolBox(\n      options = s\"-Xplugin:$cpp -Xplugin-require:clippy -P:clippy:colors=true -P:clippy:testmode=true\"\n    )\n  }\n\n  def tryCompile(snippet: String) = tb.compile(tb.parse(snippet))\n\n  for ((name, s) <- snippets) {\n    name should \"compile with errors\" in {\n      (the[ToolBoxError] thrownBy tryCompile(s)).message should include(\"Clippy advises\")\n    }\n  }\n\n  \"Clippy\" should \"return all matching advice\" in {\n    (the[ToolBoxError] thrownBy tryCompile(snippets(\"macwire\"))).message should include(\n      \"Clippy advises you to try one of these\"\n    )\n  }\n\n}\n"
  },
  {
    "path": "ui/app/ClippyApplicationLoader.scala",
    "content": "import api.UiApiImpl\nimport com.softwaremill.id.DefaultIdGenerator\nimport controllers._\nimport dal.AdvicesRepository\nimport play.api.ApplicationLoader.Context\nimport play.api._\nimport play.api.i18n.I18nComponents\nimport play.api.mvc.EssentialFilter\nimport router.Routes\nimport util.{DatabaseConfig, SqlDatabase}\nimport util.email.{DummyEmailService, SendgridEmailService}\n\nclass ClippyApplicationLoader extends ApplicationLoader {\n  def load(context: Context) = {\n    Logger.configure(context.environment)\n    val c = new ClippyComponents(context)\n    c.database.updateSchema()\n    c.application\n  }\n}\n\nclass ClippyComponents(context: Context) extends BuiltInComponentsFromContext(context) with I18nComponents {\n\n  implicit val ec = scala.concurrent.ExecutionContext.Implicits.global\n\n  lazy val router =\n    new Routes(httpErrorHandler, applicationController, assets, webJarAssets, advicesController, autowireController)\n\n  lazy val contactEmail = configuration.getString(\"email.contact\").getOrElse(\"?\")\n  lazy val emailService = SendgridEmailService\n    .createFromEnv(contactEmail)\n    .getOrElse(new DummyEmailService)\n\n  lazy val webJarAssets = new WebJarAssets(httpErrorHandler, configuration, environment)\n  lazy val assets       = new controllers.Assets(httpErrorHandler)\n\n  lazy val idGenerator = new DefaultIdGenerator()\n\n  lazy val applicationController = new ApplicationController()\n\n  lazy val database          = SqlDatabase.create(new DatabaseConfig { override val rootConfig = configuration.underlying })\n  lazy val advicesRepository = new AdvicesRepository(database, idGenerator)\n\n  lazy val uiApiImpl          = new UiApiImpl(advicesRepository, emailService, contactEmail)\n  lazy val autowireController = new AutowireController(uiApiImpl)\n\n  lazy val advicesController = new AdvicesController(advicesRepository)\n\n  override lazy val httpFilters: Seq[EssentialFilter] = List(new HttpsFilter())\n}\n"
  },
  {
    "path": "ui/app/api/UiApiImpl.scala",
    "content": "package api\n\nimport com.softwaremill.clippy._\nimport dal.AdvicesRepository\nimport util.email.EmailService\n\nimport scala.concurrent.{ExecutionContext, Future}\n\nclass UiApiImpl(\n    advicesRepository: AdvicesRepository,\n    emailService: EmailService,\n    contactEmail: String\n)(implicit ec: ExecutionContext)\n    extends UiApi {\n\n  override def sendCannotParse(errorText: String, contributorEmail: String) =\n    emailService.send(\n      contactEmail,\n      \"Clippy: unparseable message\",\n      s\"\"\"\n         |Contributor email: $contributorEmail\n         |\n         |Error text:\n         |$errorText\n       \"\"\".stripMargin\n    )\n\n  override def sendAdviceProposal(ap: AdviceProposal): Future[Unit] =\n    advicesRepository\n      .store(\n        ap.errorTextRaw,\n        ap.patternRaw,\n        ap.compilationError,\n        ap.advice,\n        AdviceState.Pending,\n        ap.library,\n        ap.contributor,\n        ap.comment\n      )\n      .flatMap { a =>\n        emailService.send(contactEmail, \"Clippy: new advice proposal\", s\"\"\"\n             |Advice proposal:\n             |$a\n             |\"\"\".stripMargin)\n      }\n\n  override def listAccepted() =\n    advicesRepository.findAll().map(_.map(_.toAdviceListing))\n\n  override def sendSuggestEdit(text: String, contactEmail: String, adviceListing: AdviceListing) =\n    emailService.send(\n      contactEmail,\n      \"Clippy: edit suggestion\",\n      s\"\"\"\n         |Edit suggestion for: $adviceListing\n         |Contact email: $contactEmail\n         |\n         |Suggestion:\n         |$text\n       \"\"\".stripMargin\n    )\n\n  override def feedback(text: String, contactEmail: String) =\n    emailService.send(contactEmail, \"Clippy: feedback\", s\"\"\"\n         |Contact email: $contactEmail\n         |\n         |Feedback:\n         |$text\n       \"\"\".stripMargin)\n}\n"
  },
  {
    "path": "ui/app/assets/stylesheets/main.less",
    "content": "#reactmain {\n  padding-top: 60px;\n}\n\nhtml {\n  position: relative;\n  min-height: 100%;\n}\n.cout {\n  width: 100%;\n  padding-top: 5px;\n  margin-bottom: 5px;\n}\nbody {\n  margin-bottom: 70px;\n}\n.footer {\n  position: absolute;\n  bottom: 0;\n  width: 100%;\n  height: 60px;\n  background-color: #f5f5f5;\n  margin-top: 10px;\n}\n.glyphicon-edit {\n  cursor: pointer;\n}\n.advice-listing {\n  table-layout: fixed;\n  word-wrap: break-word;\n}"
  },
  {
    "path": "ui/app/controllers/AdvicesController.scala",
    "content": "package controllers\n\nimport com.softwaremill.clippy.{Advice, Clippy}\nimport dal.AdvicesRepository\nimport play.api.mvc.{Action, Controller}\nimport util.{ClippyBuildInfo, Zip}\n\nimport scala.concurrent.{ExecutionContext, Future}\n\nclass AdvicesController(advicesRepository: AdvicesRepository)(implicit ec: ExecutionContext) extends Controller {\n  def get = Action.async {\n    gzippedAdvices.map(a => Ok(a).withHeaders(\"Content-Encoding\" -> \"gzip\"))\n  }\n\n  def gzippedAdvices: Future[Array[Byte]] =\n    advicesRepository.findAll().map { storedAdvices =>\n      // TODO, once there's an admin: filter out not accepted advice\n      val advices = storedAdvices\n      //.filter(_.accepted)\n        .map(_.toAdvice)\n      Zip.compress(toJsonString(advices.toList))\n    }\n\n  private def toJsonString(advices: List[Advice]): String = {\n    import org.json4s.native.JsonMethods.{render => r, compact}\n    compact(r(Clippy(ClippyBuildInfo.version, advices, Nil).toJson))\n  }\n}\n"
  },
  {
    "path": "ui/app/controllers/ApplicationController.scala",
    "content": "package controllers\n\nimport play.api.mvc.{Action, Controller}\n\nclass ApplicationController extends Controller {\n\n  def index = Action {\n    Ok(views.html.index())\n  }\n}\n"
  },
  {
    "path": "ui/app/controllers/AutowireController.scala",
    "content": "package controllers\n\nimport com.softwaremill.clippy.UiApi\nimport play.api.mvc.{Action, Controller}\n\nimport upickle.default._\nimport upickle.Js\n\nimport scala.concurrent.ExecutionContext\n\nclass AutowireController(uiApi: UiApi)(implicit ec: ExecutionContext) extends Controller {\n\n  def autowireApi(path: String) = Action.async { implicit request =>\n    val b = request.body.asText.getOrElse(\"\")\n\n    AutowireServer\n      .route[UiApi](uiApi)(\n        autowire.Core.Request(\n          path.split(\"/\"),\n          upickle.json.read(b).asInstanceOf[Js.Obj].value.toMap\n        )\n      )\n      .map(jsv => Ok(upickle.json.write(jsv)))\n  }\n}\n\nobject AutowireServer extends autowire.Server[Js.Value, Reader, Writer] {\n  def read[Result: Reader](p: Js.Value) = upickle.default.readJs[Result](p)\n  def write[Result: Writer](r: Result)  = upickle.default.writeJs(r)\n}\n"
  },
  {
    "path": "ui/app/controllers/HttpsFilter.scala",
    "content": "package controllers\n\nimport play.api.http.Status\nimport play.api.mvc.{Filter, RequestHeader, Result, Results}\n\nimport scala.concurrent.Future\n\nclass HttpsFilter extends Filter {\n  def apply(nextFilter: (RequestHeader) => Future[Result])(requestHeader: RequestHeader): Future[Result] =\n    requestHeader.headers.get(\"x-forwarded-proto\") match {\n      case Some(header) =>\n        if (header == \"https\") {\n          nextFilter(requestHeader)\n        } else {\n          Future.successful(\n            Results.Redirect(\"https://\" + requestHeader.host + requestHeader.uri, Status.MOVED_PERMANENTLY)\n          )\n        }\n      case None => nextFilter(requestHeader)\n    }\n}\n"
  },
  {
    "path": "ui/app/dal/AdvicesRepository.scala",
    "content": "package dal\n\nimport com.softwaremill.clippy.AdviceState.AdviceState\nimport com.softwaremill.clippy._\nimport com.softwaremill.id.IdGenerator\nimport util.SqlDatabase\n\nimport scala.concurrent.{ExecutionContext, Future}\n\nclass AdvicesRepository(database: SqlDatabase, idGenerator: IdGenerator)(implicit ec: ExecutionContext) {\n  import database._\n  import database.driver.api._\n\n  private class AdvicesTable(tag: Tag) extends Table[StoredAdvice](tag, \"advices\") {\n    def id                 = column[Long](\"id\", O.PrimaryKey)\n    def errorTextRaw       = column[String](\"error_text_raw\")\n    def patternRaw         = column[String](\"pattern_raw\")\n    def compilationError   = column[String](\"compilation_error\")\n    def advice             = column[String](\"advice\")\n    def state              = column[Int](\"state\")\n    def libraryGroupId     = column[String](\"library_group_id\")\n    def libraryArtifactId  = column[String](\"library_artifact_id\")\n    def libraryVersion     = column[String](\"library_version\")\n    def contributorEmail   = column[Option[String]](\"contributor_email\")\n    def contributorTwitter = column[Option[String]](\"contributor_twitter\")\n    def contributorGithub  = column[Option[String]](\"contributor_github\")\n    def comment            = column[Option[String]](\"comment\")\n\n    def * =\n      (\n        id,\n        errorTextRaw,\n        patternRaw,\n        compilationError,\n        advice,\n        state,\n        (libraryGroupId, libraryArtifactId, libraryVersion),\n        (contributorEmail, contributorGithub, contributorTwitter),\n        comment\n      ).shaped <> ({ t =>\n        StoredAdvice(\n          t._1,\n          t._2,\n          t._3,\n          CompilationError.fromJsonString(t._4).get,\n          t._5,\n          AdviceState(t._6),\n          (Library.apply _).tupled(t._7),\n          Contributor.tupled(t._8),\n          t._9\n        )\n      }, { (a: StoredAdvice) =>\n        Some(\n          (\n            a.id,\n            a.errorTextRaw,\n            a.patternRaw,\n            a.compilationError.toJsonString,\n            a.advice,\n            a.state.id,\n            Library.unapply(a.library).get,\n            Contributor.unapply(a.contributor).get,\n            a.comment\n          )\n        )\n      })\n  }\n\n  private val advices = TableQuery[AdvicesTable]\n\n  def store(\n      errorTextRaw: String,\n      patternRaw: String,\n      compilationError: CompilationError[RegexT],\n      advice: String,\n      state: AdviceState,\n      library: Library,\n      contributor: Contributor,\n      comment: Option[String]\n  ): Future[StoredAdvice] = {\n\n    val a = StoredAdvice(\n      idGenerator.nextId(),\n      errorTextRaw,\n      patternRaw,\n      compilationError,\n      advice,\n      state,\n      library,\n      contributor,\n      comment\n    )\n\n    db.run(advices += a).map(_ => a)\n  }\n\n  def findAll(): Future[Seq[StoredAdvice]] =\n    db.run(advices.result)\n}\n\ncase class StoredAdvice(\n    id: Long,\n    errorTextRaw: String,\n    patternRaw: String,\n    compilationError: CompilationError[RegexT],\n    advice: String,\n    state: AdviceState,\n    library: Library,\n    contributor: Contributor,\n    comment: Option[String]\n) {\n\n  def toAdvice = Advice(compilationError, advice, library)\n  def toAdviceListing =\n    AdviceListing(id, compilationError, advice, library, ContributorListing(contributor.github, contributor.twitter))\n}\n"
  },
  {
    "path": "ui/app/util/ConfigWithDefault.scala",
    "content": "package util\n\nimport java.util.concurrent.TimeUnit\n\nimport com.typesafe.config.Config\n\ntrait ConfigWithDefault {\n\n  def rootConfig: Config\n\n  def getBoolean(path: String, default: Boolean) = ifHasPath(path, default) { _.getBoolean(path) }\n  def getString(path: String, default: String)   = ifHasPath(path, default) { _.getString(path) }\n  def getInt(path: String, default: Int)         = ifHasPath(path, default) { _.getInt(path) }\n  def getConfig(path: String, default: Config)   = ifHasPath(path, default) { _.getConfig(path) }\n  def getMilliseconds(path: String, default: Long) = ifHasPath(path, default) {\n    _.getDuration(path, TimeUnit.MILLISECONDS)\n  }\n  def getOptionalString(path: String, default: Option[String] = None) = getOptional(path) { _.getString(path) }\n\n  private def ifHasPath[T](path: String, default: T)(get: Config => T): T =\n    if (rootConfig.hasPath(path)) get(rootConfig) else default\n\n  private def getOptional[T](fullPath: String, default: Option[T] = None)(get: Config => T) =\n    if (rootConfig.hasPath(fullPath)) {\n      Some(get(rootConfig))\n    } else {\n      default\n    }\n\n}\n"
  },
  {
    "path": "ui/app/util/DatabaseConfig.scala",
    "content": "package util\n\nimport com.typesafe.config.Config\n\ntrait DatabaseConfig extends ConfigWithDefault {\n  def rootConfig: Config\n\n  import DatabaseConfig._\n\n  lazy val dbH2Url              = getString(s\"db.h2.properties.url\", \"jdbc:h2:file:./data\")\n  lazy val dbPostgresServerName = getString(PostgresServerNameKey, \"\")\n  lazy val dbPostgresPort       = getString(PostgresPortKey, \"5432\")\n  lazy val dbPostgresDbName     = getString(PostgresDbNameKey, \"\")\n  lazy val dbPostgresUsername   = getString(PostgresUsernameKey, \"\")\n  lazy val dbPostgresPassword   = getString(PostgresPasswordKey, \"\")\n}\n\nobject DatabaseConfig {\n  val PostgresDSClass       = \"db.postgres.dataSourceClass\"\n  val PostgresServerNameKey = \"db.postgres.properties.serverName\"\n  val PostgresPortKey       = \"db.postgres.properties.portNumber\"\n  val PostgresDbNameKey     = \"db.postgres.properties.databaseName\"\n  val PostgresUsernameKey   = \"db.postgres.properties.user\"\n  val PostgresPasswordKey   = \"db.postgres.properties.password\"\n}\n"
  },
  {
    "path": "ui/app/util/SqlDatabase.scala",
    "content": "package util\n\nimport java.net.URI\n\nimport com.typesafe.config.ConfigValueFactory._\nimport com.typesafe.config.{Config, ConfigFactory}\nimport com.typesafe.scalalogging.StrictLogging\nimport org.flywaydb.core.Flyway\nimport slick.driver.JdbcProfile\nimport slick.jdbc.JdbcBackend._\n\ncase class SqlDatabase(\n    db: slick.jdbc.JdbcBackend#Database,\n    driver: JdbcProfile,\n    connectionString: JdbcConnectionString\n) {\n  def updateSchema() {\n    val flyway = new Flyway()\n    flyway.setDataSource(connectionString.url, connectionString.username, connectionString.password)\n    flyway.migrate()\n  }\n\n  def close() {\n    db.close()\n  }\n}\n\ncase class JdbcConnectionString(url: String, username: String = \"\", password: String = \"\")\n\nobject SqlDatabase extends StrictLogging {\n\n  def create(config: DatabaseConfig): SqlDatabase = {\n    val envDatabaseUrl = System.getenv(\"DATABASE_URL\")\n\n    if (config.dbPostgresServerName.length > 0)\n      createPostgresFromConfig(config)\n    else if (envDatabaseUrl != null)\n      createPostgresFromEnv(envDatabaseUrl)\n    else\n      createEmbedded(config)\n  }\n\n  def createEmbedded(connectionString: String): SqlDatabase = {\n    val db = Database.forURL(connectionString)\n    SqlDatabase(db, slick.driver.H2Driver, JdbcConnectionString(connectionString))\n  }\n\n  private def createPostgresFromEnv(envDatabaseUrl: String) = {\n    import DatabaseConfig._\n    /*\n      The DATABASE_URL is set by Heroku (if deploying there) and must be converted to a proper object\n      of type Config (for Slick). Expected format:\n      postgres://<username>:<password>@<host>:<port>/<dbname>\n     */\n    val dbUri    = new URI(envDatabaseUrl)\n    val username = dbUri.getUserInfo.split(\":\")(0)\n    val password = dbUri.getUserInfo.split(\":\")(1)\n    val intermediaryConfig = new DatabaseConfig {\n      override def rootConfig: Config =\n        ConfigFactory\n          .empty()\n          .withValue(PostgresDSClass, fromAnyRef(\"org.postgresql.ds.PGSimpleDataSource\"))\n          .withValue(PostgresServerNameKey, fromAnyRef(dbUri.getHost))\n          .withValue(PostgresPortKey, fromAnyRef(dbUri.getPort))\n          .withValue(PostgresDbNameKey, fromAnyRef(dbUri.getPath.tail))\n          .withValue(PostgresUsernameKey, fromAnyRef(username))\n          .withValue(PostgresPasswordKey, fromAnyRef(password))\n          .withFallback(ConfigFactory.load())\n    }\n    createPostgresFromConfig(intermediaryConfig)\n  }\n\n  private def postgresUrl(host: String, port: String, dbName: String) =\n    s\"jdbc:postgresql://$host:$port/$dbName\"\n\n  private def postgresConnectionString(config: DatabaseConfig) = {\n    val host     = config.dbPostgresServerName\n    val port     = config.dbPostgresPort\n    val dbName   = config.dbPostgresDbName\n    val username = config.dbPostgresUsername\n    val password = config.dbPostgresPassword\n    JdbcConnectionString(postgresUrl(host, port, dbName), username, password)\n  }\n\n  private def createPostgresFromConfig(config: DatabaseConfig) = {\n    val db = Database.forConfig(\"db.postgres\", config.rootConfig)\n    SqlDatabase(db, slick.driver.PostgresDriver, postgresConnectionString(config))\n  }\n\n  private def createEmbedded(config: DatabaseConfig): SqlDatabase = {\n    val db = Database.forConfig(\"db.h2\")\n    SqlDatabase(db, slick.driver.H2Driver, JdbcConnectionString(embeddedConnectionStringFromConfig(config)))\n  }\n\n  private def embeddedConnectionStringFromConfig(config: DatabaseConfig): String = {\n    val url      = config.dbH2Url\n    val fullPath = url.split(\":\")(3)\n    logger.info(s\"Using an embedded database, with data files located at: $fullPath\")\n    url\n  }\n}\n"
  },
  {
    "path": "ui/app/util/Zip.scala",
    "content": "package util\n\nimport java.io.{ByteArrayInputStream, ByteArrayOutputStream}\nimport java.util.zip.{GZIPInputStream, GZIPOutputStream}\n\nobject Zip {\n  private val BufferSize = 512\n\n  def compress(string: String): Array[Byte] = {\n    val os  = new ByteArrayOutputStream(string.length() / 5)\n    val gos = new GZIPOutputStream(os)\n    gos.write(string.getBytes(\"UTF-8\"))\n    gos.close()\n    os.close()\n    os.toByteArray\n  }\n\n  def decompress(compressed: Array[Byte]): String = {\n    val is        = new ByteArrayInputStream(compressed)\n    val gis       = new GZIPInputStream(is, BufferSize)\n    val string    = new StringBuilder()\n    val data      = new Array[Byte](BufferSize)\n    var bytesRead = gis.read(data)\n    while (bytesRead != -1) {\n      string.append(new String(data, 0, bytesRead, \"UTF-8\"))\n      bytesRead = gis.read(data)\n    }\n    gis.close()\n    is.close()\n    string.toString()\n  }\n}\n"
  },
  {
    "path": "ui/app/util/email/DummyEmailService.scala",
    "content": "package util.email\n\nimport com.typesafe.scalalogging.StrictLogging\n\nimport scala.collection.mutable.ListBuffer\nimport scala.concurrent.Future\n\nclass DummyEmailService extends EmailService with StrictLogging {\n  private val sentEmails: ListBuffer[(String, String, String)] = ListBuffer()\n\n  logger.info(\"Using dummy email service\")\n\n  def reset() {\n    sentEmails.clear()\n  }\n\n  override def send(to: String, subject: String, body: String) = {\n    this.synchronized {\n      sentEmails.+=((to, subject, body))\n    }\n\n    logger.info(s\"Would send email to $to, with subject: $subject, body: $body\")\n    Future.successful(())\n  }\n\n  def wasEmailSent(to: String, subject: String): Boolean =\n    sentEmails.exists(email => email._1.contains(to) && email._2 == subject)\n}\n"
  },
  {
    "path": "ui/app/util/email/EmailService.scala",
    "content": "package util.email\n\nimport scala.concurrent.Future\n\ntrait EmailService {\n  def send(to: String, subject: String, body: String): Future[Unit]\n}\n"
  },
  {
    "path": "ui/app/util/email/SendgridEmailService.scala",
    "content": "package util.email\n\nimport com.sendgrid.SendGrid\nimport com.typesafe.scalalogging.StrictLogging\n\nimport scala.concurrent.Future\nimport scala.util.Properties\n\nclass SendgridEmailService(sendgridUsername: String, sendgridPassword: String, emailFrom: String)\n    extends EmailService\n    with StrictLogging {\n\n  private lazy val sendgrid = new SendGrid(sendgridUsername, sendgridPassword)\n\n  override def send(to: String, subject: String, body: String) = {\n    val email = new SendGrid.Email()\n    email.addTo(to)\n    email.setFrom(emailFrom)\n    email.setSubject(subject)\n    email.setText(body)\n\n    val response = sendgrid.send(email)\n    if (response.getStatus) {\n      logger.info(s\"Email to $to sent\")\n    } else {\n      logger.error(\n        s\"Email to $to, subject: $subject, body: $body, not sent: \" +\n          s\"${response.getCode}/${response.getMessage}\"\n      )\n    }\n\n    Future.successful(())\n  }\n}\n\nobject SendgridEmailService extends StrictLogging {\n  def createFromEnv(emailFrom: String): Option[SendgridEmailService] =\n    for {\n      u <- Properties.envOrNone(\"SENDGRID_USERNAME\")\n      p <- Properties.envOrNone(\"SENDGRID_PASSWORD\")\n    } yield {\n      logger.info(\"Using SendGrid email service\")\n      new SendgridEmailService(u, p, emailFrom)\n    }\n}\n"
  },
  {
    "path": "ui/app/views/index.scala.html",
    "content": "<!DOCTYPE html>\n\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <title>Scala Clippy</title>\n\n    <link rel='stylesheet' href='@routes.WebJarAssets.at(WebJarAssets.locate(\"css/bootstrap.min.css\"))'>\n    <link rel=\"stylesheet\" href=\"@routes.Assets.versioned(\"stylesheets/main.css\")\"/>\n\n    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->\n    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->\n    <!--[if lt IE 9]>\n    <script src=\"https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js\"></script>\n    <script src=\"https://oss.maxcdn.com/respond/1.4.2/respond.min.js\"></script>\n    <![endif]-->\n\n    <link rel=\"shortcut icon\" type=\"image/png\" href=\"@routes.Assets.versioned(\"images/favicon.png\")\"/>\n\n    <link rel=\"canonical\" href=\"https://scala-clippy.org\">\n</head>\n<body>\n\n<!-- http://stackoverflow.com/questions/21324395/bootstrap-3-flush-footer-to-bottom-not-fixed -->\n<div class=\"container\">\n    <div id=\"reactmain\"></div>\n\n    <div id=\"use\" style=\"display: none\">@use()</div>\n\n    <script type='text/javascript' src='@routes.WebJarAssets.at(WebJarAssets.locate(\"jquery.min.js\"))'></script>\n    <script type='text/javascript' src='@routes.WebJarAssets.at(WebJarAssets.locate(\"bootstrap.min.js\"))'></script>\n    @playscalajs.html.scripts(\"uiclient\")\n\n    <a href=\"https://github.com/softwaremill/scala-clippy\"><img style=\"position: absolute; top: 51px; right: 0; border: 0;\" src=\"https://camo.githubusercontent.com/365986a132ccd6a44c23a9169022c0b5c890c387/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f7265645f6161303030302e706e67\" alt=\"Fork me on GitHub\" data-canonical-src=\"https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png\"></a>\n</div>\n<footer class=\"footer\">\n    <div>&nbsp;</div>\n    <div class=\"text-center\">\n        Copyright 2016. Created & maintained by\n        <a href=\"http://softwaremill.com\">SoftwareMill</a>.\n    </div>\n</footer>\n\n<!-- twitter -->\n<script>window.twttr = (function(d, s, id) {\n  var js, fjs = d.getElementsByTagName(s)[0],\n    t = window.twttr || {};\n  if (d.getElementById(id)) return t;\n  js = d.createElement(s);\n  js.id = id;\n  js.src = \"https://platform.twitter.com/widgets.js\";\n  fjs.parentNode.insertBefore(js, fjs);\n\n  t._e = [];\n  t.ready = function(f) {\n    t._e.push(f);\n  };\n\n  return t;\n}(document, \"script\", \"twitter-wjs\"));</script>\n\n<!-- google analytics -->\n<script>\n  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){\n  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),\n  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)\n  })(window,document,'script','//www.google-analytics.com/analytics.js','ga');\n\n  ga('create', 'UA-11235106-14', 'auto');\n  ga('send', 'pageview');\n\n</script>\n\n</body>\n</html>\n"
  },
  {
    "path": "ui/app/views/use.scala.html",
    "content": "<h2>\n    Scala Clippy\n    <a class=\"twitter-share-button\" href=\"https://twitter.com/intent/tweet?text=Scala%20Clippy%3A%20programmer-friendly%20compiler%20errors%20\">Tweet</a>\n\n    <a href=\"https://gitter.im/softwaremill/scala-clippy?utm_source=badge&amp;utm_medium=badge&amp;utm_campaign=pr-badge&amp;utm_content=badge\">\n        <img src=\"https://badges.gitter.im/softwaremill/scala-clippy.svg\" alt=\"Join the chat at https://gitter.im/softwaremill/scala-clippy\" style=\"max-width:100%; margin-bottom:5px;\" />\n    </a>\n</h2>\n\n<p>\n    Did you ever see a Scala compiler error such as:\n</p>\n<img class=\"cout\" src=\"assets/img/clippy-akka-err.png\">\n<p>\n    and had no idea what to do next? Well in this case, you need to provide an implicit instance of an <code>ActorMaterializer</code>,\n    but the compiler isn't smart enough to be able to tell you that. Luckily, <strong>Scala Clippy is here to help</strong>!\n</p>\n\n<p>\n    Just add the Scala Clippy compiler plugin, and you'll see this additional helpful message, with optional additional colors:\n</p>\n<img class=\"cout\" src=\"assets/img/clippy-akka-err-rich.png\">\n\n<h2>Adding the plugin</h2>\n\n<p>\n    The easiest to use Clippy is via an SBT plugin. If you'd like Clippy to be enabled for all projects, without\n    the need to modify each project's build, add the following to <code>~/.sbt/0.13/plugins/build.sbt</code>:\n</p>\n\n<pre>\naddSbtPlugin(\"com.softwaremill.clippy\" % \"plugin-sbt\" % \"0.6.1\")\n</pre>\n\n<p>\n    Upon first use, the plugin will download the advice dataset from <code>https://scala-clippy.org</code> and store it in the\n    <code>$HOME/.clippy</code> directory. The dataset will be updated at most once a day, in the background. You can customize the\n    dataset URL and local store by setting the <code>clippyUrl</code> and <code>clippyLocalStoreDir</code> sbt options to non-<code>None</code>-values.\n</p>\n\n<p>\n    Note: to customize a global sbt plugin (a plugin which is added via `~/.sbt/0.13/plugins/build.sbt`) keep in mind\n    that:\n</p>\n\n<ul>\n    <li>customize the plugin settings in <code>~/.sbt/0.13/build.sbt</code> (one directory up!). These settings will be\n    automatically added to all of your projects.</li>\n    <li>you'll need to add <code>import com.softwaremill.clippy.ClippySbtPlugin._</code> to access the setting names as auto-imports\n    don't work in the global settings</li>\n    <li>the clippy sbt settings are just a convenient syntax for adding compiler options (e.g., enabling colors is same\n    as <code>scalacOptions += \"-P:clippy:colors=true\"</code>)</li>\n</ul>\n\n<h2>(NEW!) Turning selected compilation warnings into errors</h2>\n<p>\n    From version 0.6.0 you can selectively define regexes for warnings which will be treated as fatal compilation errors. To do so with sbt,\n    use the <code>clippyFatalWarnings</code> setting, for example:\n</p>\n<pre>\n    import com.softwaremill.clippy.ClippySbtPlugin.WarningPatterns._\n    // ...\n    settings(\n      clippyFatalWarnings ++= List(\n        NonExhaustiveMatch,\n        \".*\\\\[wartremover:.*\\\\].*[\\\\s\\\\S]*\"\n      )\n    )\n</pre>\n<p>\n    You can also define fatal warnings in your .clippy.json file (see further sections for examples).\n</p>\n\n<h2>Enabling syntax and type mismatch diffs highlighting</h2>\n\n<p>\n    Clippy can highlight:\n</p>\n\n<ul>\n    <li>\n        in type mismatch errors, the diff between expected and actual types. This may be especially helpful for long type\n        signatures\n    </li>\n    <li>\n        syntax when displaying code fragments with errors. Example:\n        <img class=\"cout\" src=\"assets/img/clippy-syntax-highlight.png\">\n    </li>\n</ul>\n\n<p>\n    If you'd like to enable this feature in sbt globally, add the following to `~/.sbt/0.13/build.sbt`: (see also notes\n    above)\n</p>\n\n<pre>\nimport com.softwaremill.clippy.ClippySbtPlugin._ // needed in global configuration only\nclippyColorsEnabled := true\n</pre>\n\n<p>\n    To customize the colors, set any of `clippyColorDiff`, `clippyColorComment`,\n    `clippyColorType`, `clippyColorLiteral`, `clippyColorKeyword` to `Some(ClippyColor.[name])`, where `[name]` can be:\n    `Black`, `Red`, `Green`, `Yellow`, `Blue`, `Magenta`, `Cyan`, `White` or `None`.\n</p>\n\n<p>\n    You can of course add clippy on a per-project basis as well.\n</p>\n<h3>Windows users</h3>\n<p>If you notice that colors don't get correctly reset, that's probably caused by problems with interpreting the standard ANSI Reset code. You can set `clippyColorReset` to a custom value like `LightGray` to solve this issue.</p>\n<h2>Contributing advice</h2>\n\n<p>\n    Help others users by submitting an advice for a compilation error that you have encountered!\n    Just click \"contribute\" above and paste in your error!\n</p>\n\n<p>\n    Alternatively, create an issue on <a href=\"https://github.com/softwaremill/scala-clippy\">GitHub</a> or chat with\n    us on <a href=\"https://gitter.im/softwaremill/scala-clippy?utm_source=badge&amp;utm_medium=badge&amp;utm_campaign=pr-badge&amp;utm_content=badge\">Gitter</a> in case of any doubts.\n</p>\n\n<p>\n    Speaking of GitHub, you are also welcome to check out (and improve!) the plugin's &amp; website's\n    <a href=\"https://github.com/softwaremill/scala-clippy\">source code</a>.\n</p>\n\n<h2>Project-specific advices and fatal warnings</h2>\n\n<p>If you have advice that you feel is too specific to be worth sharing publicly, you can add\n  it to your project specific advice file. First set your project root:\n</p>\n<pre>\nclippyProjectRoot := Some((baseDirectory in ThisBuild).value) \n</pre>\n\nThen create a file named .clippy.json in the root of your project directory and add the advice json in the format illustrated below:\n\n<pre>\n{\n  \"version\": \"1\",\n  \"advices\": [\n    {\n      \"error\": {\n        \"type\": \"typeMismatch\",\n        \"found\": \"scala\\\\.concurrent\\\\.Future\\\\[Int\\\\]\",\n        \"required\": \"Int\"\n      },\n      \"text\": \"Maybe you used map where you should have used flatMap?\",\n      \"library\": {\n        \"groupId\": \"scala.lang\",\n        \"artifactId\": \"Future\",\n        \"version\": \"1.0\"\n      }\n    }\n  ],\n  \"fatalWarnings\": [\n    {\n      \"pattern\": \"match may not be exhaustive[\\\\s\\\\S]*\",\n      \"text\": \"Additional optional text to be displayed\"\n    }\n  ]\n}\n</pre>\n<h2>Library-specific advice</h2>\nIf you have advice that is specific to a library or library version you can also bundle the advice with your library.\nIf your users have Scala-Clippy installed they will see your advice if your library is inclued in their project.\nThis can be helpful in the common case where users of your library need specific imports to be able to use your functionality.\nTo bundle clippy advice with your library just put it in a file named clippy.json in your resources directory.\n\n<h2>Alternative ways to use Clippy</h2>\n\n<p>\n    You can also use Clippy directly as a compiler plugin. If you use SBT, add the following setting to your\n    project's <code>.sbt</code> file:\n</p>\n\n<pre>\naddCompilerPlugin(\"com.softwaremill.clippy\" %% \"plugin\" % \"0.6.1\" classifier \"bundle\")\n</pre>\n\n<p>\n    If you are using <code>scalac</code> directly, add the following option:\n</p>\n\n<pre>\n-Xplugin:clippy-plugin_2.11-0.6.1-bundle.jar\n</pre>\n\n<p>\n    See clippy <a href=\"https://github.com/softwaremill/scala-clippy\">README</a> for more details.</p>\n</p>\n"
  },
  {
    "path": "ui/conf/application.conf",
    "content": "# This is the main configuration file for the application.\n# ~~~~~\n\nplay.application.loader = \"ClippyApplicationLoader\"\n\n# Secret key\n# ~~~~~\n# The secret key is used to secure cryptographics functions.\n#\n# This must be changed for production, but we recommend not changing it in this file.\n#\n# See http://www.playframework.com/documentation/latest/ApplicationSecret for more details.\nplay.crypto.secret = \"changeme\"\nplay.crypto.secret = ${?PLAY_CRYPTO_SECRET}\n\n# The application languages\n# ~~~~~\nplay.i18n.langs = [ \"en\" ]\n\n# Router\n# ~~~~~\n# Define the Router object to use for this application.\n# This router will be looked up first when the application is starting up,\n# so make sure this is the entry point.\n# Furthermore, it's assumed your route file is named properly.\n# So for an application router like `my.application.Router`,\n# you may need to define a router file `conf/my.application.routes`.\n# Default to Routes in the root package (and conf/routes)\n# play.http.router = my.application.Routes\n\ndb {\n  h2 {\n    dataSourceClass = \"org.h2.jdbcx.JdbcDataSource\"\n    properties = {\n      url = \"jdbc:h2:file:./data/clippy\"\n    }\n  }\n  postgres {\n    dataSourceClass = \"org.postgresql.ds.PGSimpleDataSource\"\n    maxConnections = 16\n    properties = {\n      serverName = \"\"\n      portNumber = \"5432\"\n      databaseName = \"\"\n      user = \"\"\n      password = \"\"\n    }\n  }\n}\n\nemail.contact=\"clippy@scala-clippy.org\"\n\nplay.server.http.port=${?PORT}"
  },
  {
    "path": "ui/conf/db/migration/V1__create_schema.sql",
    "content": "create table \"advices\" (\n  \"id\" bigint not null primary key,\n  \"error_text_raw\" varchar not null,\n  \"compilation_error\" varchar not null,\n  \"advice\" varchar not null,\n  \"state\" smallint not null,\n  \"library_group_id\" varchar not null,\n  \"library_artifact_id\" varchar not null,\n  \"library_version\" varchar not null,\n  \"contributor_email\" varchar,\n  \"contributor_twitter\" varchar,\n  \"contributor_github\" varchar,\n  \"comment\" varchar\n);\n"
  },
  {
    "path": "ui/conf/db/migration/V2__add_pattern.sql",
    "content": "alter table \"advices\" add column \"pattern_raw\" varchar not null default '';\n"
  },
  {
    "path": "ui/conf/logback.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n%rEx</pattern>\n        </encoder>\n    </appender>\n\n    <logger name=\"application\" level=\"DEBUG\" additivity=\"false\">\n        <appender-ref ref=\"STDOUT\" />\n    </logger>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\" />\n    </root>\n\n</configuration>"
  },
  {
    "path": "ui/conf/routes",
    "content": "# Routes\n# This file defines all application routes (Higher priority routes first)\n# ~~~~\n\n# Home page\nGET     /                           controllers.ApplicationController.index\n\n# Map static resources from the /public folder to the /assets URL path\nGET     /assets/*file               controllers.Assets.versioned(path=\"/public\", file: Asset)\n\n# Webjars\nGET     /webjars/*file              controllers.WebJarAssets.at(file)\n\nGET     /api/advices                controllers.AdvicesController.get\n\n# Autowire calls\nPOST    /ui-api/*path               controllers.AutowireController.autowireApi(path: String)"
  },
  {
    "path": "ui/test/dal/AdvicesRepositoryTest.scala",
    "content": "package dal\n\nimport com.softwaremill.clippy._\nimport com.softwaremill.id.DefaultIdGenerator\nimport util.BaseSqlSpec\nimport org.scalatest.concurrent.ScalaFutures\nimport org.scalatest.concurrent.IntegrationPatience\n\nclass AdvicesRepositoryTest extends BaseSqlSpec with ScalaFutures with IntegrationPatience {\n  it should \"store & read an advice\" in {\n    // given\n    val ar = new AdvicesRepository(database, new DefaultIdGenerator())\n\n    // when\n    val stored = ar\n      .store(\n        \"zzz\",\n        \"yyy\",\n        TypeMismatchError[RegexT](RegexT(\"x\"), None, RegexT(\"y\"), None, None),\n        \"z\",\n        AdviceState.Pending,\n        Library(\"g\", \"a\", \"1\"),\n        Contributor(None, None, Some(\"t\")),\n        Some(\"c\")\n      )\n      .futureValue\n\n    // then\n    val r = ar.findAll().futureValue\n    r should have size (1)\n\n    val found = r.head\n\n    stored should be(found)\n    found.errorTextRaw should be(\"zzz\")\n    found.patternRaw should be(\"yyy\")\n    found.compilationError should be(TypeMismatchError(RegexT(\"x\"), None, RegexT(\"y\"), None, None))\n    found.advice should be(\"z\")\n    found.state should be(AdviceState.Pending)\n    found.library should be(Library(\"g\", \"a\", \"1\"))\n    found.contributor should be(Contributor(None, None, Some(\"t\")))\n    found.comment should be(Some(\"c\"))\n  }\n}\n"
  },
  {
    "path": "ui/test/util/BaseSqlSpec.scala",
    "content": "package util\n\nimport org.scalatest.concurrent.ScalaFutures\nimport org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach, FlatSpec, Matchers}\n\nimport scala.concurrent.ExecutionContext\n\ntrait BaseSqlSpec extends FlatSpec with Matchers with BeforeAndAfterAll with BeforeAndAfterEach with ScalaFutures {\n\n  private val connectionString = \"jdbc:h2:mem:clippy_test\" + this.getClass.getSimpleName + \";DB_CLOSE_DELAY=-1\"\n\n  lazy val database = SqlDatabase.createEmbedded(connectionString)\n\n  override protected def beforeAll() {\n    super.beforeAll()\n    createAll()\n  }\n\n  override protected def afterAll() {\n    super.afterAll()\n    dropAll()\n    database.close()\n  }\n\n  private def dropAll() {\n    import database.driver.api._\n    database.db.run(sqlu\"DROP ALL OBJECTS\").futureValue\n  }\n\n  private def createAll() {\n    database.updateSchema()\n  }\n\n  override protected def afterEach() {\n    try {\n      dropAll()\n      createAll()\n    } catch {\n      case e: Exception => e.printStackTrace()\n    }\n\n    super.afterEach()\n  }\n\n  implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global\n}\n"
  },
  {
    "path": "ui-client/src/main/scala/com/softwaremill/clippy/App.scala",
    "content": "package com.softwaremill.clippy\n\nimport japgolly.scalajs.react._\nimport autowire._\nimport japgolly.scalajs.react.vdom.prefix_<^._\nimport scala.concurrent.ExecutionContext.Implicits.global\nimport scala.concurrent.Future\nimport scala.util.{Failure, Success}\n\nobject App {\n  sealed trait Page\n  case object UsePage                                                                       extends Page\n  case object ContributeStep1InputError                                                     extends Page\n  case class ContributeParseError(errorText: String)                                        extends Page\n  case class ContributeStep2EditPattern(errorTextRaw: String, ce: CompilationError[ExactT]) extends Page\n  case class ContributeStep3SubmitAdvice(\n      errorTextRaw: String,\n      patternText: String,\n      ceFromPattern: CompilationError[ExactT]\n  ) extends Page\n  case object ListingPage  extends Page\n  case object FeedbackPage extends Page\n\n  case class State(page: Page, errorMsgs: List[String], infoMsgs: List[String])\n\n  class Backend($ : BackendScope[Unit, State]) {\n    private val handleReset: Callback = clearMsgs >> $.modState(_.copy(page = ContributeStep1InputError))\n\n    private def handleErrorTextSubmitted(errorText: String): Callback =\n      CompilationErrorParser.parse(errorText) match {\n        case None     => clearMsgs >> $.modState(_.copy(page = ContributeParseError(errorText)))\n        case Some(ce) => clearMsgs >> $.modState(_.copy(page = ContributeStep2EditPattern(errorText, ce)))\n      }\n\n    private def handleSendParseError(errorText: String)(email: String): Callback =\n      handleFuture(\n        AutowireClient[UiApi].sendCannotParse(errorText, email).call(),\n        Some(\"Error submitted successfully! We'll get in touch soon.\"),\n        Some((_: Unit) => $.modState(s => s.copy(page = ContributeStep1InputError)))\n      )\n\n    private def handlePatternSubmitted(\n        errorTextRaw: String,\n        patternText: String,\n        ceFromPattern: CompilationError[ExactT]\n    ): Callback =\n      $.modState(s => s.copy(page = ContributeStep3SubmitAdvice(errorTextRaw, patternText, ceFromPattern)))\n\n    private def handleSendAdviceProposal(ap: AdviceProposal): Callback =\n      handleFuture(\n        AutowireClient[UiApi].sendAdviceProposal(ap).call(),\n        Some(\"Advice submitted successfully!\"),\n        Some((_: Unit) => $.modState(s => s.copy(page = ContributeStep1InputError)))\n      )\n\n    private lazy val handleFuture = new HandleFuture {\n      override def apply[T](f: Future[T], successMsg: Option[String], successCallback: Option[(T) => Callback]) =\n        CallbackTo(f onComplete {\n          case Success(v) =>\n            val msgCallback = successMsg map handleShowInfo\n            val vCallback   = successCallback map (_(v))\n\n            (msgCallback.getOrElse(Callback.empty) >> vCallback.getOrElse(Callback.empty)).runNow()\n\n          case Failure(e) => handleShowError(\"Error communicating with the server\").runNow()\n        })\n    }\n\n    private def handleShowError(error: String): Callback =\n      clearMsgs >> $.modState(s => s.copy(errorMsgs = error :: s.errorMsgs))\n\n    private def handleShowInfo(info: String): Callback =\n      clearMsgs >> $.modState(s => s.copy(infoMsgs = info :: s.infoMsgs))\n\n    private def clearMsgs = $.modState(_.copy(errorMsgs = Nil, infoMsgs = Nil))\n\n    private def handleSwitchPage(newPage: Page): Callback =\n      clearMsgs >> $.modState { s =>\n        def isContribute(p: Page) = p != UsePage && p != ListingPage && p != FeedbackPage\n        if (s.page == newPage || (isContribute(s.page) && isContribute(newPage))) s else s.copy(page = newPage)\n      }\n\n    private def showMsgs(s: State) = <.span(\n      s.infoMsgs.map(m => <.div(^.cls := \"alert alert-success\", ^.role := \"alert\")(m)) ++\n        s.errorMsgs.map(m => <.div(^.cls := \"alert alert-danger\", ^.role := \"alert\")(m)): _*\n    )\n\n    private def showPage(s: State) = s.page match {\n      case UsePage =>\n        Use.component()\n\n      case ContributeStep1InputError =>\n        Contribute.Step1InputError.component(\n          Contribute.Step1InputError.Props(handleErrorTextSubmitted, handleShowError)\n        )\n\n      case ContributeParseError(et) =>\n        Contribute.ParseError.component(\n          Contribute.ParseError.Props(handleReset, handleSendParseError(et), handleShowError)\n        )\n\n      case ContributeStep2EditPattern(errorTextRaw, ce) =>\n        Contribute.Step2EditPattern.component(\n          Contribute.Step2EditPattern.Props(errorTextRaw, ce, handleReset, handlePatternSubmitted, handleShowError)\n        )\n\n      case ContributeStep3SubmitAdvice(errorTextRaw, pattern, ceFromPattern) =>\n        Contribute.Step3SubmitAdvice.component(\n          Contribute.Step3SubmitAdvice\n            .Props(errorTextRaw, pattern, ceFromPattern, handleReset, handleSendAdviceProposal, handleShowError)\n        )\n\n      case ListingPage =>\n        Listing.component(Listing.Props(handleShowError, clearMsgs, handleFuture))\n\n      case FeedbackPage =>\n        Feedback.component(Feedback.Props(handleShowError, clearMsgs, handleFuture))\n    }\n\n    def render(s: State) = <.span(\n      Menu.component((s.page, handleSwitchPage)),\n      <.div(^.cls := \"container\")(\n        showMsgs(s),\n        showPage(s)\n      )\n    )\n  }\n}\n\ntrait HandleFuture {\n  def apply[T](f: Future[T], successMsg: Option[String], successCallback: Option[T => Callback]): Callback\n}\n"
  },
  {
    "path": "ui-client/src/main/scala/com/softwaremill/clippy/AutowireClient.scala",
    "content": "package com.softwaremill.clippy\n\nimport org.scalajs.dom\nimport scala.concurrent.Future\nimport scalajs.concurrent.JSExecutionContext.Implicits.queue\nimport upickle.default._\nimport upickle.Js\n\nobject AutowireClient extends autowire.Client[Js.Value, Reader, Writer] {\n  override def doCall(req: Request): Future[Js.Value] =\n    dom.ext.Ajax\n      .post(\n        url = \"/ui-api/\" + req.path.mkString(\"/\"),\n        data = upickle.json.write(Js.Obj(req.args.toSeq: _*))\n      )\n      .map(_.responseText)\n      .map(upickle.json.read)\n\n  def read[Result: Reader](p: Js.Value) = readJs[Result](p)\n  def write[Result: Writer](r: Result)  = writeJs(r)\n}\n"
  },
  {
    "path": "ui-client/src/main/scala/com/softwaremill/clippy/BsUtils.scala",
    "content": "package com.softwaremill.clippy\n\nimport japgolly.scalajs.react._\nimport japgolly.scalajs.react.extra.ExternalVar\nimport japgolly.scalajs.react.vdom.prefix_<^._\n\nobject BsUtils {\n  def bsPanel(body: TagMod*) = <.div(^.cls := \"panel panel-default\") {\n    <.div(^.cls := \"panel-body\")(body)\n  }\n\n  // Maybe this could be a component? But a function works for now\n  def bsFormEl(ev: ExternalVar[FormField])(body: Seq[TagMod] => ReactTag) = {\n    val formField = ev.value\n    val elId      = Utils.randomString(8)\n    <.div(^.cls := \"form-group\", formField.error ?= (^.cls := \"has-error\"))(\n      <.label(^.htmlFor := elId, if (formField.required) <.strong(formField.label) else formField.label),\n      body(\n        Seq(\n          ^.id := elId,\n          formField.required ?= (^.required := \"required\"),\n          ^.value := formField.v,\n          ^.onChange ==> ((e: ReactEventI) => ev.set(formField.copy(v = e.target.value)))\n        )\n      )\n    )\n  }\n}\n"
  },
  {
    "path": "ui-client/src/main/scala/com/softwaremill/clippy/Contribute.scala",
    "content": "package com.softwaremill.clippy\n\nimport japgolly.scalajs.react._\nimport japgolly.scalajs.react.vdom.prefix_<^._\nimport BsUtils._\nimport Utils._\nimport monocle.macros.Lenses\n\nobject Contribute {\n  object Step1InputError {\n    case class Props(next: String => Callback, showError: String => Callback)\n\n    @Lenses\n    case class State(errorText: FormField)\n    implicit val stateVal = new Validatable[State] {\n      override def validated(s: State) = s.copy(errorText = s.errorText.validated)\n      override def fields(s: State)    = List(s.errorText)\n    }\n\n    class Backend($ : BackendScope[Props, State]) {\n      def render(s: State, p: Props) = <.div(\n        bsPanel(\n          <.p(\n            \"Scala Clippy is only as good as its advice database. Help other users by submitting an advice for a compilation error that you have encountered!\"\n          ),\n          <.p(\"First, paste in the error and we'll see if we can parse it. Only the error message is needed, e.g.:\"),\n          <.pre(\n            \"\"\"[error] type mismatch;\n              |[error] found   : akka.http.scaladsl.server.StandardRoute\n              |[error] required: akka.stream.scaladsl.Flow[akka.http.scaladsl.model.HttpRequest,akka.http.scaladsl.model.HttpResponse,Any]\"\"\".stripMargin\n          ),\n          <.p(\"You will be able to edit the pattern that will be used when matching errors later.\")\n        ),\n        <.form(\n          ^.onSubmit ==> FormField.submitValidated($, p.showError)(s => p.next(s.errorText.v)),\n          bsFormEl(externalVar($, s, State.errorText))(mods => <.textarea(^.cls := \"form-control\", ^.rows := 5)(mods)),\n          <.button(^.`type` := \"submit\", ^.cls := \"btn btn-primary\")(\"Next\")\n        )\n      )\n    }\n\n    val component = ReactComponentB[Props](\"Step1InputError\")\n      .initialState(State(FormField(\"Error text\", required = true)))\n      .renderBackend[Backend]\n      .build\n  }\n\n  object Step2EditPattern {\n    case class Props(\n        errorTextRaw: String,\n        ce: CompilationError[ExactT],\n        reset: Callback,\n        next: (String, String, CompilationError[ExactT]) => Callback,\n        showError: String => Callback\n    )\n\n    @Lenses\n    case class State(patternText: FormField)\n    implicit val stateVal = new Validatable[State] {\n      override def validated(s: State) = s.copy(patternText = s.patternText.validated)\n      override def fields(s: State)    = List(s.patternText)\n    }\n\n    class Backend($ : BackendScope[Props, State]) {\n      def render(s: State, p: Props) = {\n        val parsedPattern       = CompilationErrorParser.parse(s.patternText.v)\n        val errorMatchesPattern = parsedPattern.map(_.asRegex).map(_.matches(p.ce))\n\n        val alert = errorMatchesPattern match {\n          case None =>\n            <.div(^.cls := \"alert alert-danger\", ^.role := \"alert\")(\"Cannot parse the error\")\n          case Some(false) =>\n            <.div(^.cls := \"alert alert-danger\", ^.role := \"alert\")(\"The pattern doesn't match the original error\")\n          case Some(true) =>\n            <.div(^.cls := \"alert alert-success\", ^.role := \"alert\")(\"The pattern matches the original error\")\n        }\n\n        val canProceed = errorMatchesPattern.getOrElse(false)\n\n        val nextCallback = parsedPattern match {\n          case None                => Callback.empty\n          case Some(ceFromPattern) => p.next(p.errorTextRaw, s.patternText.v, ceFromPattern)\n        }\n\n        <.div(\n          bsPanel(\n            <.p(\"Parsing successfull! Here's what we've found:\"),\n            <.pre(p.ce.toString),\n            <.p(\n              \"You can now create a pattern for matching errors using a wildcard character: *. For example, you can generalize a pattern by replacing concrete type parameters with *:\"\n            ),\n            <.pre(\"cats.Monad[List] -> cats.Monad[*]\"),\n            <.p(\"Or, you can just click next and leave the submitted error to be an exact pattern.\")\n          ),\n          alert,\n          <.form(\n            ^.onSubmit ==> FormField.submitValidated($, p.showError)(_ => nextCallback),\n            bsFormEl(externalVar($, s, State.patternText))(\n              mods => <.textarea(^.cls := \"form-control\", ^.rows := 5)(mods)\n            ),\n            <.button(^.`type` := \"reset\", ^.cls := \"btn btn-default\", ^.onClick --> p.reset)(\"Reset\"),\n            <.span(\" \"),\n            <.button(^.`type` := \"submit\", ^.cls := \"btn btn-primary\", ^.disabled := !canProceed)(\"Next\")\n          )\n        )\n      }\n    }\n\n    val component = ReactComponentB[Props](\"Step2EditPattern\")\n      .initialState_P { p =>\n        State(FormField(\"Pattern\", required = true, p.errorTextRaw, error = false))\n      }\n      .renderBackend[Backend]\n      .build\n  }\n\n  object Step3SubmitAdvice {\n    case class Props(\n        errorTextRaw: String,\n        patternRaw: String,\n        ceFromPattern: CompilationError[ExactT],\n        reset: Callback,\n        send: AdviceProposal => Callback,\n        showError: String => Callback\n    )\n\n    @Lenses\n    case class State(\n        advice: FormField,\n        libraryGroupId: FormField,\n        libraryArtifactId: FormField,\n        libraryVersion: FormField,\n        email: FormField,\n        twitter: FormField,\n        github: FormField,\n        comment: FormField\n    )\n\n    implicit val stateVal = new Validatable[State] {\n      override def validated(s: State) = s.copy(\n        advice = s.advice.validated,\n        libraryGroupId = s.libraryGroupId.validated,\n        libraryArtifactId = s.libraryArtifactId.validated,\n        libraryVersion = s.libraryVersion.validated,\n        email = s.email.validated,\n        twitter = s.twitter.validated,\n        github = s.github.validated,\n        comment = s.comment.validated\n      )\n      override def fields(s: State) =\n        List(\n          s.advice,\n          s.libraryGroupId,\n          s.libraryArtifactId,\n          s.libraryVersion,\n          s.email,\n          s.twitter,\n          s.github,\n          s.comment\n        )\n    }\n\n    class Backend($ : BackendScope[Props, State]) {\n      def render(s: State, p: Props) = <.div(\n        bsPanel(\n          <.p(\"You can now define the advice associated with the error. Here's the pattern you have entered:\"),\n          <.pre(p.ceFromPattern.toString),\n          <.p(\n            \"You can optionally leave an e-mail so that we can let you know when your proposal is accepted, and a twitter/github handle so that we can add proper attribution, visible in the advice browser.\"\n          )\n        ),\n        <.form(\n          ^.onSubmit ==> FormField.submitValidated($, p.showError)(\n            s =>\n              p.send(\n                AdviceProposal(\n                  p.errorTextRaw,\n                  p.patternRaw,\n                  p.ceFromPattern.asRegex,\n                  s.advice.v,\n                  Library(s.libraryGroupId.v, s.libraryArtifactId.v, s.libraryVersion.v),\n                  Contributor(s.email.vOpt, s.twitter.vOpt, s.github.vOpt),\n                  s.comment.vOpt\n                )\n            )\n          ),\n          bsFormEl(externalVar($, s, State.advice))(mods => <.textarea(^.cls := \"form-control\", ^.rows := 3)(mods)),\n          <.hr,\n          bsFormEl(externalVar($, s, State.libraryGroupId))(\n            mods => <.input(^.`type` := \"text\", ^.cls := \"form-control\", ^.placeholder := \"org.scala\")(mods)\n          ),\n          bsFormEl(externalVar($, s, State.libraryArtifactId))(\n            mods => <.input(^.`type` := \"text\", ^.cls := \"form-control\", ^.placeholder := \"scala-lang\")(mods)\n          ),\n          bsFormEl(externalVar($, s, State.libraryVersion))(\n            mods => <.input(^.`type` := \"text\", ^.cls := \"form-control\", ^.placeholder := \"2.11-M3\")(mods)\n          ),\n          <.hr,\n          bsFormEl(externalVar($, s, State.email))(\n            mods =>\n              <.input(^.`type` := \"email\", ^.cls := \"form-control\", ^.placeholder := \"scalacoder@company.com\")(mods)\n          ),\n          bsFormEl(externalVar($, s, State.twitter))(\n            mods =>\n              <.div(^.cls := \"input-group\")(\n                <.div(^.cls := \"input-group-addon\")(\"@\"),\n                <.input(^.`type` := \"text\", ^.cls := \"form-control\", ^.placeholder := \"twitter\")(mods)\n            )\n          ),\n          bsFormEl(externalVar($, s, State.github))(\n            mods =>\n              <.div(^.cls := \"input-group\")(\n                <.div(^.cls := \"input-group-addon\")(\"@\"),\n                <.input(^.`type` := \"text\", ^.cls := \"form-control\", ^.placeholder := \"github\")(mods)\n            )\n          ),\n          <.hr,\n          bsFormEl(externalVar($, s, State.comment))(mods => <.textarea(^.cls := \"form-control\", ^.rows := 3)(mods)),\n          <.button(^.`type` := \"reset\", ^.cls := \"btn btn-default\", ^.onClick --> p.reset)(\"Reset\"),\n          <.span(\" \"),\n          <.button(^.`type` := \"submit\", ^.cls := \"btn btn-primary\")(\"Send\")\n        )\n      )\n    }\n\n    val component = ReactComponentB[Props](\"Step3SubmitAdvice\")\n      .initialState(\n        State(\n          FormField(\"Advice\", required = true),\n          FormField(\"Library group id\", required = true),\n          FormField(\"Library artifact id\", required = true),\n          FormField(\"Library version\", required = true),\n          FormField(\"E-mail (optional)\", required = false),\n          FormField(\"Twitter handle (optional)\", required = false),\n          FormField(\"Github handle (optional)\", required = false),\n          FormField(\"Comment (optional)\", required = false)\n        )\n      )\n      .renderBackend[Backend]\n      .build\n  }\n\n  object ParseError {\n    case class Props(reset: Callback, send: String => Callback, showError: String => Callback)\n\n    @Lenses\n    case class State(email: FormField)\n    implicit val stateVal = new Validatable[State] {\n      override def validated(s: State) = s.copy(email = s.email.validated)\n      override def fields(s: State)    = List(s.email)\n    }\n\n    class Backend($ : BackendScope[Props, State]) {\n      def render(s: State, p: Props) = <.div(\n        bsPanel(\n          <.p(\n            \"Unfortunately we cannot parse the error. Let us know how to contact you, we'll try to find out what's wrong and get back to you.\"\n          )\n        ),\n        <.form(\n          ^.onSubmit ==> FormField.submitValidated($, p.showError)(s => p.send(s.email.v)),\n          bsFormEl(externalVar($, s, State.email))(\n            mods =>\n              <.input(^.`type` := \"email\", ^.cls := \"form-control\", ^.placeholder := \"scalacoder@company.com\")(mods)\n          ),\n          <.button(^.`type` := \"reset\", ^.cls := \"btn btn-default\", ^.onClick --> p.reset)(\"Reset\"),\n          <.span(\" \"),\n          <.button(^.`type` := \"submit\", ^.cls := \"btn btn-primary\")(\"Send\")\n        )\n      )\n    }\n\n    val component = ReactComponentB[Props](\"ParseError\")\n      .initialState(State(FormField(\"Email\", required = true)))\n      .renderBackend[Backend]\n      .build\n  }\n}\n"
  },
  {
    "path": "ui-client/src/main/scala/com/softwaremill/clippy/Feedback.scala",
    "content": "package com.softwaremill.clippy\n\nimport autowire._\nimport com.softwaremill.clippy.BsUtils._\nimport com.softwaremill.clippy.Utils._\nimport japgolly.scalajs.react._\nimport japgolly.scalajs.react.vdom.prefix_<^._\nimport monocle.macros.Lenses\nimport scala.concurrent.ExecutionContext.Implicits.global\n\nobject Feedback {\n  case class Props(showError: String => Callback, clearMsgs: Callback, handleFuture: HandleFuture)\n\n  @Lenses\n  case class State(contact: FormField, feedback: FormField)\n  implicit val stateVal = new Validatable[State] {\n    override def validated(s: State) = s.copy(\n      contact = s.contact.validated,\n      feedback = s.feedback.validated\n    )\n    override def fields(s: State) = List(s.contact, s.feedback)\n  }\n\n  class Backend($ : BackendScope[Props, State]) {\n    def render(s: State, p: Props) = {\n      def sendFeedbackCallback() =\n        p.handleFuture(\n          AutowireClient[UiApi].feedback(s.feedback.v, s.contact.v).call(),\n          Some(\"Feedback sent, thank you!\"),\n          Some(\n            (_: Unit) => $.modState(s => s.copy(contact = s.contact.copy(v = \"\"), feedback = s.feedback.copy(v = \"\")))\n          )\n        )\n\n      <.form(\n        ^.onSubmit ==> FormField.submitValidated($, p.showError)(s => sendFeedbackCallback()),\n        bsFormEl(externalVar($, s, State.contact))(\n          mods =>\n            <.input(^.`type` := \"email\", ^.cls := \"form-control\", ^.placeholder := \"scalacoder@company.com\")(mods)\n        ),\n        bsFormEl(externalVar($, s, State.feedback))(mods => <.textarea(^.cls := \"form-control\", ^.rows := \"3\")(mods)),\n        <.button(^.`type` := \"send\", ^.cls := \"btn btn-primary\")(\"Send\")\n      )\n    }\n  }\n\n  val component = ReactComponentB[Props](\"Use\")\n    .initialState(\n      State(\n        FormField(\"Contact email\", required = true),\n        FormField(\"Feedback\", required = true)\n      )\n    )\n    .renderBackend[Backend]\n    .build\n}\n"
  },
  {
    "path": "ui-client/src/main/scala/com/softwaremill/clippy/FormField.scala",
    "content": "package com.softwaremill.clippy\n\nimport japgolly.scalajs.react._\n\ncase class FormField(label: String, required: Boolean, v: String, error: Boolean) {\n  def validated =\n    if (required) {\n      if (v.isEmpty) copy(error = true) else copy(error = false)\n    } else this\n  def vOpt: Option[String] = if (v.isEmpty) None else Some(v)\n}\nobject FormField {\n  def apply(label: String, required: Boolean): FormField = FormField(label, required, \"\", error = false)\n  def errorMsgIfAny(fields: Seq[FormField]): Option[String] =\n    fields.find(_.error).map(ff => s\"${ff.label} is required\") // required is the only type of error there could be\n\n  def submitValidated[P, S: Validatable](\n      $ : BackendScope[P, S],\n      showError: String => Callback\n  )(submit: S => Callback)(e: ReactEventI): Callback =\n    for {\n      _     <- e.preventDefaultCB\n      props <- $.props\n      s     <- $.state\n      v  = implicitly[Validatable[S]]\n      s2 = v.validated(s)\n      _ <- $.setState(s2)\n      fields = v.fields(s2)\n      em     = errorMsgIfAny(fields)\n      _ <- em.fold(submit(s2))(showError)\n    } yield ()\n}\n\ntrait Validatable[S] {\n  def validated(s: S): S\n  def fields(s: S): Seq[FormField]\n}\n"
  },
  {
    "path": "ui-client/src/main/scala/com/softwaremill/clippy/Listing.scala",
    "content": "package com.softwaremill.clippy\n\nimport com.softwaremill.clippy.BsUtils._\nimport com.softwaremill.clippy.Utils._\nimport japgolly.scalajs.react._\nimport japgolly.scalajs.react.vdom.prefix_<^._\nimport autowire._\nimport monocle.macros.Lenses\nimport scala.concurrent.ExecutionContext.Implicits.global\n\nobject Listing {\n  case class Props(showError: String => Callback, clearMsgs: Callback, handleFuture: HandleFuture)\n\n  @Lenses\n  case class State(\n      advices: Seq[AdviceListing],\n      suggestEditId: Option[Long],\n      suggestContact: FormField,\n      suggestText: FormField\n  )\n  implicit val stateVal = new Validatable[State] {\n    override def validated(s: State) = s.copy(\n      suggestContact = s.suggestContact.validated,\n      suggestText = s.suggestText.validated\n    )\n    override def fields(s: State) = List(s.suggestContact, s.suggestText)\n  }\n\n  class Backend($ : BackendScope[Props, State]) {\n    def render(s: State, p: Props) = {\n      def suggestEditCallback(a: AdviceListing) =\n        p.clearMsgs >> $.modState(s => s.copy(suggestEditId = Some(a.id), suggestText = s.suggestText.copy(v = \"\")))\n      def cancelSuggestEditCallback(a: AdviceListing) = $.modState(_.copy(suggestEditId = None))\n      def sendSuggestEditCallback(a: AdviceListing) =\n        cancelSuggestEditCallback(a) >> p.handleFuture(\n          AutowireClient[UiApi].sendSuggestEdit(s.suggestText.v, s.suggestContact.v, a).call(),\n          Some(\"Suggestion sent, thank you!\"),\n          None\n        )\n\n      def rowForAdvice(a: AdviceListing) = <.tr(\n        <.td(<.pre(a.compilationError.toString)),\n        <.td(a.advice),\n        <.td(a.library.toString),\n        <.td(<.span(^.cls := \"glyphicon glyphicon-edit\", ^.onClick --> suggestEditCallback(a)))\n      )\n\n      def suggestEdit(a: AdviceListing) = <.tr(\n        <.td(^.colSpan := 4)(\n          <.form(\n            ^.onSubmit ==> FormField.submitValidated($, p.showError)(s => sendSuggestEditCallback(a)),\n            bsFormEl(externalVar($, s, State.suggestContact))(\n              mods =>\n                <.input(^.`type` := \"email\", ^.cls := \"form-control\", ^.placeholder := \"scalacoder@company.com\")(mods)\n            ),\n            bsFormEl(externalVar($, s, State.suggestText))(\n              mods => <.textarea(^.cls := \"form-control\", ^.rows := \"3\")(mods)\n            ),\n            <.button(^.`type` := \"reset\", ^.cls := \"btn btn-default\", ^.onClick --> cancelSuggestEditCallback(a))(\n              \"Cancel\"\n            ),\n            <.span(\" \"),\n            <.button(^.`type` := \"send\", ^.cls := \"btn btn-primary\")(\"Send\")\n          )\n        )\n      )\n\n      <.table(^.cls := \"table table-striped advice-listing\")(\n        <.thead(\n          <.tr(\n            <.th(\"Compilation error (as regex)\"),\n            <.th(^.width := \"25%\")(\"Advice\"),\n            <.th(^.width := \"20%\")(\"Library\"),\n            <.th(^.width := \"8%\")(\"Suggest edit\")\n          )\n        ),\n        <.tbody(\n          s.advices.flatMap(\n            a => List(rowForAdvice(a)) ++ s.suggestEditId.filter(_ == a.id).map(_ => suggestEdit(a)).toList\n          ): _*\n        )\n      )\n    }\n\n    def initAdvices(p: Props): Callback =\n      p.handleFuture(\n        AutowireClient[UiApi].listAccepted().call(),\n        None,\n        Some((s: Seq[AdviceListing]) => $.modState(_.copy(advices = s)))\n      )\n  }\n\n  val component = ReactComponentB[Props](\"Use\")\n    .initialState(\n      State(\n        Nil,\n        None,\n        FormField(\"Contact email (optional)\", required = false),\n        FormField(\"Suggestion\", required = true)\n      )\n    )\n    .renderBackend[Backend]\n    .componentDidMount(ctx => ctx.backend.initAdvices(ctx.props))\n    .build\n}\n"
  },
  {
    "path": "ui-client/src/main/scala/com/softwaremill/clippy/Main.scala",
    "content": "package com.softwaremill.clippy\n\nimport japgolly.scalajs.react._\nimport org.scalajs.jquery._\nimport scala.scalajs.js\nimport japgolly.scalajs.react.vdom.prefix_<^._\n\nobject Main extends js.JSApp {\n  type HtmlId = String\n\n  def main(): Unit =\n    jQuery(setupUI _)\n\n  def setupUI(): Unit = {\n    val mountNode = org.scalajs.dom.document.getElementById(\"reactmain\")\n\n    val app = ReactComponentB[Unit](\"App\")\n      .initialState(App.State(App.UsePage, Nil, Nil))\n      .renderBackend[App.Backend]\n      .build\n\n    ReactDOM.render(app(), mountNode)\n  }\n}\n"
  },
  {
    "path": "ui-client/src/main/scala/com/softwaremill/clippy/Menu.scala",
    "content": "package com.softwaremill.clippy\n\nimport com.softwaremill.clippy.App._\nimport japgolly.scalajs.react._\nimport japgolly.scalajs.react.vdom.prefix_<^._\n\nobject Menu {\n  val component = ReactComponentB[(Page, Page => Callback)](\"Menu\").render { $ =>\n    val isUsePage                         = $.props._1 == UsePage\n    val isListingPage                     = $.props._1 == ListingPage\n    val isFeedbackPage                    = $.props._1 == FeedbackPage\n    val isContributePage                  = !isUsePage && !isListingPage && !isFeedbackPage\n    def switchTo(p: Page)(e: ReactEventI) = e.preventDefaultCB >> $.props._2(p)\n\n    <.nav(^.cls := \"navbar navbar-inverse navbar-fixed-top\")(\n      <.div(^.cls := \"container\")(\n        <.div(^.cls := \"navbar-header\")(\n          <.button(\n            ^.`type` := \"button\",\n            ^.cls := \"navbar-toggle collapsed\",\n            \"data-toggle\".reactAttr := \"collapse\",\n            \"data-target\".reactAttr := \"navbar\",\n            \"aria-expanded\".reactAttr := \"false\",\n            \"aria-controls\".reactAttr := \"navbar\"\n          )(\n            <.span(^.cls := \"sr-only\")(\"Toggle navigation\"),\n            <.span(^.cls := \"icon-bar\"),\n            <.span(^.cls := \"icon-bar\"),\n            <.span(^.cls := \"icon-bar\")\n          ),\n          <.a(^.cls := \"navbar-brand\", ^.onClick ==> switchTo(UsePage), ^.href := \"#\")(\"Scala Clippy\")\n        ),\n        <.div(^.id := \"navbar\", ^.cls := \"collapse navbar-collapse\")(\n          <.ul(^.cls := \"nav navbar-nav\")(\n            <.li(isUsePage ?= (^.cls := \"active\"))(\n              <.a(\"Use\", ^.onClick ==> switchTo(UsePage), ^.href := \"#\")\n            ),\n            <.li(isContributePage ?= (^.cls := \"active\"))(\n              <.a(\"Contribute\", ^.onClick ==> switchTo(ContributeStep1InputError), ^.href := \"#\")\n            ),\n            <.li(isListingPage ?= (^.cls := \"active\"))(\n              <.a(\"Browse\", ^.onClick ==> switchTo(ListingPage), ^.href := \"#\")\n            ),\n            <.li(isFeedbackPage ?= (^.cls := \"active\"))(\n              <.a(\"Send feedback\", ^.onClick ==> switchTo(FeedbackPage), ^.href := \"#\")\n            )\n          )\n        )\n      )\n    )\n  }.build\n}\n"
  },
  {
    "path": "ui-client/src/main/scala/com/softwaremill/clippy/Use.scala",
    "content": "package com.softwaremill.clippy\n\nimport japgolly.scalajs.react._\nimport japgolly.scalajs.react.vdom.prefix_<^._\n\nobject Use {\n  val component = ReactComponentB[Unit](\"Use\").render { $ =>\n    val html = org.scalajs.dom.document.getElementById(\"use\").innerHTML\n    <.span(^.dangerouslySetInnerHtml(html))\n  }.build\n}\n"
  },
  {
    "path": "ui-client/src/main/scala/com/softwaremill/clippy/Utils.scala",
    "content": "package com.softwaremill.clippy\n\nimport japgolly.scalajs.react.BackendScope\nimport japgolly.scalajs.react.extra.ExternalVar\nimport monocle._\n\nimport scala.util.Random\n\nobject Utils {\n  def randomString(length: Int) = Random.alphanumeric take length mkString \"\"\n\n  // ExternalVar companion has only methods for creating a var from AccessRD (read direct), here we are reading\n  // through callbacks, so we need that extra method\n  def externalVar[S, A]($ : BackendScope[_, S], s: S, l: Lens[S, A]): ExternalVar[A] =\n    ExternalVar(l.get(s))(a => $.modState(l.set(a)))\n}\n"
  },
  {
    "path": "ui-shared/src/main/scala/com/softwaremill/clippy/AdviceState.scala",
    "content": "package com.softwaremill.clippy\n\nobject AdviceState extends Enumeration {\n  type AdviceState = Value\n  val Pending, Accepted, Rejected = Value\n}\n"
  },
  {
    "path": "ui-shared/src/main/scala/com/softwaremill/clippy/Contributor.scala",
    "content": "package com.softwaremill.clippy\n\ncase class Contributor(email: Option[String], twitter: Option[String], github: Option[String])\n"
  },
  {
    "path": "ui-shared/src/main/scala/com/softwaremill/clippy/UiApi.scala",
    "content": "package com.softwaremill.clippy\n\nimport scala.concurrent.Future\n\ntrait UiApi extends ContributeApi with ListingApi with FeedbackApi\n\ntrait FeedbackApi {\n  def feedback(text: String, contactEmail: String): Future[Unit]\n}\n\ntrait ContributeApi {\n  def sendCannotParse(errorText: String, contributorEmail: String): Future[Unit]\n  def sendAdviceProposal(adviceProposal: AdviceProposal): Future[Unit]\n}\n\ntrait ListingApi {\n  def listAccepted(): Future[Seq[AdviceListing]]\n  def sendSuggestEdit(text: String, contactEmail: String, adviceListing: AdviceListing): Future[Unit]\n}\n\ncase class AdviceProposal(\n    errorTextRaw: String,\n    patternRaw: String,\n    compilationError: CompilationError[RegexT],\n    advice: String,\n    library: Library,\n    contributor: Contributor,\n    comment: Option[String]\n)\n\ncase class ContributorListing(twitter: Option[String], github: Option[String])\n\ncase class AdviceListing(\n    id: Long,\n    compilationError: CompilationError[RegexT],\n    advice: String,\n    library: Library,\n    contributor: ContributorListing\n)\n"
  }
]