Full Code of softwaremill/scala-clippy for AI

master 6829f56c1042 cached
77 files
185.5 KB
50.1k tokens
1 symbols
1 requests
Download .txt
Showing preview only (208K chars total). Download the full file or copy to clipboard to get everything.
Repository: softwaremill/scala-clippy
Branch: master
Commit: 6829f56c1042
Files: 77
Total size: 185.5 KB

Directory structure:
gitextract_gwq_0d06/

├── .gitignore
├── .scalafmt.conf
├── .travis.yml
├── LICENSE.txt
├── README.md
├── build.sbt
├── model/
│   └── src/
│       ├── main/
│       │   └── scala/
│       │       └── com/
│       │           └── softwaremill/
│       │               └── clippy/
│       │                   ├── Advice.scala
│       │                   ├── Clippy.scala
│       │                   ├── CompilationError.scala
│       │                   ├── CompilationErrorParser.scala
│       │                   ├── Library.scala
│       │                   ├── StringDiff.scala
│       │                   ├── Template.scala
│       │                   └── Warning.scala
│       └── test/
│           └── scala/
│               └── com/
│                   └── softwaremill/
│                       └── clippy/
│                           ├── CompilationErrorParserTest.scala
│                           ├── CompilationErrorTest.scala
│                           ├── LibraryProperties.scala
│                           ├── RegexTTest.scala
│                           ├── StringDiffSpecification.scala
│                           ├── StringDiffTest.scala
│                           └── TypeNamesGenerators.scala
├── package.json
├── plugin/
│   └── src/
│       └── main/
│           ├── resources/
│           │   └── scalac-plugin.xml
│           ├── scala/
│           │   └── com/
│           │       └── softwaremill/
│           │           └── clippy/
│           │               ├── AdviceLoader.scala
│           │               ├── ClippyPlugin.scala
│           │               ├── ColorsConfig.scala
│           │               ├── FailOnWarningsReporter.scala
│           │               ├── Highlighter.scala
│           │               ├── InjectReporter.scala
│           │               ├── RestoreReporter.scala
│           │               └── Utils.scala
│           ├── scala-2.11/
│           │   └── com/
│           │       └── softwaremill/
│           │           └── clippy/
│           │               ├── DelegatingPosition.scala
│           │               └── DelegatingReporter.scala
│           └── scala-2.12/
│               └── com/
│                   └── softwaremill/
│                       └── clippy/
│                           ├── DelegatingPosition.scala
│                           └── DelegatingReporter.scala
├── plugin-sbt/
│   └── src/
│       └── main/
│           └── scala/
│               └── com/
│                   └── softwaremill/
│                       └── clippy/
│                           └── ClippySbtPlugin.scala
├── project/
│   ├── build.properties
│   └── plugins.sbt
├── tests/
│   └── src/
│       └── test/
│           └── scala/
│               └── org/
│                   └── softwaremill/
│                       └── clippy/
│                           └── CompileTests.scala
├── ui/
│   ├── app/
│   │   ├── ClippyApplicationLoader.scala
│   │   ├── api/
│   │   │   └── UiApiImpl.scala
│   │   ├── assets/
│   │   │   └── stylesheets/
│   │   │       └── main.less
│   │   ├── controllers/
│   │   │   ├── AdvicesController.scala
│   │   │   ├── ApplicationController.scala
│   │   │   ├── AutowireController.scala
│   │   │   └── HttpsFilter.scala
│   │   ├── dal/
│   │   │   └── AdvicesRepository.scala
│   │   ├── util/
│   │   │   ├── ConfigWithDefault.scala
│   │   │   ├── DatabaseConfig.scala
│   │   │   ├── SqlDatabase.scala
│   │   │   ├── Zip.scala
│   │   │   └── email/
│   │   │       ├── DummyEmailService.scala
│   │   │       ├── EmailService.scala
│   │   │       └── SendgridEmailService.scala
│   │   └── views/
│   │       ├── index.scala.html
│   │       └── use.scala.html
│   ├── conf/
│   │   ├── application.conf
│   │   ├── db/
│   │   │   └── migration/
│   │   │       ├── V1__create_schema.sql
│   │   │       └── V2__add_pattern.sql
│   │   ├── logback.xml
│   │   └── routes
│   └── test/
│       ├── dal/
│       │   └── AdvicesRepositoryTest.scala
│       └── util/
│           └── BaseSqlSpec.scala
├── ui-client/
│   └── src/
│       └── main/
│           └── scala/
│               └── com/
│                   └── softwaremill/
│                       └── clippy/
│                           ├── App.scala
│                           ├── AutowireClient.scala
│                           ├── BsUtils.scala
│                           ├── Contribute.scala
│                           ├── Feedback.scala
│                           ├── FormField.scala
│                           ├── Listing.scala
│                           ├── Main.scala
│                           ├── Menu.scala
│                           ├── Use.scala
│                           └── Utils.scala
└── ui-shared/
    └── src/
        └── main/
            └── scala/
                └── com/
                    └── softwaremill/
                        └── clippy/
                            ├── AdviceState.scala
                            ├── Contributor.scala
                            └── UiApi.scala

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
node_modules
*.iml
*.ipr
*.iws
.idea/
target/
data/
*.log
.DS_Store
local.sbt


================================================
FILE: .scalafmt.conf
================================================
style = defaultWithAlign
maxColumn = 120
align.openParenCallSite = false
align.openParenDefnSite = false
danglingParentheses = true

rewrite.rules = [RedundantBraces, RedundantParens, SortImports, PreferCurlyFors]
rewrite.redundantBraces.includeUnitMethods = true
rewrite.redundantBraces.stringInterpolation = true


================================================
FILE: .travis.yml
================================================
sudo: false
language: scala
jdk:
- oraclejdk8
scala:
- 2.11.8
install:
- ". $HOME/.nvm/nvm.sh"
- nvm install stable
- nvm use stable
- npm install
script:
- sbt test
- '[ "${TRAVIS_PULL_REQUEST}" = "false" ] && sbt updateImpactSubmit || true'
notifications:
  slack:
    secure: BLLFmdz5G6098EfMgw+CXNqqnwQPDHhaXp0Bg3GWkUoAh3/BoWFhI6iTRgJW3q85IBLa6A2el0wLY8mK1lyD4mRg5dLTPWCILqb8eaF6Shop/FQi0O3pyACvfYUitW9EBalug9VVZzceKsHDaW9pbuqDxCWv1qWyBlTv7aSfVnyYlh6LC+3YqsWFeMHA7/W/lSAklvHkk7GTYIp3A30DsYup2K/EHMJ4ZxuA0CZOOrE4jOfEOzZgm787asPT8o12yb3CWEBZai3Fxs4s6I25w1t6Y5JTPXnYi01OIug6mDtZRDAh1ZJHUdyhOXbxqib6QbCrayg1ou3yde8pKm3KgnMMAJtu6wB8XCMlCDCJ81qkfOCY7TtUfax2spTD5xyIkoQxHPSqOnKyaBYXMKaQ4gMMGiJQToZ8hPtQTYDZTMK5VD5CS1kNMqJBI4V7YlFnKchbv1NUBTKA18QouTCVu369hBb2gANzI1IR4AoP+LRgFjCqEyAAcqoa1jkuENyF1KZykTa3NrkENBlzhEJofYwIi9TP8JUY8mCJp2dnLqnWU/PlJJBTEkMd/00My92znleo2S4gaDgd79quD7iJuZotJt5pkad9auh18ZFQcgmgo9dc3nS3yCk+/vVfPvYKP0pHRWe5RmPkaVbCZ6kCP79LIVlMcA1oYhpNRiwXe5o=


================================================
FILE: LICENSE.txt
================================================

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright 2013-2016 Softwaremill

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: README.md
================================================
# Scala clippy

[![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)
[![Build Status](https://travis-ci.org/softwaremill/scala-clippy.svg?branch=master)](https://travis-ci.org/softwaremill/scala-clippy)
[![Dependencies](https://app.updateimpact.com/badge/634276070333485056/clippy.svg?config=compile)](https://app.updateimpact.com/latest/634276070333485056/clippy)
[![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)

Enrich your Scala compiler error output with additional advices and colors!

![enriched error example](ui/app/assets/img/clippy-akka-err-rich.png "")

# Documentation

Read the detailed [documentation](https://scala-clippy.org).

# Contributing to the project

You can also help developing the plugin and/or the UI for submitting new advices! The module structure is:

* `model` - code shared between the UI and the plugin. Contains basic model case classes, such as `CompilationError` + parser
* `plugin` - the compiler plugin which actually displays the advices and matches errors agains the database of known errors
* `tests` - tests for the compiler plugin. Must be a separate project, as it requires the plugin jar to be ready
* `ui` - the ui server project in Play
* `ui-client` - the Scala.JS client-side code
* `ui-shared` - code shared between the UI server and UI client (but not needed for the plugin)

For 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).
If you want to write your own tests with compilation using `mkToolbox`, remember to add a `-P:clippy:testmode=true`
compiler option. It ensures that a correct reporter replacement mechanism is used, which needs to be different
specifically for tests. See [CompileTests.scala](tests/src/test/scala/org/softwaremill/clippy/CompileTests.scala) for reference.


To publish locally append "-SNAPSHOT" to the version number then run
````scala
sbt "project plugin" "+ publishLocal"
````

Run advice tests with
````scala
sbt tests/test
````

# Heroku deployment

Locally:

* Install the Heroku Toolbelt
* link the local git repository with the Heroku application: `heroku git:remote -a scala-clippy`
* run `sbt deployHeroku` to deploy the current code as a fat-jar

Currently deployed on `https://www.scala-clippy.org`

# Credits

Clippy contributors:

* [Krzysztof Ciesielski](https://github.com/kciesielski)
* [Łukasz Żuchowski](https://github.com/Zuchos)
* [Shane Delmore](https://github.com/ShaneDelmore)
* [Adam Warski](https://github.com/adamw)

Syntax highlighting code is copied from [Ammonite](http://www.lihaoyi.com/Ammonite/).


================================================
FILE: build.sbt
================================================
import sbt._
import Keys._
import sbtassembly.AssemblyKeys

import scala.xml.transform.RuleTransformer
import scala.xml.transform.RewriteRule
import scala.xml.{Node => XNode}
import scala.xml.{NodeSeq => XNodeSeq}
import scala.xml.{Elem => XElem}

val slickVersion = "3.1.1"

val json4s = "org.json4s" %% "json4s-native" % "3.5.0"

// testing
val scalatest  = "org.scalatest"  %% "scalatest"  % "3.0.1"  % "test"
val scalacheck = "org.scalacheck" %% "scalacheck" % "1.13.4" % "test"

name := "clippy"

// factor out common settings into a sequence
lazy val commonSettingsNoScalaVersion = Seq(
  organization := "com.softwaremill.clippy",
  version := "0.6.1",
  scalacOptions ++= Seq("-unchecked", "-deprecation"),
  parallelExecution := false,
  // Sonatype OSS deployment
  publishTo := {
    val nexus = "https://oss.sonatype.org/"
    if (version.value.trim.endsWith("SNAPSHOT"))
      Some("snapshots" at nexus + "content/repositories/snapshots")
    else
      Some("releases" at nexus + "service/local/staging/deploy/maven2")
  },
  credentials += Credentials(Path.userHome / ".ivy2" / ".credentials"),
  publishMavenStyle := true,
  publishArtifact in Test := false,
  pomIncludeRepository := { _ =>
    false
  },
  pomExtra :=
    <scm>
      <url>git@github.com:softwaremill/scala-clippy.git</url>
      <connection>scm:git:git@github.com:softwaremill/scala-clippy.git</connection>
    </scm>
      <developers>
        <developer>
          <id>adamw</id>
          <name>Adam Warski</name>
          <url>http://www.warski.org</url>
        </developer>
      </developers>,
  licenses := ("Apache2", new java.net.URL("http://www.apache.org/licenses/LICENSE-2.0.txt")) :: Nil,
  homepage := Some(new java.net.URL("http://www.softwaremill.com")),
  com.updateimpact.Plugin.apiKey in ThisBuild := sys.env
    .getOrElse("UPDATEIMPACT_API_KEY", (com.updateimpact.Plugin.apiKey in ThisBuild).value)
)

lazy val commonSettings = commonSettingsNoScalaVersion ++ Seq(
  scalaVersion := "2.11.11"
)

lazy val sbt10CompatSettings = Seq(
  sbtVersion in Global := (if (scalaVersion.value startsWith "2.12.") "1.1.6" else "0.13.15"),
  scalaCompilerBridgeSource := ("org.scala-sbt" % "compiler-interface" % "0.13.15" % "component").sources
)

lazy val clippy = (project in file("."))
  .settings(commonSettings)
  .settings(
    publishArtifact := false,
    // heroku
    herokuFatJar in Compile := Some((assemblyOutputPath in ui in assembly).value),
    deployHeroku in Compile := (deployHeroku in Compile).dependsOn(assembly in ui).value
  )
  .aggregate(modelJvm, plugin, pluginSbt, tests, ui)

lazy val model = (crossProject.crossType(CrossType.Pure) in file("model"))
  .settings(commonSettings: _*)
  .settings(
    libraryDependencies ++= Seq(scalatest, scalacheck, json4s)
  )

lazy val modelJvm = model.jvm.settings(name := "modelJvm")
lazy val modelJs  = model.js.settings(name := "modelJs")

def removeDep(groupId: String, artifactId: String) = new RewriteRule {
  override def transform(n: XNode): XNodeSeq = n match {
    case e: XElem if (e \ "groupId").text == groupId && (e \ "artifactId").text.startsWith(artifactId) =>
      XNodeSeq.Empty
    case _ => n
  }
}

lazy val plugin = (project in file("plugin"))
  .enablePlugins(BuildInfoPlugin)
  .settings(commonSettings)
  .settings(
    crossScalaVersions := Seq(scalaVersion.value, "2.12.1", "2.10.6"),
    libraryDependencies ++= Seq(
      "org.scala-lang" % "scala-compiler" % scalaVersion.value % "provided",
      "com.lihaoyi"    %% "scalaparse"    % "0.4.2",
      "com.lihaoyi"    %% "fansi"         % "0.2.3",
      scalatest,
      scalacheck,
      json4s
    ),
    // this is needed for fastparse to work on 2.10
    libraryDependencies ++= (if (scalaVersion.value startsWith "2.10.")
                               Seq(compilerPlugin("org.scalamacros" % s"paradise" % "2.1.0" cross CrossVersion.full))
                             else Seq()),
    pomPostProcess := { (node: XNode) =>
      new RuleTransformer(removeDep("org.json4s", "json4s-native")).transform(node).head
    },
    buildInfoPackage := "com.softwaremill.clippy",
    buildInfoObject := "ClippyBuildInfo",
    artifact in (Compile, assembly) := {
      val art = (artifact in (Compile, assembly)).value
      art.copy(`classifier` = Some("bundle"))
    },
    assemblyOption in assembly := (assemblyOption in assembly).value.copy(includeScala = false),
    // including the model classes for re-compilation, as for some reason depending on modelJvm doesn't work
    unmanagedSourceDirectories in Compile ++= (sourceDirectories in (modelJvm, Compile)).value
  )
  .settings(sbt10CompatSettings)
  .settings(addArtifact(artifact in (Compile, assembly), assembly))

lazy val pluginJar = AssemblyKeys.`assembly` in (plugin, Compile)

lazy val pluginSbt = (project in file("plugin-sbt"))
  .enablePlugins(BuildInfoPlugin)
  .settings(commonSettingsNoScalaVersion)
  .settings(
    sbtPlugin := true,
    name := "plugin-sbt",
    buildInfoPackage := "com.softwaremill.clippy",
    buildInfoObject := "ClippyBuildInfo",
    scalaVersion := "2.10.6",
    crossSbtVersions := Vector("0.13.16", "1.0.0")
  )
  .settings(sbt10CompatSettings)

lazy val tests = (project in file("tests"))
  .settings(commonSettings)
  .settings(
    publishArtifact := false,
    libraryDependencies ++= Seq(
      json4s,
      scalatest,
      "com.typesafe.akka"        %% "akka-http" % "10.0.0",
      "com.softwaremill.macwire" %% "macros"    % "2.2.2" % "provided",
      "com.typesafe.slick"       %% "slick"     % slickVersion
    ),
    // during tests, read from the local repository, if at all available
    scalacOptions ++= List(
      s"-Xplugin:${pluginJar.value.getAbsolutePath}",
      "-P:clippy:url=http://localhost:9000",
      "-P:clippy:colors=true"
    ),
    envVars in Test := (envVars in Test).value + ("CLIPPY_PLUGIN_PATH" -> pluginJar.value.getAbsolutePath),
    fork in Test := true
  )
  .dependsOn(modelJvm)

lazy val ui: Project = (project in file("ui"))
  .enablePlugins(BuildInfoPlugin)
  .settings(commonSettings)
  .settings(
    libraryDependencies ++= Seq(
      "com.h2database" % "h2" % "1.4.190", // % "test",
      scalatest,
      "org.webjars"             %% "webjars-play"         % "2.4.0-1",
      "org.webjars"             % "bootstrap"             % "3.3.6",
      "org.webjars"             % "jquery"                % "1.11.3",
      "com.vmunier"             %% "play-scalajs-scripts" % "0.3.0",
      "com.softwaremill.common" %% "id-generator"         % "1.1.0",
      "com.sendgrid"            % "sendgrid-java"         % "2.2.2" exclude ("commons-logging", "commons-logging"),
      "org.postgresql"          % "postgresql"            % "9.4.1207",
      "com.typesafe.slick"      %% "slick"                % slickVersion,
      "com.typesafe.slick"      %% "slick-hikaricp"       % slickVersion,
      "org.flywaydb"            % "flyway-core"           % "3.2.1"
    ),
    scalaJSProjects := Seq(uiClient),
    pipelineStages in Assets := Seq(scalaJSProd),
    routesGenerator := InjectedRoutesGenerator,
    // heroku & fat-jar
    assemblyJarName in assembly := "app.jar",
    mainClass in assembly := Some("play.core.server.ProdServerStart"),
    fullClasspath in assembly += Attributed.blank(PlayKeys.playPackageAssets.value),
    buildInfoPackage := "util",
    buildInfoObject := "ClippyBuildInfo",
    assemblyMergeStrategy in assembly := {
      // anything in public/lib is copied from webjars and causes duplicate resources exceptions
      case PathList("public", "lib", xs @ _ *) => MergeStrategy.discard
      case "JS_DEPENDENCIES"                   => MergeStrategy.discard
      case x =>
        val oldStrategy = (assemblyMergeStrategy in assembly).value
        oldStrategy(x)
    }
  )
  .enablePlugins(PlayScala)
  .aggregate(uiClient)
  .dependsOn(uiSharedJvm)

val scalaJsReactVersion = "0.11.3"

lazy val uiClient: Project = (project in file("ui-client"))
  .settings(commonSettings)
  .settings(name := "uiClient")
  .settings(
    scalaJSUseMainModuleInitializer := true,
    scalaJSUseMainModuleInitializer in Test := false,
    addCompilerPlugin(compilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full)), // for @Lenses
    libraryDependencies ++= Seq(
      "org.scala-js"                      %%% "scalajs-dom"    % "0.9.1",
      "be.doeraene"                       %%% "scalajs-jquery" % "0.9.1",
      "com.github.japgolly.scalajs-react" %%% "core"           % scalaJsReactVersion,
      "com.github.japgolly.scalajs-react" %%% "ext-monocle"    % scalaJsReactVersion,
      "com.github.japgolly.fork.monocle"  %%% "monocle-macro"  % "1.2.0"
    ),
    jsDependencies ++= Seq(
      RuntimeDOM          % "test",
      "org.webjars.bower" % "react" % "15.3.2" / "react-with-addons.js" minified "react-with-addons.min.js" commonJSName "React",
      "org.webjars.bower" % "react" % "15.3.2" / "react-dom.js" minified "react-dom.min.js" dependsOn "react-with-addons.js" commonJSName "ReactDOM"
    )
  )
  .enablePlugins(ScalaJSPlugin, ScalaJSWeb)
  .dependsOn(uiSharedJs)

lazy val uiShared = (crossProject.crossType(CrossType.Pure) in file("ui-shared"))
  .settings(commonSettings: _*)
  .settings(
    name := "uiShared",
    libraryDependencies ++= Seq(
      "com.lihaoyi" %%% "autowire" % "0.2.5",
      "com.lihaoyi" %%% "upickle"  % "0.3.6"
    )
  )
  .jsConfigure(_ enablePlugins ScalaJSWeb)
  .dependsOn(model)

lazy val uiSharedJvm = uiShared.jvm.settings(name := "uiSharedJvm")
lazy val uiSharedJs  = uiShared.js.settings(name := "uiSharedJs")


================================================
FILE: model/src/main/scala/com/softwaremill/clippy/Advice.scala
================================================
package com.softwaremill.clippy

import org.json4s.JsonAST._

final case class Advice(compilationError: CompilationError[RegexT], advice: String, library: Library) {
  def errMatching: PartialFunction[CompilationError[ExactT], String] = {
    case ce if compilationError.matches(ce) => advice
  }

  def toJson: JValue = JObject(
    "error"   -> compilationError.toJson,
    "text"    -> JString(advice),
    "library" -> library.toJson
  )
}

object Advice {
  def fromJson(jvalue: JValue): Option[Advice] =
    (for {
      JObject(fields)               <- jvalue
      JField("error", errorJV)      <- fields
      error                         <- CompilationError.fromJson(errorJV).toList
      JField("text", JString(text)) <- fields
      JField("library", libraryJV)  <- fields
      library                       <- Library.fromJson(libraryJV).toList
    } yield Advice(error, text, library)).headOption
}


================================================
FILE: model/src/main/scala/com/softwaremill/clippy/Clippy.scala
================================================
package com.softwaremill.clippy

import org.json4s.JsonAST._

case class Clippy(version: String, advices: List[Advice], fatalWarnings: List[Warning]) {
  def checkPluginVersion(ourVersion: String, logInfo: String => Unit) =
    if (version != ourVersion) {
      logInfo(s"New version of clippy plugin available: $version. Please update!")
    }

  def toJson: JValue = JObject(
    "version"       -> JString(version),
    "advices"       -> JArray(advices.map(_.toJson)),
    "fatalWarnings" -> JArray(fatalWarnings.map(_.toJson))
  )
}

object Clippy {
  def fromJson(jvalue: JValue): Option[Clippy] =
    (for {
      JObject(fields)                     <- jvalue
      JField("version", JString(version)) <- fields
    } yield {
      val advices = jvalue.findField {
        case JField("advices", _) => true
        case _                    => false
      } match {
        case Some((_, JArray(advicesJV))) => advicesJV.flatMap(Advice.fromJson)
        case _                            => Nil
      }
      val fatalWarnings = fields.find { tpl =>
        tpl._1 == "fatalWarnings"
      } match {
        case Some((_, JArray(fatalWarningsJV))) => fatalWarningsJV.flatMap(Warning.fromJson)
        case _                                  => Nil
      }
      Clippy(version, advices, fatalWarnings)
    }).headOption
}


================================================
FILE: model/src/main/scala/com/softwaremill/clippy/CompilationError.scala
================================================
package com.softwaremill.clippy

import org.json4s.JValue
import org.json4s.JsonAST.{JArray, JField, JObject, JString}
import org.json4s.native.JsonMethods._
import CompilationError._

sealed trait CompilationError[T <: Template] {
  def toJson: JValue
  def toJsonString: String = compact(render(toJson))
  def matches(other: CompilationError[ExactT])(implicit ev: T =:= RegexT): Boolean
  def asRegex(implicit ev: T =:= ExactT): CompilationError[RegexT]
}

case class TypeMismatchError[T <: Template](
    found: T,
    foundExpandsTo: Option[T],
    required: T,
    requiredExpandsTo: Option[T],
    notes: Option[String]
) extends CompilationError[T] {

  def notesAfterNewline = notes.fold("")(n => "\n" + n)

  override def toString = {
    def expandsTo(et: Option[T]): String = et.fold("")(e => s" (expands to: $e)")
    s"Type mismatch error.\nFound: $found${expandsTo(foundExpandsTo)},\nrequired: $required${expandsTo(requiredExpandsTo)}$notesAfterNewline"
  }

  override def toJson =
    JObject(
      List(TypeField -> JString("typeMismatch"), "found" -> JString(found.v), "required" -> JString(required.v))
        ++ foundExpandsTo.fold[List[JField]](Nil)(e => List("foundExpandsTo"       -> JString(e.v)))
        ++ requiredExpandsTo.fold[List[JField]](Nil)(e => List("requiredExpandsTo" -> JString(e.v)))
    )

  override def matches(other: CompilationError[ExactT])(implicit ev: T =:= RegexT) = other match {
    case TypeMismatchError(f, fe, r, re, _) =>
      def optMatches(t: Option[T], v: Option[ExactT]) =
        (for {
          tt <- t
          vv <- v
        } yield tt.matches(vv)).getOrElse(true)

      found.matches(f) && optMatches(foundExpandsTo, fe) && required.matches(r) && optMatches(requiredExpandsTo, re)

    case _ =>
      false
  }

  def hasExpands: Boolean = foundExpandsTo.nonEmpty || requiredExpandsTo.nonEmpty

  override def asRegex(implicit ev: T =:= ExactT) =
    TypeMismatchError(
      RegexT.fromPattern(found.v),
      foundExpandsTo.map(fe => RegexT.fromPattern(fe.v)),
      RegexT.fromPattern(required.v),
      requiredExpandsTo.map(re => RegexT.fromPattern(re.v)),
      notes
    )
}

case class NotFoundError[T <: Template](what: T) extends CompilationError[T] {

  override def toString = s"Not found error: $what"

  override def toJson =
    JObject(TypeField -> JString("notFound"), "what" -> JString(what.v))

  override def matches(other: CompilationError[ExactT])(implicit ev: T =:= RegexT) = other match {
    case NotFoundError(w) => what.matches(w)
    case _                => false
  }

  override def asRegex(implicit ev: T =:= ExactT) = NotFoundError(RegexT.fromPattern(what.v))
}

case class NotAMemberError[T <: Template](what: T, notAMemberOf: T) extends CompilationError[T] {

  override def toString = s"Not a member error: $what isn't a member of $notAMemberOf"

  override def toJson =
    JObject(
      TypeField      -> JString("notAMember"),
      "what"         -> JString(what.v),
      "notAMemberOf" -> JString(notAMemberOf.v)
    )

  override def matches(other: CompilationError[ExactT])(implicit ev: T =:= RegexT) = other match {
    case NotAMemberError(w, n) => what.matches(w) && notAMemberOf.matches(n)
    case _                     => false
  }

  override def asRegex(implicit ev: T =:= ExactT) =
    NotAMemberError(RegexT.fromPattern(what.v), RegexT.fromPattern(notAMemberOf.v))
}

case class ImplicitNotFoundError[T <: Template](parameter: T, implicitType: T) extends CompilationError[T] {

  override def toString = s"Implicit not found error: for parameter $parameter of type $implicitType"

  override def toJson =
    JObject(
      TypeField      -> JString("implicitNotFound"),
      "parameter"    -> JString(parameter.v),
      "implicitType" -> JString(implicitType.v)
    )

  override def matches(other: CompilationError[ExactT])(implicit ev: T =:= RegexT) = other match {
    case ImplicitNotFoundError(p, i) => parameter.matches(p) && implicitType.matches(i)
    case _                           => false
  }

  override def asRegex(implicit ev: T =:= ExactT) =
    ImplicitNotFoundError(RegexT.fromPattern(parameter.v), RegexT.fromPattern(implicitType.v))
}

case class TypeclassNotFoundError[T <: Template](typeclass: T, forType: T) extends CompilationError[T] {

  override def toString = s"Implicit $typeclass typeclass not found error: for type $forType"

  override def toJson =
    JObject(
      TypeField   -> JString("typeclassNotFound"),
      "typeclass" -> JString(typeclass.v),
      "forType"   -> JString(forType.v)
    )

  override def matches(other: CompilationError[ExactT])(implicit ev: T =:= RegexT) = other match {
    case TypeclassNotFoundError(p, i) => typeclass.matches(p) && forType.matches(i)
    case _                            => false
  }

  override def asRegex(implicit ev: T =:= ExactT) =
    TypeclassNotFoundError(RegexT.fromPattern(typeclass.v), RegexT.fromPattern(forType.v))
}

case class DivergingImplicitExpansionError[T <: Template](forType: T, startingWith: T, in: T)
    extends CompilationError[T] {

  override def toString = s"Diverging implicit expansion error: for type $forType starting with $startingWith in $in"

  override def toJson =
    JObject(
      TypeField      -> JString("divergingImplicitExpansion"),
      "forType"      -> JString(forType.v),
      "startingWith" -> JString(startingWith.v),
      "in"           -> JString(in.v)
    )

  override def matches(other: CompilationError[ExactT])(implicit ev: T =:= RegexT) = other match {
    case DivergingImplicitExpansionError(f, s, i) => forType.matches(f) && startingWith.matches(s) && in.matches(i)
    case _                                        => false
  }

  override def asRegex(implicit ev: T =:= ExactT) =
    DivergingImplicitExpansionError(
      RegexT.fromPattern(forType.v),
      RegexT.fromPattern(startingWith.v),
      RegexT.fromPattern(in.v)
    )
}

case class TypeArgumentsDoNotConformToOverloadedBoundsError[T <: Template](
    typeArgs: T,
    alternativesOf: T,
    alternatives: Set[T]
) extends CompilationError[T] {

  override def toString =
    s"Type arguments: $typeArgs for overloaded: $alternativesOf do not conform to any bounds: ${alternatives.map(_.toString).mkString(" <or> ")}"

  override def toJson =
    JObject(
      TypeField        -> JString("typeArgumentsDoNotConformToOverloadedBounds"),
      "typeArgs"       -> JString(typeArgs.v),
      "alternativesOf" -> JString(alternativesOf.v),
      "alternatives"   -> JArray(alternatives.map(a => JString(a.v)).toList)
    )

  override def matches(other: CompilationError[ExactT])(implicit ev: T =:= RegexT) = other match {
    case TypeArgumentsDoNotConformToOverloadedBoundsError(t, af, a) =>
      typeArgs.matches(t) &&
        alternativesOf.matches(af) && RegexT.setMatches(alternatives.map(ev.apply), a)
    case _ => false
  }

  override def asRegex(implicit ev: T =:= ExactT) =
    TypeArgumentsDoNotConformToOverloadedBoundsError(
      RegexT.fromPattern(typeArgs.v),
      RegexT.fromPattern(alternativesOf.v),
      alternatives.map(a => RegexT.fromPattern(a.v))
    )
}

object CompilationError {
  val TypeField = "type"

  def fromJsonString(s: String): Option[CompilationError[RegexT]] = fromJson(parse(s))

  def fromJson(jvalue: JValue): Option[CompilationError[RegexT]] = {

    def regexTFromJson(fields: List[JField], name: String): Option[RegexT] =
      (for {
        JField(`name`, JString(v)) <- fields
      } yield RegexT.fromRegex(v)).headOption

    def multipleRegexTFromJson(fields: List[JField], name: String): Option[Set[RegexT]] =
      (for {
        JField(`name`, JArray(vv)) <- fields
      } yield vv.collect { case JString(v) => RegexT.fromRegex(v) }.toSet).headOption

    def extractWithType(typeValue: String, fields: List[JField]): Option[CompilationError[RegexT]] = typeValue match {
      case "typeMismatch" =>
        for {
          found <- regexTFromJson(fields, "found")
          foundExpandsTo = regexTFromJson(fields, "foundExpandsTo")
          required <- regexTFromJson(fields, "required")
          requiredExpandsTo = regexTFromJson(fields, "requiredExpandsTo")
        } yield TypeMismatchError(found, foundExpandsTo, required, requiredExpandsTo, None)

      case "notFound" =>
        for {
          what <- regexTFromJson(fields, "what")
        } yield NotFoundError(what)

      case "notAMember" =>
        for {
          what         <- regexTFromJson(fields, "what")
          notAMemberOf <- regexTFromJson(fields, "notAMemberOf")
        } yield NotAMemberError(what, notAMemberOf)

      case "implicitNotFound" =>
        for {
          parameter    <- regexTFromJson(fields, "parameter")
          implicitType <- regexTFromJson(fields, "implicitType")
        } yield ImplicitNotFoundError(parameter, implicitType)

      case "divergingImplicitExpansion" =>
        for {
          forType      <- regexTFromJson(fields, "forType")
          startingWith <- regexTFromJson(fields, "startingWith")
          in           <- regexTFromJson(fields, "in")
        } yield DivergingImplicitExpansionError(forType, startingWith, in)

      case "typeArgumentsDoNotConformToOverloadedBounds" =>
        for {
          typeArgs       <- regexTFromJson(fields, "typeArgs")
          alternativesOf <- regexTFromJson(fields, "alternativesOf")
          alternatives   <- multipleRegexTFromJson(fields, "alternatives")
        } yield TypeArgumentsDoNotConformToOverloadedBoundsError(typeArgs, alternativesOf, alternatives)

      case "typeclassNotFound" =>
        for {
          typeclass <- regexTFromJson(fields, "typeclass")
          forType   <- regexTFromJson(fields, "forType")
        } yield TypeclassNotFoundError(typeclass, forType)

      case _ => None
    }

    (for {
      JObject(fields)                       <- jvalue
      JField(TypeField, JString(typeValue)) <- fields
      v                                     <- extractWithType(typeValue, fields).toList
    } yield v).headOption
  }
}


================================================
FILE: model/src/main/scala/com/softwaremill/clippy/CompilationErrorParser.scala
================================================
package com.softwaremill.clippy

import java.util.regex.Pattern

object CompilationErrorParser {
  private val FoundRegexp            = """found\s*:\s*([^\n]+)""".r
  private val RequiredPrefixRegexp   = """required\s*:""".r
  private val AfterRequiredRegexp    = """required\s*:\s*([^\n]+)""".r
  private val WhichExpandsToRegexp   = """\s*\(which expands to\)\s*([^\n]+)""".r
  private val NotFoundRegexp         = """not found\s*:\s*([^\n]+)""".r
  private val NotAMemberRegexp       = """:?\s*([^\n:]+) is not a member of""".r
  private val NotAMemberOfRegexp     = """is not a member of\s*([^\n]+)""".r
  private val ImplicitNotFoundRegexp = """could not find implicit value for parameter\s*([^:]+):\s*([^\n]+)""".r
  private val DivergingImplicitExpansionRegexp =
    """diverging implicit expansion for type\s*([^\s]+)\s*.*\s*starting with method\s*([^\s]+)\s*in\s*([^\n]+)""".r
  private val TypeArgumentsDoNotConformToOverloadedBoundsRegexp =
    """type arguments \[([^\]]+)\] conform to the bounds of none of the overloaded alternatives of\s*([^:\n]+)[^:]*: ([^\n]+)""".r
  private val TypeclassNotFoundRegexp = """No implicit (.*) defined for ([^\n]+)""".r

  def parse(e: String): Option[CompilationError[ExactT]] = {
    val error = e.replaceAll(Pattern.quote("[error]"), "")
    if (error.contains("type mismatch")) {
      RequiredPrefixRegexp.split(error).toList match {
        case List(beforeReq, afterReq) =>
          for {
            found <- FoundRegexp.findFirstMatchIn(beforeReq)
            foundExpandsTo = WhichExpandsToRegexp.findFirstMatchIn(beforeReq)
            required <- AfterRequiredRegexp.findFirstMatchIn(error)
            requiredExpandsTo = WhichExpandsToRegexp.findFirstMatchIn(afterReq)
          } yield {
            val notes = requiredExpandsTo match {
              case Some(et) => getNotesFromIndex(afterReq, et.end)
              case None     => getNotesFromIndex(error, required.end)
            }

            TypeMismatchError[ExactT](
              ExactT(found.group(1)),
              foundExpandsTo.map(m => ExactT(m.group(1))),
              ExactT(required.group(1)),
              requiredExpandsTo.map(m => ExactT(m.group(1))),
              notes
            )
          }

        case _ =>
          None
      }
    } else if (error.contains("not found")) {
      for {
        what <- NotFoundRegexp.findFirstMatchIn(error)
      } yield NotFoundError[ExactT](ExactT(what.group(1)))
    } else if (error.contains("is not a member of")) {
      for {
        what         <- NotAMemberRegexp.findFirstMatchIn(error)
        notAMemberOf <- NotAMemberOfRegexp.findFirstMatchIn(error)
      } yield NotAMemberError[ExactT](ExactT(what.group(1)), ExactT(notAMemberOf.group(1)))
    } else if (error.contains("could not find implicit value for parameter")) {
      for {
        inf <- ImplicitNotFoundRegexp.findFirstMatchIn(error)
      } yield ImplicitNotFoundError[ExactT](ExactT(inf.group(1)), ExactT(inf.group(2)))
    } else if (error.contains("diverging implicit expansion for type")) {
      for {
        inf <- DivergingImplicitExpansionRegexp.findFirstMatchIn(error)
      } yield DivergingImplicitExpansionError[ExactT](ExactT(inf.group(1)), ExactT(inf.group(2)), ExactT(inf.group(3)))
    } else if (error.contains("conform to the bounds of none of the overloaded alternatives")) {
      for {
        inf <- TypeArgumentsDoNotConformToOverloadedBoundsRegexp.findFirstMatchIn(error)
      } yield
        TypeArgumentsDoNotConformToOverloadedBoundsError[ExactT](
          ExactT(inf.group(1)),
          ExactT(inf.group(2)),
          inf.group(3).split(Pattern.quote(" <and> ")).toSet.map(ExactT.apply)
        )
    } else if (error.contains("No implicit")) {
      for {
        inf <- TypeclassNotFoundRegexp.findFirstMatchIn(error)
        group2 = inf.group(2)
      } yield
        TypeclassNotFoundError(
          ExactT(inf.group(1)),
          ExactT(if (group2.endsWith(".")) group2.substring(0, group2.length - 1) else group2)
        )
    } else None
  }

  private def getNotesFromIndex(msg: String, afterIdx: Int): Option[String] = {
    val fromIdx = afterIdx + 1
    if (msg.length >= fromIdx + 1) {
      val notes = msg.substring(fromIdx).trim
      if (notes == "") None else Some(notes)
    } else None
  }
}


================================================
FILE: model/src/main/scala/com/softwaremill/clippy/Library.scala
================================================
package com.softwaremill.clippy

import org.json4s.JsonAST.{JField, JObject, JString, JValue}

case class Library(groupId: String, artifactId: String, version: String) {
  def toJson: JValue = JObject(
    "groupId"    -> JString(groupId),
    "artifactId" -> JString(artifactId),
    "version"    -> JString(version)
  )

  override def toString = s"$groupId:$artifactId:$version"
}

object Library {
  def fromJson(jvalue: JValue): Option[Library] =
    (for {
      JObject(fields)                           <- jvalue
      JField("groupId", JString(groupId))       <- fields
      JField("artifactId", JString(artifactId)) <- fields
      JField("version", JString(version))       <- fields
    } yield Library(groupId, artifactId, version)).headOption
}


================================================
FILE: model/src/main/scala/com/softwaremill/clippy/StringDiff.scala
================================================
package com.softwaremill.clippy

object StringDiff {
  val separators                       = List(' ', ',', '(', ')', '[', ']', '#', '#', '=', '>', '{', '.')
  def isSeparator(char: Char): Boolean = separators.contains(char)
}

class StringDiff(expected: String, actual: String, color: String => String) {
  import StringDiff._
  def diff(message: String): String =
    if (this.expected == this.actual) format(message, this.expected, this.actual)
    else format(message, markDiff(expected), markDiff(actual))

  private def format(msg: String, expected: String, actual: String) = msg.format(expected, actual)

  private def markDiff(source: String) = {
    val prefix = findCommonPrefix()
    val suffix = findCommonSuffix()
    if (overlappingPrefixSuffix(source, prefix, suffix))
      source
    else {
      val diff = color(source.substring(prefix.length, source.length - suffix.length))
      prefix + diff + suffix
    }
  }

  private def overlappingPrefixSuffix(source: String, prefix: String, suffix: String) =
    prefix.length + suffix.length >= source.length

  def findCommonPrefix(expectedStr: String = expected, actualStr: String = actual): String = {
    val prefixChars = expectedStr.zip(actualStr).takeWhile(Function.tupled(_ == _)).map(_._1)

    val lastSeparatorIndex = prefixChars.lastIndexWhere(isSeparator)
    val prefixEndIndex     = if (lastSeparatorIndex == -1) 0 else lastSeparatorIndex + 1

    if (prefixChars.nonEmpty && prefixEndIndex < prefixChars.length)
      prefixChars.mkString.substring(0, prefixEndIndex)
    else {
      prefixChars.mkString
    }
  }

  def findCommonSuffix(): String =
    findCommonPrefix(expected.reverse, actual.reverse).reverse
}


================================================
FILE: model/src/main/scala/com/softwaremill/clippy/Template.scala
================================================
package com.softwaremill.clippy

import java.util.regex.Pattern

import scala.util.Try
import scala.util.matching.Regex

sealed trait Template {
  def v: String
}

case class ExactT(v: String) extends Template {
  override def toString = v
}

case class RegexT(v: String) extends Template {
  lazy val regex                  = Try(new Regex(v)).getOrElse(new Regex("^$"))
  def matches(e: ExactT): Boolean = regex.pattern.matcher(e.v).matches()
  override def toString           = v
}
object RegexT {

  /**
    * Patterns can include wildcards (`*`)
    */
  def fromPattern(pattern: String): RegexT = {
    val regexp = pattern
      .split("\\*", -1)
      .map(el => if (el != "") Pattern.quote(el) else el)
      .flatMap(el => List(".*", el))
      .tail
      .filter(_.nonEmpty)
      .mkString("")

    RegexT.fromRegex(regexp)
  }

  def fromRegex(v: String): RegexT =
    new RegexT(v)

  def setMatches(rr: Set[RegexT], ee: Set[ExactT]): Boolean =
    if (rr.size != ee.size) false
    else {
      rr.toList.forall { r =>
        ee.exists(r.matches)
      }
    }
}


================================================
FILE: model/src/main/scala/com/softwaremill/clippy/Warning.scala
================================================
package com.softwaremill.clippy

import org.json4s.JsonAST._

final case class Warning(pattern: RegexT, text: Option[String]) {
  def toJson: JValue =
    JObject(
      "pattern" -> JString(pattern.toString)
    ) ++ text.map(t => JObject("text" -> JString(t))).getOrElse(JNothing)
}

object Warning {
  def fromJson(jvalue: JValue): Option[Warning] =
    (for {
      JObject(fields)                        <- jvalue
      JField("pattern", JString(patternStr)) <- fields
      pattern = RegexT.fromRegex(patternStr)

    } yield {
      val text = jvalue.findField {
        case (("text", _)) => true
        case _             => false
      } match {
        case Some((_, JString(textStr))) => Some(textStr)
        case _                           => None
      }
      Warning(pattern, text)
    }).headOption
}


================================================
FILE: model/src/test/scala/com/softwaremill/clippy/CompilationErrorParserTest.scala
================================================
package com.softwaremill.clippy

import org.scalatest.{FlatSpec, Matchers}

class CompilationErrorParserTest extends FlatSpec with Matchers {
  it should "parse akka's route error message" in {
    val e =
      """type mismatch;
        | found   : akka.http.scaladsl.server.StandardRoute
        | required: akka.stream.scaladsl.Flow[akka.http.scaladsl.model.HttpRequest,akka.http.scaladsl.model.HttpResponse,Any]""".stripMargin

    CompilationErrorParser.parse(e) should be(
      Some(
        TypeMismatchError(
          ExactT("akka.http.scaladsl.server.StandardRoute"),
          None,
          ExactT(
            "akka.stream.scaladsl.Flow[akka.http.scaladsl.model.HttpRequest,akka.http.scaladsl.model.HttpResponse,Any]"
          ),
          None,
          None
        )
      )
    )
  }

  it should "parse an error message with [error] prefix" in {
    val e =
      """[error] /Users/adamw/projects/clippy/tests/src/main/scala/com/softwaremill/clippy/Working.scala:16: type mismatch;
        |[error]  found   : akka.http.scaladsl.server.StandardRoute
        |[error]  required: akka.stream.scaladsl.Flow[akka.http.scaladsl.model.HttpRequest,akka.http.scaladsl.model.HttpResponse,Any]""".stripMargin

    CompilationErrorParser.parse(e) should be(
      Some(
        TypeMismatchError(
          ExactT("akka.http.scaladsl.server.StandardRoute"),
          None,
          ExactT(
            "akka.stream.scaladsl.Flow[akka.http.scaladsl.model.HttpRequest,akka.http.scaladsl.model.HttpResponse,Any]"
          ),
          None,
          None
        )
      )
    )
  }

  it should "parse a type mismatch error with a single expands to section" in {
    val e =
      """type mismatch;
        |found   : japgolly.scalajs.react.CompState.ReadCallbackWriteCallbackOps[com.softwaremill.clippy.Contribute.Step2.State]#This[com.softwaremill.clippy.FormField]
        |required: japgolly.scalajs.react.CompState.AccessRD[?]
        |   (which expands to)  japgolly.scalajs.react.CompState.ReadDirectWriteCallbackOps[?]""".stripMargin

    CompilationErrorParser.parse(e) should be(
      Some(
        TypeMismatchError(
          ExactT(
            "japgolly.scalajs.react.CompState.ReadCallbackWriteCallbackOps[com.softwaremill.clippy.Contribute.Step2.State]#This[com.softwaremill.clippy.FormField]"
          ),
          None,
          ExactT("japgolly.scalajs.react.CompState.AccessRD[?]"),
          Some(ExactT("japgolly.scalajs.react.CompState.ReadDirectWriteCallbackOps[?]")),
          None
        )
      )
    )
  }

  it should "parse a type mismatch error with two expands to sections" in {
    val e =
      """type mismatch;
        |found   : japgolly.scalajs.react.CompState.ReadCallbackWriteCallbackOps[com.softwaremill.clippy.Contribute.Step2.State]#This[com.softwaremill.clippy.FormField]
        |   (which expands to)  japgolly.scalajs.react.CompState.ReadCallbackWriteCallbackOps[com.softwaremill.clippy.FormField]
        |required: japgolly.scalajs.react.CompState.AccessRD[?]
        |   (which expands to)  japgolly.scalajs.react.CompState.ReadDirectWriteCallbackOps[?]""".stripMargin

    CompilationErrorParser.parse(e) should be(
      Some(
        TypeMismatchError(
          ExactT(
            "japgolly.scalajs.react.CompState.ReadCallbackWriteCallbackOps[com.softwaremill.clippy.Contribute.Step2.State]#This[com.softwaremill.clippy.FormField]"
          ),
          Some(
            ExactT("japgolly.scalajs.react.CompState.ReadCallbackWriteCallbackOps[com.softwaremill.clippy.FormField]")
          ),
          ExactT("japgolly.scalajs.react.CompState.AccessRD[?]"),
          Some(ExactT("japgolly.scalajs.react.CompState.ReadDirectWriteCallbackOps[?]")),
          None
        )
      )
    )
  }

  it should "parse macwire's wire not found error message" in {
    val e = "not found: value wire"

    CompilationErrorParser.parse(e) should be(Some(NotFoundError(ExactT("value wire"))))
  }

  it should "parse not a member of message" in {
    val e = "value call is not a member of scala.concurrent.Future[Unit]"

    CompilationErrorParser.parse(e) should be(
      Some(NotAMemberError(ExactT("value call"), ExactT("scala.concurrent.Future[Unit]")))
    )
  }

  it should "parse not a member of message with extra text" in {
    val e =
      "[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]"

    CompilationErrorParser.parse(e) should be(
      Some(NotAMemberError(ExactT("value call"), ExactT("scala.concurrent.Future[Unit]")))
    )
  }

  it should "parse an implicit not found" in {
    val e =
      "could not find implicit value for parameter marshaller: spray.httpx.marshalling.ToResponseMarshaller[scala.concurrent.Future[String]]"

    CompilationErrorParser.parse(e) should be(
      Some(
        ImplicitNotFoundError(
          ExactT("marshaller"),
          ExactT("spray.httpx.marshalling.ToResponseMarshaller[scala.concurrent.Future[String]]")
        )
      )
    )
  }

  it should "parse a diverging implicit error " in {
    val e =
      "diverging implicit expansion for type io.circe.Decoder.Secondary[this.Out] starting with method decodeCaseClass in trait GenericInstances"

    CompilationErrorParser.parse(e) should be(
      Some(
        DivergingImplicitExpansionError(
          ExactT("io.circe.Decoder.Secondary[this.Out]"),
          ExactT("decodeCaseClass"),
          ExactT("trait GenericInstances")
        )
      )
    )
  }

  it should "parse a diverging implicit error with extra text" in {
    val e =
      """
        |[error] /home/src/main/scala/Routes.scala:19: diverging implicit expansion for type io.circe.Decoder.Secondary[this.Out]
        |[error] starting with method decodeCaseClass in trait GenericInstances
      """.stripMargin

    CompilationErrorParser.parse(e) should be(
      Some(
        DivergingImplicitExpansionError(
          ExactT("io.circe.Decoder.Secondary[this.Out]"),
          ExactT("decodeCaseClass"),
          ExactT("trait GenericInstances")
        )
      )
    )
  }

  it should "parse a type arguments do not conform to any overloaded bounds error" in {
    val e =
      """
        |[error]  clippy/Working.scala:32: type arguments [org.softwaremill.clippy.User] conform to the bounds of none of the overloaded alternatives of
        |value apply: [E <: slick.lifted.AbstractTable[_]]=> slick.lifted.TableQuery[E] <and> [E <: slick.lifted.AbstractTable[_]](cons: slick.lifted.Tag => E)slick.lifted.TableQuery[E]
        |protected val users = TableQuery[User]
      """.stripMargin

    CompilationErrorParser.parse(e) should be(
      Some(
        TypeArgumentsDoNotConformToOverloadedBoundsError(
          ExactT("org.softwaremill.clippy.User"),
          ExactT("value apply"),
          Set(
            ExactT("[E <: slick.lifted.AbstractTable[_]]=> slick.lifted.TableQuery[E]"),
            ExactT("[E <: slick.lifted.AbstractTable[_]](cons: slick.lifted.Tag => E)slick.lifted.TableQuery[E]")
          )
        )
      )
    )
  }

  it should "parse a no implicit defined for" in {
    val e =
      """
        |[error] /Users/clippy/model/src/main/scala/com/softwaremill/clippy/CompilationErrorParser.scala:18: No implicit Ordering defined for java.time.LocalDate.
        |[error]   Seq(java.time.LocalDate.MIN, java.time.LocalDate.MAX).sorted
      """.stripMargin

    CompilationErrorParser.parse(e) should be(
      Some(TypeclassNotFoundError(ExactT("Ordering"), ExactT("java.time.LocalDate")))
    )
  }

  it should "parse an error with notes" in {
    val e =
      """
        |type mismatch;
        | found   : org.softwaremill.clippy.ImplicitResolutionDiamond.C
        | required: Array[String]
        |Note that implicit conversions are not applicable because they are ambiguous:
        | both method toMessage in object B of type (b: org.softwaremill.clippy.ImplicitResolutionDiamond.B)Array[String]
        | and method toMessage in object A of type (a: org.softwaremill.clippy.ImplicitResolutionDiamond.A)Array[String]
        | are possible conversion functions from org.softwaremill.clippy.ImplicitResolutionDiamond.C to Array[String]
      """.stripMargin

    CompilationErrorParser.parse(e) should be(
      Some(
        TypeMismatchError(
          ExactT("org.softwaremill.clippy.ImplicitResolutionDiamond.C"),
          None,
          ExactT("Array[String]"),
          None,
          Some(
            """Note that implicit conversions are not applicable because they are ambiguous:
             | both method toMessage in object B of type (b: org.softwaremill.clippy.ImplicitResolutionDiamond.B)Array[String]
             | and method toMessage in object A of type (a: org.softwaremill.clippy.ImplicitResolutionDiamond.A)Array[String]
             | are possible conversion functions from org.softwaremill.clippy.ImplicitResolutionDiamond.C to Array[String]""".stripMargin
          )
        )
      )
    )
  }

  it should "parse an error with expands to & notes" in {
    val e =
      """
        |type mismatch;
        | found   : org.softwaremill.clippy.ImplicitResolutionDiamond.C
        | required: Array[String]
        |   (which expands to)  japgolly.scalajs.react.CompState.ReadDirectWriteCallbackOps[?]
        |Note that implicit conversions are not applicable because they are ambiguous:
        | both method toMessage in object B of type (b: org.softwaremill.clippy.ImplicitResolutionDiamond.B)Array[String]
        | and method toMessage in object A of type (a: org.softwaremill.clippy.ImplicitResolutionDiamond.A)Array[String]
        | are possible conversion functions from org.softwaremill.clippy.ImplicitResolutionDiamond.C to Array[String]
      """.stripMargin

    CompilationErrorParser.parse(e) should be(
      Some(
        TypeMismatchError(
          ExactT("org.softwaremill.clippy.ImplicitResolutionDiamond.C"),
          None,
          ExactT("Array[String]"),
          Some(ExactT("japgolly.scalajs.react.CompState.ReadDirectWriteCallbackOps[?]")),
          Some(
            """Note that implicit conversions are not applicable because they are ambiguous:
             | both method toMessage in object B of type (b: org.softwaremill.clippy.ImplicitResolutionDiamond.B)Array[String]
             | and method toMessage in object A of type (a: org.softwaremill.clippy.ImplicitResolutionDiamond.A)Array[String]
             | are possible conversion functions from org.softwaremill.clippy.ImplicitResolutionDiamond.C to Array[String]""".stripMargin
          )
        )
      )
    )
  }
}


================================================
FILE: model/src/test/scala/com/softwaremill/clippy/CompilationErrorTest.scala
================================================
package com.softwaremill.clippy

import org.scalacheck.Prop._
import org.scalacheck.Properties
import org.scalatest.{FlatSpec, Matchers}

class CompilationErrorTest extends FlatSpec with Matchers {
  it should "do not match different exact not found errors" in {
    // given
    val err = NotFoundError(RegexT.fromPattern("value wire[]"))
    val nonMatchingErrs = List(
      NotFoundError(ExactT("value wirex")),
      NotFoundError(ExactT("value wir")),
      NotFoundError(ExactT("avalue wire")),
      NotFoundError(ExactT("hakuna matata"))
    )

    // then
    nonMatchingErrs.foreach(err.matches(_) should be(false))
  }

  it should "match regex in not found errors" in {
    // given
    val err = NotFoundError(RegexT.fromPattern("value wi*"))
    val matchingErrs = List(
      NotFoundError(ExactT("value wire")),
      NotFoundError(ExactT("value wirex")),
      NotFoundError(ExactT("value wi")),
      NotFoundError(ExactT("value wire55"))
    )
    val nonMatchingErrs = List(
      NotFoundError(ExactT("avalue wire")),
      NotFoundError(ExactT("avalue w5"))
    )

    // then
    matchingErrs.foreach(err.matches(_) should be(true))
    nonMatchingErrs.foreach(err.matches(_) should be(false))
  }

  it should "not match different exact type mismatch errors" in {
    // given
    val err = TypeMismatchError(
      RegexT.fromPattern("com.softwaremill.String"),
      None,
      RegexT.fromPattern("com.softwaremill.RequiredType[String]"),
      None,
      None
    )

    val nonMatchingErrs = List(
      TypeMismatchError(ExactT("com.softwaremill.String"), None, ExactT("com.softwaremill.OtherType"), None, None),
      TypeMismatchError(ExactT("com.softwaremill.Int"), None, ExactT("com.softwaremill.OtherType"), None, None),
      TypeMismatchError(
        ExactT("com.softwaremill.Int"),
        None,
        ExactT("com.softwaremill.RequiredType[String]"),
        None,
        None
      )
    )

    // then
    nonMatchingErrs.foreach(err.matches(_) should be(false))
  }

  it should "match regex in type mismatch errors" in {
    // given
    val err = TypeMismatchError(
      RegexT.fromPattern("slick.dbio.DBIOAction[*]"),
      None,
      RegexT.fromPattern("slick.lifted.Rep[Option[*]]"),
      None,
      None
    )
    val matchingErrs = List(
      TypeMismatchError(
        ExactT("slick.dbio.DBIOAction[Unit,slick.dbio.NoStream,slick.dbio.Effect.Write]"),
        None,
        ExactT("slick.lifted.Rep[Option[?]]"),
        None,
        Some("notes")
      ),
      TypeMismatchError(
        ExactT("slick.dbio.DBIOAction[String,slick.dbio.NoStream,slick.dbio.Effect.Read]"),
        None,
        ExactT("slick.lifted.Rep[Option[Int]]"),
        None,
        None
      ),
      TypeMismatchError(
        ExactT("slick.dbio.DBIOAction[Unit,slick.dbio.NoStream,slick.dbio.Effect.Read]"),
        None,
        ExactT("slick.lifted.Rep[Option[Option[Int]]"),
        None,
        None
      )
    )
    val nonMatchingErrs = List(
      TypeMismatchError(
        ExactT("slick.dbio.DBIOAction[Unit,slick.dbio.NoStream,slick.dbio.Effect.Read]"),
        None,
        ExactT("String"),
        None,
        Some("notes")
      ),
      TypeMismatchError(ExactT("String"), None, ExactT("slick.lifted.Rep[Option[?]]"), None, None),
      TypeMismatchError(ExactT("String"), None, ExactT("com.softwaremill.AweSomeType"), None, None)
    )

    // then
    matchingErrs.foreach(err.matches(_) should be(true))
    nonMatchingErrs.foreach(err.matches(_) should be(false))
  }
}

class CompilationErrorProperties extends Properties("CompilationError") {
  property("obj -> json -> obj works for type mismatch error") = forAll {
    (found: String, foundExpandsTo: Option[String], required: String, requiredExpandsTo: Option[String]) =>
      val e = TypeMismatchError(
        RegexT.fromPattern(found),
        foundExpandsTo.map(RegexT.fromPattern),
        RegexT.fromPattern(required),
        requiredExpandsTo.map(RegexT.fromPattern),
        None
      )
      CompilationError.fromJson(e.toJson).contains(e)
  }

  property("obj -> json -> obj works for not found error") = forAll { (what: String) =>
    val e = NotFoundError(RegexT.fromPattern(what))
    CompilationError.fromJson(e.toJson).contains(e)
  }

  property("obj -> json -> obj works for not a member error") = forAll { (what: String, notAMemberOf: String) =>
    val e = NotAMemberError(RegexT.fromPattern(what), RegexT.fromPattern(notAMemberOf))
    CompilationError.fromJson(e.toJson).contains(e)
  }

  property("obj -> json -> obj works for implicit not found") = forAll { (parameter: String, implicitType: String) =>
    val e = ImplicitNotFoundError(RegexT.fromPattern(parameter), RegexT.fromPattern(implicitType))
    CompilationError.fromJson(e.toJson).contains(e)
  }

  property("obj -> json -> obj works for diverging implicit expansions") = forAll {
    (forType: String, startingWith: String, in: String) =>
      val e = DivergingImplicitExpansionError(
        RegexT.fromPattern(forType),
        RegexT.fromPattern(startingWith),
        RegexT.fromPattern(in)
      )
      CompilationError.fromJson(e.toJson).contains(e)
  }

  property("obj -> json -> obj works for type arguments do not conform to overloaded bounds") = forAll {
    (typeArgs: String, alternativesOf: String, alternatives: Set[String]) =>
      val e = TypeArgumentsDoNotConformToOverloadedBoundsError(
        RegexT.fromPattern(typeArgs),
        RegexT.fromPattern(alternativesOf),
        alternatives.map(a => RegexT.fromPattern(a))
      )
      CompilationError.fromJson(e.toJson).contains(e)
  }

  property("match identical not found error") = forAll { (what: String) =>
    NotFoundError(RegexT.fromPattern(what)).matches(NotFoundError(ExactT(what)))
  }

  property("matches identical type mismatch error") = forAll {
    (found: String, required: String, notes: Option[String]) =>
      TypeMismatchError(RegexT.fromPattern(found), None, RegexT.fromPattern(required), None, None)
        .matches(TypeMismatchError(ExactT(found), None, ExactT(required), None, notes))
  }
}


================================================
FILE: model/src/test/scala/com/softwaremill/clippy/LibraryProperties.scala
================================================
package com.softwaremill.clippy

import org.scalacheck.Prop._
import org.scalacheck.Properties

class LibraryProperties extends Properties("Library") {
  property("obj -> json -> obj") = forAll { (gid: String, aid: String, v: String) =>
    val l = Library(gid, aid, v)
    Library.fromJson(l.toJson).contains(l)
  }
}


================================================
FILE: model/src/test/scala/com/softwaremill/clippy/RegexTTest.scala
================================================
package com.softwaremill.clippy

import org.scalatest.{FlatSpec, Matchers}

class RegexTTest extends FlatSpec with Matchers {
  val matchingTests = List(
    ("slick.dbio.DBIOAction[*]", "slick.dbio.DBIOAction[Unit,slick.dbio.NoStream,slick.dbio.Effect.Write]"),
    ("slick.dbio.DBIOAction[Unit,*,*]", "slick.dbio.DBIOAction[Unit,slick.dbio.NoStream,slick.dbio.Effect.Write]"),
    (
      "slick.dbio.DBIOAction[*,slick.dbio.NoStream,*]",
      "slick.dbio.DBIOAction[Unit,slick.dbio.NoStream,slick.dbio.Effect.Write]"
    )
  )

  val nonMatchingTests = List(
    ("slick.dbio.DBIOAction[*]", "scala.concurrent.Future[Unit]"),
    ("slick.dbio.DBIOAction[Unit,*,*]", "slick.dbio.DBIOAction[String,slick.dbio.NoStream,slick.dbio.Effect.Write]"),
    (
      "slick.dbio.DBIOAction[*,slick.dbio.NoStream,*]",
      "slick.dbio.DBIOAction[Unit,slick.dbio.Stream,slick.dbio.Effect.Write]"
    )
  )

  for ((pattern, test) <- matchingTests) {
    pattern should s"match $test" in {
      RegexT.fromPattern(pattern).matches(ExactT(test)) should be(true)
    }
  }

  for ((pattern, test) <- nonMatchingTests) {
    pattern should s"not match $test" in {
      RegexT.fromPattern(pattern).matches(ExactT(test)) should be(false)
    }
  }
}


================================================
FILE: model/src/test/scala/com/softwaremill/clippy/StringDiffSpecification.scala
================================================
package com.softwaremill.clippy

import org.scalacheck.Prop.forAll
import org.scalacheck.Properties

class StringDiffSpecification extends Properties("StringDiff") with TypeNamesGenerators {

  val S     = "S"
  val E     = "E"
  val AddSE = (s: String) => S + s + E

  def innerTypeDiffsCorrectly(fourTypes: List[String]): Boolean = {
    val List(x, y, v, z) = fourTypes
    val expected         = s"$x[$y[$z]]"
    val actual           = s"$x[$v[$z]]"
    val msg              = new StringDiff(expected, actual, AddSE).diff("expected: %s actual: %s")
    msg == s"""expected: $x[$S$y$E[$z]] actual: $x[$S$v$E[$z]]"""
  }

  def twoTypesAreFullyDiff(twoTypes: List[String]): Boolean = {
    val List(x, y) = twoTypes
    new StringDiff(x, y, AddSE).diff("expected: %s actual: %s") == s"""expected: $S$x$E actual: $S$y$E"""
  }

  property("X[Y[Z]] vs X[V[Z]] always gives X[<diff>[Z]] excluding packages") =
    forAll(different(singleTypeName)(4))(innerTypeDiffsCorrectly)

  property("X[Y[Z]] vs X[V[Z]] always gives X[<diff>[Z]] if Y and V have common prefix") =
    forAll(typesWithCommonPrefix(4))(innerTypeDiffsCorrectly)

  property("X[Y[Z]] vs X[V[Z]] always gives X[<diff>[Z]] if Y and V have common suffix") =
    forAll(typesWithCommonSuffix(4))(innerTypeDiffsCorrectly)

  property("A[X] vs B[X] always marks outer as diff for A != B when A and B have common prefix") =
    forAll(typesWithCommonPrefix(2), complexTypeName(maxDepth = 3)) { (outerTypes, x) =>
      val List(a, b) = outerTypes
      val expected   = s"$a[$x]"
      val actual     = s"$b[$x]"
      val msg        = new StringDiff(expected, actual, AddSE).diff("expected: %s actual: %s")
      msg == s"""expected: $S$a$E[$x] actual: $S$b$E[$x]"""
    }

  property("package.A[X] vs package.B[X] always gives package.<diff>A</diff>[X]") =
    forAll(javaPackage, different(singleTypeName)(2), complexTypeName(maxDepth = 3)) { (pkg, outerTypes, x) =>
      val List(a, b) = outerTypes
      val expected   = s"$pkg$a[$x]"
      val actual     = s"$pkg$b[$x]"
      val msg        = new StringDiff(expected, actual, AddSE).diff("expected: %s actual: %s")
      msg == s"""expected: $pkg$S$a$E[$x] actual: $pkg$S$b$E[$x]"""
    }

  property("any complex X vs Y is a full diff when X and Y don't have common suffix nor prefix") =
    forAll(different(complexTypeName(maxDepth = 4))(2).suchThat(noCommonPrefixSuffix))(twoTypesAreFullyDiff)

  property("any single X vs Y is a full diff") = forAll(different(singleTypeName)(2))(twoTypesAreFullyDiff)

  def noCommonPrefixSuffix(twoTypes: List[String]): Boolean = {
    val List(x, y) = twoTypes
    x.head != y.head && x.last != y.last
  }

}


================================================
FILE: model/src/test/scala/com/softwaremill/clippy/StringDiffTest.scala
================================================
package com.softwaremill.clippy

import org.scalatest.{FlatSpec, Matchers}

class StringDiffTest extends FlatSpec with Matchers {

  val S     = "S"
  val E     = "E"
  val AddSE = (s: String) => S + s + E

  val testData = List(
    (
      "Super[String, String]",
      "Super[Option[String], String]",
      "expected Super[" + S + "String" + E + ", String] but was Super[" + S + "Option[String]" + E + ", String]"
    ),
    (
      "Cool[String, String]",
      "Super[Option[String], String]",
      "expected " + S + "Cool[String" + E + ", String] but was " + S + "Super[Option[String]" + E + ", String]"
    ),
    (
      "(String, String)",
      "Super[Option[String], String]",
      "expected " + S + "(String, String)" + E + " but was " + S + "Super[Option[String], String]" + E
    ),
    (
      "Map[Long, Double]",
      "Map[String, Double]",
      "expected Map[" + S + "Long" + E + ", Double] but was Map[" + S + "String" + E + ", Double]"
    ),
    (
      "(Int, Int, Float, Int, Char)",
      "(Int, Int, Int, Char)",
      "expected (Int, Int, " + S + "Float" + E + ", Int, Char) but was (Int, Int, Int, Char)"
    )
  )

  "StringDiff" should "diff" in {
    for ((expected, actual, expectedDiff) <- testData) {

      val diff = new StringDiff(expected, actual, AddSE).diff("expected %s but was %s")

      diff should be(expectedDiff)
    }
  }

  it should "find common prefix" in {
    new StringDiff("Map[Long, Double]", "Map[String, Double]", AddSE).findCommonPrefix() should be("Map[")
  }

  it should "find common suffix" in {
    new StringDiff("Map[Long, Double]", "Map[String, Double]", AddSE).findCommonSuffix() should be(", Double]")
  }
}


================================================
FILE: model/src/test/scala/com/softwaremill/clippy/TypeNamesGenerators.scala
================================================
package com.softwaremill.clippy

import org.scalacheck.Gen

trait TypeNamesGenerators {

  import Gen._

  def typeNameChar = frequency((1, numChar), (3, Gen.const('_')), (14, alphaChar))

  val specialTypeName = Gen.oneOf(List("Option", "Operation", "Op", "String", "Long", "Set", "Aux"))

  def javaPackage: Gen[String] =
    for {
      depth <- Gen.choose(0, 3)
      names <- Gen.listOfN(depth, packageIdentifer)
    } yield {
      val str = names.filter(_.nonEmpty).mkString(".")
      if (str.nonEmpty)
        str + "."
      else
        str
    }

  def packageIdentifer: Gen[String] =
    for {
      c  <- alphaLowerChar
      cs <- Gen.resize(7, listOf(alphaNumChar))
    } yield {
      (c :: cs).mkString
    }

  def randomTypeWithPackage: Gen[String] =
    for {
      p <- javaPackage
      t <- randomTypeWithPackage
    } yield p + t

  def randomTypeName: Gen[String] =
    for {
      c  <- alphaChar
      cs <- Gen.resize(7, listOf(typeNameChar))
    } yield (c :: cs).mkString

  def singleTypeName = frequency((3, randomTypeName), (7, specialTypeName))

  def functionalTypeName(maxResultDepth: Int): Gen[String] =
    for {
      argsMemberCount <- Gen.choose(2, 4)
      args            <- Gen.oneOf(singleTypeName, tupleTypeName(depth = 0, argsMemberCount))
      result          <- complexTypeName(maxResultDepth)
    } yield {
      s"$args => $result"
    }

  def genericTypeName(depth: Int, memberCount: Int): Gen[String] =
    for {
      name       <- singleTypeName
      innerNames <- Gen.listOfN(memberCount, if (depth == 0) singleTypeName else complexTypeName(depth - 1))
    } yield s"$name[${innerNames.mkString(", ")}]"

  def tupleTypeName(depth: Int, memberCount: Int): Gen[String] =
    for {
      innerNames <- Gen.listOfN(memberCount, if (depth == 0) singleTypeName else complexTypeName(depth - 1))
    } yield s"(${innerNames.mkString(", ")})"

  def different[T](gen: Gen[T]) =
    (count: Int) =>
      Gen.listOfN(count, gen).suchThat { list =>
        list.distinct == list
    }

  def typesWithCommonPrefix =
    (count: Int) =>
      for {
        types        <- different(singleTypeName)(count)
        commonPrefix <- singleTypeName
      } yield types.map(commonPrefix + _)

  def typesWithCommonSuffix =
    (count: Int) =>
      for {
        types        <- different(singleTypeName)(count)
        commonSuffix <- singleTypeName
      } yield types.map(_ + commonSuffix)

  def flatTypeIdentifier =
    for {
      tupleMemberCount <- Gen.choose(2, 4)
      generator <- Gen.oneOf(
        Seq(singleTypeName, tupleTypeName(0, tupleMemberCount), innerTypeName(0), functionalTypeName(0))
      )
      typeStr <- generator
    } yield typeStr

  def deepTypeName(maxDepth: Int): Gen[String] =
    for {
      genericMemberCount <- Gen.choose(1, 3)
      tupleMemberCount   <- Gen.choose(2, 4)
      generator <- Gen.oneOf(
        Seq(
          singleTypeName,
          tupleTypeName(maxDepth, tupleMemberCount),
          genericTypeName(maxDepth, genericMemberCount),
          innerTypeName(maxDepth),
          functionalTypeName(maxDepth)
        )
      )
      typeStr <- generator
    } yield typeStr

  def complexTypeName(maxDepth: Int): Gen[String] =
    if (maxDepth == 0)
      flatTypeIdentifier
    else
      deepTypeName(maxDepth)

  def innerTypeName(maxDepth: Int): Gen[String] =
    for {
      outerName <- singleTypeName
      innerType <- complexTypeName(maxDepth)
    } yield s"$outerName#$innerType"
}


================================================
FILE: package.json
================================================
{
  "name": "scala-clippy-site",
    "dependencies": {
      "jsdom": "9.12.0"
    }

}


================================================
FILE: plugin/src/main/resources/scalac-plugin.xml
================================================
<plugin>
    <name>clippy</name>
    <classname>com.softwaremill.clippy.ClippyPlugin</classname>
</plugin>

================================================
FILE: plugin/src/main/scala/com/softwaremill/clippy/AdviceLoader.scala
================================================
package com.softwaremill.clippy

import java.io._
import java.net.{HttpURLConnection, URL}
import java.util.zip.GZIPInputStream

import com.softwaremill.clippy.Utils._

import scala.concurrent.{ExecutionContext, Future}
import scala.io.Source
import scala.tools.nsc.Global
import scala.util.{Failure, Success, Try}
import scala.collection.JavaConverters._

class AdviceLoader(
    global: Global,
    url: String,
    localStoreDir: File,
    projectAdviceFile: Option[File],
    localAdviceFiles: List[URL]
)(implicit ec: ExecutionContext) {
  private val OneDayMillis = 1000L * 60 * 60 * 24

  private val localStore = new File(localStoreDir, "clippy.json.gz")

  private lazy val resourcesAdvice: AdvicesAndWarnings =
    localAdviceFiles.map(loadAdviceFromUrL).reduceOption(_ ++ _).getOrElse(AdvicesAndWarnings.empty)

  private def loadAdviceFromUrL(url: URL): AdvicesAndWarnings =
    TryWith(url.openStream())(inputStreamToClippy(_)) match {
      case Success(clippyData) => AdvicesAndWarnings(clippyData.advices, clippyData.fatalWarnings)
      case Failure(_) =>
        global.inform(s"Cannot load advice from ${url.getPath} : Ignoring.")
        AdvicesAndWarnings.empty
    }

  private lazy val projectAdvice: AdvicesAndWarnings =
    projectAdviceFile.map(file => loadAdviceFromUrL(file.toURI.toURL)).getOrElse(AdvicesAndWarnings.empty)

  def load(): Future[Clippy] = {
    val localClippy = if (!localStore.exists()) {
      fetchStoreParse()
    } else {
      val needsUpdate = System.currentTimeMillis() - localStore.lastModified() > OneDayMillis

      // fetching in the background
      val runningFetch = if (needsUpdate) {
        Some(fetchStoreParseInBackground())
      } else None

      val localLoad = Try(loadLocally()) match {
        case Success(v) => Future.successful(v)
        case Failure(t) => Future.failed(t)
      }

      localLoad.map(bytes => inputStreamToClippy(decodeZippedBytes(bytes))).recoverWith {
        case e: Exception =>
          global.warning(s"Cannot load advice from local store: $localStore. Trying to fetch from server")
          runningFetch.getOrElse(fetchStoreParse())
      }
    }

    // Add in advice found in resources and project root
    localClippy.map(
      clippy =>
        clippy.copy(
          advices = (projectAdvice.advices ++ resourcesAdvice.advices ++ clippy.advices).distinct,
          fatalWarnings =
            (projectAdvice.fatalWarnings ++ resourcesAdvice.fatalWarnings ++ clippy.fatalWarnings).distinct
      )
    )
  }

  private def fetchStoreParse(): Future[Clippy] =
    fetchCompressedJson()
      .map { bytes =>
        storeLocallyInBackground(bytes)
        bytes
      }
      .map(bytes => inputStreamToClippy(decodeZippedBytes(bytes)))
      .recover {
        case e: Exception =>
          global.inform(s"Unable to load/store local Clippy advice due to: ${e.getMessage}")
          Clippy(ClippyBuildInfo.version, Nil, Nil)
      }
      .andThen { case Success(v) => v.checkPluginVersion(ClippyBuildInfo.version, println) }

  private def fetchStoreParseInBackground(): Future[Clippy] = {
    val f = fetchStoreParse()
    f.onFailure {
      case e: Exception => global.inform(s"Cannot fetch data from $url due to: $e")
    }
    f
  }

  private def fetchCompressedJson(): Future[Array[Byte]] = Future {
    val u    = new URL(url)
    val conn = u.openConnection().asInstanceOf[HttpURLConnection]

    try {
      conn.setRequestMethod("GET")
      inputStreamToBytes(conn.getInputStream)
    } finally conn.disconnect()
  }

  private def decodeZippedBytes(bytes: Array[Byte]): GZIPInputStream = new GZIPInputStream(decodeUtf8Bytes(bytes))

  private def decodeUtf8Bytes(bytes: Array[Byte]): ByteArrayInputStream = new ByteArrayInputStream(bytes)

  private def inputStreamToClippy(byteStream: InputStream): Clippy = {
    import org.json4s.native.JsonMethods._
    val data = Source.fromInputStream(byteStream, "UTF-8").getLines().mkString("\n")
    Clippy
      .fromJson(parse(data))
      .getOrElse(throw new IllegalArgumentException("Cannot deserialize Clippy data"))
  }

  private def storeLocally(bytes: Array[Byte]): Unit = {
    if (!localStoreDir.isDirectory && !localStoreDir.mkdir()) {
      throw new IOException(s"Cannot create directory $localStoreDir")
    }
    TryWith(new FileOutputStream(localStore))(_.write(bytes)).get
  }

  private def storeLocallyInBackground(bytes: Array[Byte]): Unit =
    Future {
      runNonDaemon {
        storeLocally(bytes)
      }
    }.onFailure {
      case e: Exception => global.inform(s"Cannot store data at $localStore due to: $e")
    }

  private def loadLocally(source: File = localStore): Array[Byte] = inputStreamToBytes(new FileInputStream(source))
}

object AdviceLoader {
  val localFile = "clippy.json.gz"
}


================================================
FILE: plugin/src/main/scala/com/softwaremill/clippy/ClippyPlugin.scala
================================================
package com.softwaremill.clippy

import java.io.File
import java.net.{URL, URLClassLoader}
import java.util.concurrent.TimeoutException

import scala.collection.JavaConverters._
import scala.concurrent.Await
import scala.concurrent.duration._
import scala.reflect.internal.util.Position
import scala.tools.nsc.Global
import scala.tools.nsc.plugins.Plugin
import scala.tools.nsc.plugins.PluginComponent
import scala.tools.util.PathResolver

final case class AdvicesAndWarnings(advices: List[Advice], fatalWarnings: List[Warning]) {
  def ++(other: AdvicesAndWarnings): AdvicesAndWarnings =
    copy(advices = advices ++ other.advices, fatalWarnings = fatalWarnings ++ other.fatalWarnings)
}

object AdvicesAndWarnings {
  def empty: AdvicesAndWarnings = AdvicesAndWarnings(Nil, Nil)
}

class ClippyPlugin(val global: Global) extends Plugin {

  override val name: String = "clippy"

  override val description: String = "gives good advice"

  var url: String                         = ""
  var colorsConfig: ColorsConfig          = ColorsConfig.Disabled
  var testMode                            = false
  val DefaultStoreDir                     = new File(System.getProperty("user.home"), ".clippy")
  var localStoreDir                       = DefaultStoreDir
  var projectRoot: Option[File]           = None
  var initialFatalWarnings: List[Warning] = Nil

  lazy val localAdviceFiles = {
    val classPathURLs = new PathResolver(global.settings).result.asURLs
    val classLoader   = new URLClassLoader(classPathURLs.toArray, getClass.getClassLoader)
    classLoader.getResources("clippy.json").asScala.toList
  }

  lazy val advicesAndWarnings =
    loadAdvicesAndWarnings(url, localStoreDir, projectRoot, localAdviceFiles)

  def getFatalWarningAdvice(warningText: String): Option[Warning] =
    advicesAndWarnings.fatalWarnings.find(warning => warning.pattern.matches(ExactT(warningText)))

  def handleError(pos: Position, msg: String): String = {
    val parsedMsg = CompilationErrorParser.parse(msg)
    val matchers  = advicesAndWarnings.advices.map(_.errMatching.lift)
    val matches   = matchers.flatMap(pf => parsedMsg.flatMap(pf)).distinct

    matches.size match {
      case 0 =>
        (parsedMsg, colorsConfig) match {
          case (Some(tme: TypeMismatchError[ExactT]), cc: ColorsConfig.Enabled) =>
            prettyPrintTypeMismatchError(tme, cc)
          case _ => msg
        }
      case 1 =>
        matches.mkString(s"$msg\n Clippy advises: ", "", "")
      case _ =>
        matches.mkString(s"$msg\n Clippy advises you to try one of these solutions: \n   ", "\n or\n   ", "")
    }
  }

  override def processOptions(options: List[String], error: (String) => Unit): Unit = {
    colorsConfig = colorsFromOptions(options)
    url = urlFromOptions(options)
    testMode = testModeFromOptions(options)
    localStoreDir = localStoreDirFromOptions(options)
    projectRoot = projectRootFromOptions(options)
    initialFatalWarnings = initialFatalWarningsFromOptions(options)
    if (testMode) {
      val r = global.reporter
      global.reporter = new FailOnWarningsReporter(
        new DelegatingReporter(r, handleError, colorsConfig),
        getFatalWarningAdvice,
        colorsConfig
      )
    }
  }

  override val components: List[PluginComponent] = {

    List(
      new InjectReporter(handleError, getFatalWarningAdvice, global) {
        override def colorsConfig = ClippyPlugin.this.colorsConfig
        override def isEnabled    = !testMode
      },
      new RestoreReporter(global) {
        override def isEnabled = !testMode
      }
    )
  }

  private def prettyPrintTypeMismatchError(tme: TypeMismatchError[ExactT], colors: ColorsConfig.Enabled): String = {
    val colorDiff = (s: String) => colors.diff(s).toString
    val plain     = new StringDiff(tme.found.toString, tme.required.toString, colorDiff)

    val expandsMsg = if (tme.hasExpands) {
      val reqExpandsTo   = tme.requiredExpandsTo.getOrElse(tme.required)
      val foundExpandsTo = tme.foundExpandsTo.getOrElse(tme.found)
      val expands        = new StringDiff(foundExpandsTo.toString, reqExpandsTo.toString, colorDiff)
      s"""${expands.diff("\nExpanded types:\nfound   : %s\nrequired: %s\"")}"""
    } else
      ""

    s""" type mismatch;
         | Clippy advises, pay attention to the marked parts:
         | ${plain.diff("found   : %s\n required: %s")}$expandsMsg${tme.notesAfterNewline}""".stripMargin
  }

  private def urlFromOptions(options: List[String]): String =
    options.find(_.startsWith("url=")).map(_.substring(4)).getOrElse("https://www.scala-clippy.org") + "/api/advices"

  private def colorsFromOptions(options: List[String]): ColorsConfig =
    if (boolFromOptions(options, "colors")) {

      def colorToFansi(color: String): fansi.Attrs = color match {
        case "black"         => fansi.Color.Black
        case "light-gray"    => fansi.Color.LightGray
        case "dark-gray"     => fansi.Color.DarkGray
        case "red"           => fansi.Color.Red
        case "light-red"     => fansi.Color.LightRed
        case "green"         => fansi.Color.Green
        case "light-green"   => fansi.Color.LightGreen
        case "yellow"        => fansi.Color.Yellow
        case "light-yellow"  => fansi.Color.LightYellow
        case "blue"          => fansi.Color.Blue
        case "light-blue"    => fansi.Color.LightBlue
        case "magenta"       => fansi.Color.Magenta
        case "light-magenta" => fansi.Color.LightMagenta
        case "cyan"          => fansi.Color.Cyan
        case "light-cyan"    => fansi.Color.LightCyan
        case "white"         => fansi.Color.White
        case "none"          => fansi.Attrs.Empty
        case x =>
          global.warning("Unknown color: " + x)
          fansi.Attrs.Empty
      }

      val partColorPattern = "colors-(.*)=(.*)".r
      options.filter(_.startsWith("colors-")).foldLeft(ColorsConfig.defaultEnabled) {
        case (current, partAndColor) =>
          val partColorPattern(part, colorStr) = partAndColor
          val color                            = colorToFansi(colorStr.trim.toLowerCase())
          part.trim.toLowerCase match {
            case "diff"    => current.copy(diff = color)
            case "comment" => current.copy(comment = color)
            case "type"    => current.copy(`type` = color)
            case "literal" => current.copy(literal = color)
            case "keyword" => current.copy(keyword = color)
            case "reset"   => current.copy(reset = color)
            case x =>
              global.warning("Unknown colored part: " + x)
              current
          }
      }
    } else ColorsConfig.Disabled

  private def testModeFromOptions(options: List[String]): Boolean = boolFromOptions(options, "testmode")

  private def boolFromOptions(options: List[String], option: String): Boolean =
    options
      .find(_.startsWith(s"$option="))
      .map(_.substring(option.length + 1))
      .getOrElse("false")
      .toBoolean

  private def projectRootFromOptions(options: List[String]): Option[File] =
    options
      .find(_.startsWith("projectRoot="))
      .map(_.substring(12))
      .map(new File(_, ".clippy.json"))
      .filter(_.exists())

  private def initialFatalWarningsFromOptions(options: List[String]): List[Warning] =
    options
      .find(_.startsWith("fatalWarnings="))
      .map(_.substring(14))
      .map { str =>
        str.split('|').toList.map(str => Warning(RegexT(str), text = None))
      }
      .getOrElse(Nil)

  private def localStoreDirFromOptions(options: List[String]): File =
    options.find(_.startsWith("store=")).map(_.substring(6)).map(new File(_)).getOrElse(DefaultStoreDir)

  private def loadAdvicesAndWarnings(
      url: String,
      localStoreDir: File,
      projectAdviceFile: Option[File],
      localAdviceFiles: List[URL]
  ): AdvicesAndWarnings = {
    implicit val ec = scala.concurrent.ExecutionContext.Implicits.global

    try {
      val clippyData = Await
        .result(
          new AdviceLoader(global, url, localStoreDir, projectAdviceFile, localAdviceFiles).load(),
          10.seconds
        )
      AdvicesAndWarnings(clippyData.advices, clippyData.fatalWarnings ++ initialFatalWarnings)
    } catch {
      case e: TimeoutException =>
        global.warning(s"Unable to read advices from $url and store to $localStoreDir within 10 seconds.")
        AdvicesAndWarnings.empty
      case e: Exception =>
        global.warning(s"Exception when reading advices from $url and storing to $localStoreDir: $e")
        AdvicesAndWarnings.empty
    }
  }
}


================================================
FILE: plugin/src/main/scala/com/softwaremill/clippy/ColorsConfig.scala
================================================
package com.softwaremill.clippy

sealed trait ColorsConfig

object ColorsConfig {
  case object Disabled extends ColorsConfig

  case class Enabled(
      diff: fansi.Attrs,
      comment: fansi.Attrs,
      `type`: fansi.Attrs,
      literal: fansi.Attrs,
      keyword: fansi.Attrs,
      reset: fansi.Attrs
  ) extends ColorsConfig

  val defaultEnabled = Enabled(
    fansi.Color.Red,
    fansi.Color.Blue,
    fansi.Color.Green,
    fansi.Color.Magenta,
    fansi.Color.Yellow,
    fansi.Attr.Reset
  )
}


================================================
FILE: plugin/src/main/scala/com/softwaremill/clippy/FailOnWarningsReporter.scala
================================================
package com.softwaremill.clippy

import scala.reflect.internal.util.Position
import scala.tools.nsc.reporters.Reporter

class FailOnWarningsReporter(r: Reporter, warningMatcher: String => Option[Warning], colorsConfig: ColorsConfig)
    extends Reporter {
  override protected def info0(pos: Position, msg: String, severity: Severity, force: Boolean) = {
    val wrapped = DelegatingPosition.wrap(pos, colorsConfig)

    // cannot delegate to info0 as it's protected, hence special-casing on the possible severity values
    if (severity == INFO) {
      r.info(wrapped, msg, force)
    } else if (severity == WARNING) {
      warning(wrapped, msg)
    } else if (severity == ERROR) {
      error(wrapped, msg)
    } else {
      error(wrapped, s"UNKNOWN SEVERITY: $severity\n$msg")
    }
  }

  override def echo(msg: String)                   = r.echo(msg)
  override def comment(pos: Position, msg: String) = r.comment(DelegatingPosition.wrap(pos, colorsConfig), msg)
  override def hasErrors                           = r.hasErrors || cancelled
  override def reset() = {
    cancelled = false
    r.reset()
  }

  //

  override def echo(pos: Position, msg: String)     = r.echo(DelegatingPosition.wrap(pos, colorsConfig), msg)
  override def errorCount                           = r.errorCount
  override def warningCount                         = r.warningCount
  override def hasWarnings                          = r.hasWarnings
  override def flush()                              = r.flush()
  override def count(severity: Severity): Int       = r.count(conv(severity))
  override def resetCount(severity: Severity): Unit = r.resetCount(conv(severity))

  //

  private def conv(s: Severity): r.Severity = s match {
    case INFO    => r.INFO
    case WARNING => r.WARNING
    case ERROR   => r.ERROR
  }

  //
  override def warning(pos: Position, msg: String) = {
    val wrapped = DelegatingPosition.wrap(pos, colorsConfig)
    warningMatcher(msg) match {
      case Some(Warning(_, adviceOpt)) =>
        val finalMsg = adviceOpt.map(advice => msg + s"\nClippy advises: $advice").getOrElse(msg)
        r.error(wrapped, finalMsg)
      case None =>
        r.warning(wrapped, msg)
    }
  }

  override def error(pos: Position, msg: String) = {
    val wrapped = DelegatingPosition.wrap(pos, colorsConfig)
    r.error(wrapped, msg)
  }
}


================================================
FILE: plugin/src/main/scala/com/softwaremill/clippy/Highlighter.scala
================================================
package com.softwaremill.clippy

import fastparse.all._

import scalaparse.Scala._
import scalaparse.syntax.Identifiers._

/**
  * Copied from https://github.com/lihaoyi/Ammonite/blob/master/amm/repl/src/main/scala/ammonite/repl/Highlighter.scala
  */
object Highlighter {

  object BackTicked {
    def unapplySeq(s: Any): Option[List[String]] =
      "`([^`]+)`".r.unapplySeq(s.toString)
  }

  def flattenIndices(
      boundedIndices: Seq[(Int, fansi.Attrs, Boolean)],
      buffer: Vector[Char]
  ) =
    boundedIndices
      .sliding(2)
      .map {
        case Seq((s, c1, _), (e, c2, _)) =>
          assert(e >= s, s"s: $s e: $e")
          c1(fansi.Str(buffer.slice(s, e), errorMode = fansi.ErrorMode.Sanitize))
      }
      .reduce(_ ++ _)
      .render
      .toVector

  def defaultHighlight(
      buffer: Vector[Char],
      comment: fansi.Attrs,
      `type`: fansi.Attrs,
      literal: fansi.Attrs,
      keyword: fansi.Attrs,
      reset: fansi.Attrs
  ) = {
    val boundedIndices = defaultHighlightIndices(buffer, comment, `type`, literal, keyword, reset)
    flattenIndices(boundedIndices, buffer)
  }
  def defaultHighlightIndices(
      buffer: Vector[Char],
      comment: fansi.Attrs,
      `type`: fansi.Attrs,
      literal: fansi.Attrs,
      keyword: fansi.Attrs,
      reset: fansi.Attrs
  ) = Highlighter.highlightIndices(
    Parsers.Splitter,
    buffer, {
      case Literals.Expr.Interp | Literals.Pat.Interp       => reset
      case Literals.Comment                                 => comment
      case ExprLiteral                                      => literal
      case TypeId                                           => `type`
      case BackTicked(body) if alphaKeywords.contains(body) => keyword
    },
    reset
  )
  def highlightIndices[T](
      parser: Parser[_],
      buffer: Vector[Char],
      ruleColors: PartialFunction[Parser[_], T],
      endColor: T
  ): Seq[(Int, T, Boolean)] = {
    val indices = {
      var indices = collection.mutable.Buffer((0, endColor, false))
      var done    = false
      val input   = buffer.mkString
      parser.parse(
        input,
        instrument = (rule, idx, res) => {
          for (color <- ruleColors.lift(rule)) {
            val closeColor = indices.last._2
            val startIndex = indices.length
            indices += ((idx, color, true))

            res() match {
              case s: Parsed.Success[_] =>
                val prev = indices(startIndex - 1)._1

                if (idx < prev && s.index <= prev) {
                  indices.remove(startIndex, indices.length - startIndex)

                }
                while (idx < indices.last._1 && s.index <= indices.last._1) {
                  indices.remove(indices.length - 1)
                }
                indices += ((s.index, closeColor, false))
                if (s.index == buffer.length) done = true
              case f: Parsed.Failure
                  if f.index == buffer.length
                    && (WL ~ End).parse(input, idx).isInstanceOf[Parsed.Failure] =>
                // EOF, stop all further parsing
                done = true
              case _ => // hard failure, or parsed nothing. Discard all progress
                indices.remove(startIndex, indices.length - startIndex)
            }
          }
        }
      )
      indices
    }
    // Make sure there's an index right at the start and right at the end! This
    // resets the colors at the snippet's end so they don't bleed into later output
    indices ++ Seq((999999999, endColor, false))
  }
  def highlight(
      parser: Parser[_],
      buffer: Vector[Char],
      ruleColors: PartialFunction[Parser[_], fansi.Attrs],
      endColor: fansi.Attrs
  ) = {
    val boundedIndices = highlightIndices(parser, buffer, ruleColors, endColor)
    flattenIndices(boundedIndices, buffer)
  }

}

object Parsers {
  import fastparse.noApi._

  import scalaparse.Scala._
  import WhitespaceApi._

  val Prelude = P((Annot ~ OneNLMax).rep ~ (Mod ~/ Pass).rep)
  val Statement =
    P(scalaparse.Scala.TopPkgSeq | scalaparse.Scala.Import | Prelude ~ BlockDef | StatCtx.Expr)
  def StatementBlock(blockSep: P0) =
    P(Semis.? ~ (!blockSep ~ Statement ~~ WS ~~ (Semis | End)).!.repX)
  val Splitter = P(StatementBlock(Fail) ~ WL ~ End)
}


================================================
FILE: plugin/src/main/scala/com/softwaremill/clippy/InjectReporter.scala
================================================
package com.softwaremill.clippy

import scala.reflect.internal.util.Position
import scala.tools.nsc.plugins.PluginComponent
import scala.tools.nsc.{Global, Phase}

/**
  * Responsible for replacing the global reporter with our custom Clippy reporter after the first phase of compilation.
  */
abstract class InjectReporter(
    handleError: (Position, String) => String,
    getFatalWarningAdvice: String => Option[Warning],
    superGlobal: Global
) extends PluginComponent {

  override val global = superGlobal
  def colorsConfig: ColorsConfig
  def isEnabled: Boolean
  override val runsAfter  = List[String]("parser")
  override val runsBefore = List[String]("namer")
  override val phaseName  = "inject-clippy-reporter"

  override def newPhase(prev: Phase) = new Phase(prev) {

    override def name = phaseName

    override def description = "Switches the reporter to Clippy's reporter chain"

    override def run(): Unit =
      if (isEnabled) {
        val r = global.reporter
        global.reporter = new FailOnWarningsReporter(
          new DelegatingReporter(r, handleError, colorsConfig),
          getFatalWarningAdvice,
          colorsConfig
        )
      }
  }

}


================================================
FILE: plugin/src/main/scala/com/softwaremill/clippy/RestoreReporter.scala
================================================
package com.softwaremill.clippy

import scala.tools.nsc.plugins.PluginComponent
import scala.tools.nsc.{Global, Phase}

/**
  * Replaces global reporter back with the original global reporter. Sbt uses its own xsbt.DelegatingReporter
  * which we cannot replace outside of Scala compilation phases. This component makes sure that before the compilation
  * is over, original reporter gets reassigned to the global field.
  */
class RestoreReporter(val global: Global) extends PluginComponent {

  val originalReporter    = global.reporter
  def isEnabled: Boolean  = true
  override val runsAfter  = List[String]("jvm")
  override val runsBefore = List[String]("terminal")
  override val phaseName  = "restore-original-reporter"

  override def newPhase(prev: Phase) = new Phase(prev) {

    override def name = phaseName

    override def description = "Switches the reporter from Clippy's DelegatingReporter back to original one"

    override def run(): Unit =
      if (isEnabled)
        global.reporter = originalReporter
  }

}


================================================
FILE: plugin/src/main/scala/com/softwaremill/clippy/Utils.scala
================================================
package com.softwaremill.clippy

import java.io.{ByteArrayOutputStream, InputStream}
import java.io.Closeable
import scala.util.control.NonFatal
import scala.util.{Failure, Try}

object Utils {

  /**
    * All future callbacks will be running on a daemon thread pool which can be interrupted at any time if the JVM
    * exits, if the compiler finished its job.
    *
    * Here we are trying to make as sure as possible (unless the JVM crashes) that we'll run the given code.
    */
  def runNonDaemon(t: => Unit) = {
    val shutdownHook = new Thread() {
      private val lock             = new Object
      @volatile private var didRun = false

      override def run() =
        lock.synchronized {
          if (!didRun) {
            t
            didRun = true
          }
        }
    }

    Runtime.getRuntime.addShutdownHook(shutdownHook)
    try shutdownHook.run()
    finally Runtime.getRuntime.removeShutdownHook(shutdownHook)
  }

  def inputStreamToBytes(is: InputStream): Array[Byte] =
    try {
      val baos = new ByteArrayOutputStream()
      val buf  = new Array[Byte](512)
      var read = 0
      while ({ read = is.read(buf, 0, buf.length); read } != -1) {
        baos.write(buf, 0, read)
      }
      baos.toByteArray
    } finally is.close()

  object TryWith {
    def apply[C <: Closeable, R](resource: => C)(f: C => R): Try[R] =
      Try(resource).flatMap(resourceInstance => {
        try {
          val returnValue = f(resourceInstance)
          Try(resourceInstance.close()).map(_ => returnValue)
        } catch {
          case NonFatal(exceptionInFunction) =>
            try {
              resourceInstance.close()
              Failure(exceptionInFunction)
            } catch {
              case NonFatal(exceptionInClose) =>
                exceptionInFunction.addSuppressed(exceptionInClose)
                Failure(exceptionInFunction)
            }
        }
      })
  }
}


================================================
FILE: plugin/src/main/scala-2.11/com/softwaremill/clippy/DelegatingPosition.scala
================================================
package com.softwaremill.clippy

import scala.reflect.internal.util.{NoPosition, Position, SourceFile}
import scala.reflect.macros.Attachments

class DelegatingPosition(delegate: Position, colorsConfig: ColorsConfig) extends Position {

  // used by scalac to report errors
  override def showError(msg: String): String = highlight(delegate.showError(msg))

  // used by sbt
  override def lineContent: String = highlight(delegate.lineContent)

  def highlight(str: String): String = colorsConfig match {
    case e: ColorsConfig.Enabled =>
      Highlighter
        .defaultHighlight(
          str.toVector,
          e.comment,
          e.`type`,
          e.literal,
          e.keyword,
          e.reset
        )
        .mkString
    case _ => str
  }

  // impl copied

  override def fail(what: String): Nothing = throw new UnsupportedOperationException(s"Position.$what on $this")

  // simple delegates with position wrapping when position is returned

  @scala.deprecated("use `point`")
  override def offset: Option[Int] = delegate.offset

  override def all: Set[Any] = delegate.all

  @scala.deprecated("use `focus`")
  override def toSingleLine: Position = DelegatingPosition.wrap(delegate.toSingleLine, colorsConfig)

  override def pos: Position = DelegatingPosition.wrap(delegate.pos, colorsConfig)

  override def get[T](implicit evidence$2: ClassManifest[T]): Option[T] = delegate.get(evidence$2)

  override def finalPosition: Pos = DelegatingPosition.wrap(delegate.finalPosition, colorsConfig)

  override def withPos(newPos: Position): Attachments { type Pos = DelegatingPosition.this.Pos } =
    delegate.withPos(newPos)

  @scala.deprecated("use `line`")
  override def safeLine: Int = delegate.safeLine

  override def isTransparent: Boolean = delegate.isTransparent

  override def contains[T](implicit evidence$3: ClassManifest[T]): Boolean = delegate.contains(evidence$3)

  override def isOffset: Boolean = delegate.isOffset

  @scala.deprecated("use `showDebug`")
  override def dbgString: String = delegate.dbgString

  override def isOpaqueRange: Boolean = delegate.isOpaqueRange

  override def update[T](attachment: T)(
      implicit evidence$4: ClassManifest[T]
  ): Attachments { type Pos = DelegatingPosition.this.Pos } = delegate.update(attachment)(evidence$4)

  override def pointOrElse(alt: Int): Int = delegate.pointOrElse(alt)

  @scala.deprecated("use `finalPosition`")
  override def inUltimateSource(source: SourceFile): Position =
    DelegatingPosition.wrap(delegate.inUltimateSource(source), colorsConfig)

  override def isDefined: Boolean = delegate.isDefined

  override def makeTransparent: Position = DelegatingPosition.wrap(delegate.makeTransparent, colorsConfig)

  override def isRange: Boolean = delegate.isRange

  override def source: SourceFile = delegate.source

  override def remove[T](
      implicit evidence$5: ClassManifest[T]
  ): Attachments { type Pos = DelegatingPosition.this.Pos } = delegate.remove(evidence$5)

  override def withStart(start: Int): Position = DelegatingPosition.wrap(delegate.withStart(start), colorsConfig)

  @scala.deprecated("use `lineCaret`")
  override def lineWithCarat(maxWidth: Int): (String, String) = delegate.lineWithCarat(maxWidth)

  override def start: Int = delegate.start

  override def withPoint(point: Int): Position = DelegatingPosition.wrap(delegate.withPoint(point), colorsConfig)

  override def point: Int = delegate.point

  override def isEmpty: Boolean = delegate.isEmpty

  override def end: Int = delegate.end

  override def withEnd(end: Int): Position = DelegatingPosition.wrap(delegate.withEnd(end), colorsConfig)

  @scala.deprecated("Use `withSource(source)` and `withShift`")
  override def withSource(source: SourceFile, shift: Int): Position =
    DelegatingPosition.wrap(delegate.withSource(source, shift), colorsConfig)

  override def withSource(source: SourceFile): Position =
    DelegatingPosition.wrap(delegate.withSource(source), colorsConfig)

  @scala.deprecated("Use `start` instead")
  override def startOrPoint: Int = delegate.startOrPoint

  override def withShift(shift: Int): Position = DelegatingPosition.wrap(delegate.withShift(shift), colorsConfig)

  @scala.deprecated("Use `end` instead")
  override def endOrPoint: Int = delegate.endOrPoint

  override def focusStart: Position = DelegatingPosition.wrap(delegate.focusStart, colorsConfig)

  override def focus: Position = DelegatingPosition.wrap(delegate.focus, colorsConfig)

  override def focusEnd: Position = DelegatingPosition.wrap(delegate.focusEnd, colorsConfig)

  override def |(that: Position, poses: Position*): Position =
    DelegatingPosition.wrap(delegate.|(that, poses: _*), colorsConfig)

  override def |(that: Position): Position = DelegatingPosition.wrap(delegate.|(that), colorsConfig)

  override def ^(point: Int): Position = DelegatingPosition.wrap(delegate.^(point), colorsConfig)

  override def |^(that: Position): Position = DelegatingPosition.wrap(delegate.|^(that), colorsConfig)

  override def ^|(that: Position): Position = DelegatingPosition.wrap(delegate.^|(that), colorsConfig)

  override def union(pos: Position): Position = DelegatingPosition.wrap(delegate.union(pos), colorsConfig)

  override def includes(pos: Position): Boolean = delegate.includes(pos)

  override def properlyIncludes(pos: Position): Boolean = delegate.properlyIncludes(pos)

  override def precedes(pos: Position): Boolean = delegate.precedes(pos)

  override def properlyPrecedes(pos: Position): Boolean = delegate.properlyPrecedes(pos)

  override def sameRange(pos: Position): Boolean = delegate.sameRange(pos)

  override def overlaps(pos: Position): Boolean = delegate.overlaps(pos)

  override def line: Int = delegate.line

  override def column: Int = delegate.column

  override def lineCaret: String = delegate.lineCaret

  @scala.deprecated("use `lineCaret`")
  override def lineCarat: String = delegate.lineCarat

  override def showDebug: String = delegate.showDebug

  override def show: String = delegate.show
}

object DelegatingPosition {
  def wrap(pos: Position, colorsConfig: ColorsConfig): Position =
    pos match {
      case NoPosition                  => pos
      case wrapped: DelegatingPosition => wrapped
      case _                           => new DelegatingPosition(pos, colorsConfig)
    }
}


================================================
FILE: plugin/src/main/scala-2.11/com/softwaremill/clippy/DelegatingReporter.scala
================================================
package com.softwaremill.clippy

import scala.reflect.internal.util.Position
import scala.tools.nsc.reporters.Reporter

class DelegatingReporter(r: Reporter, handleError: (Position, String) => String, colorsConfig: ColorsConfig)
    extends Reporter {
  override protected def info0(pos: Position, msg: String, severity: Severity, force: Boolean) = {
    val wrapped = DelegatingPosition.wrap(pos, colorsConfig)

    // cannot delegate to info0 as it's protected, hence special-casing on the possible severity values
    if (severity == INFO) {
      r.info(wrapped, msg, force)
    } else if (severity == WARNING) {
      warning(wrapped, msg)
    } else if (severity == ERROR) {
      error(wrapped, msg)
    } else {
      error(wrapped, s"UNKNOWN SEVERITY: $severity\n$msg")
    }
  }

  override def echo(msg: String)                   = r.echo(msg)
  override def comment(pos: Position, msg: String) = r.comment(DelegatingPosition.wrap(pos, colorsConfig), msg)
  override def hasErrors                           = r.hasErrors || cancelled
  override def reset() = {
    cancelled = false
    r.reset()
  }

  //

  override def echo(pos: Position, msg: String)     = r.echo(DelegatingPosition.wrap(pos, colorsConfig), msg)
  override def warning(pos: Position, msg: String)  = r.warning(DelegatingPosition.wrap(pos, colorsConfig), msg)
  override def errorCount                           = r.errorCount
  override def warningCount                         = r.warningCount
  override def hasWarnings                          = r.hasWarnings
  override def flush()                              = r.flush()
  override def count(severity: Severity): Int       = r.count(conv(severity))
  override def resetCount(severity: Severity): Unit = r.resetCount(conv(severity))

  //

  private def conv(s: Severity): r.Severity = s match {
    case INFO    => r.INFO
    case WARNING => r.WARNING
    case ERROR   => r.ERROR
  }

  //

  override def error(pos: Position, msg: String) = {
    val wrapped = DelegatingPosition.wrap(pos, colorsConfig)
    r.error(wrapped, handleError(wrapped, msg))
  }
}


================================================
FILE: plugin/src/main/scala-2.12/com/softwaremill/clippy/DelegatingPosition.scala
================================================
package com.softwaremill.clippy

import scala.reflect.internal.util.{NoPosition, Position, SourceFile}
import scala.reflect.macros.Attachments

class DelegatingPosition(delegate: Position, colorsConfig: ColorsConfig) extends Position {

  // used by scalac to report errors
  override def showError(msg: String): String = highlight(delegate.showError(msg))

  // used by sbt
  override def lineContent: String = highlight(delegate.lineContent)

  def highlight(str: String): String = colorsConfig match {
    case e: ColorsConfig.Enabled =>
      Highlighter
        .defaultHighlight(
          str.toVector,
          e.comment,
          e.`type`,
          e.literal,
          e.keyword,
          e.reset
        )
        .mkString
    case _ => str
  }

  // impl copied

  override def fail(what: String): Nothing = throw new UnsupportedOperationException(s"Position.$what on $this")

  // simple delegates with position wrapping when position is returned

  @scala.deprecated("use `point`")
  override def offset: Option[Int] = delegate.offset

  override def all: Set[Any] = delegate.all

  @scala.deprecated("use `focus`")
  override def toSingleLine: Position = DelegatingPosition.wrap(delegate.toSingleLine, colorsConfig)

  override def pos: Position = DelegatingPosition.wrap(delegate.pos, colorsConfig)

  override def get[T](implicit evidence$2: ClassManifest[T]): Option[T] = delegate.get(evidence$2)

  override def finalPosition: Pos = DelegatingPosition.wrap(delegate.finalPosition, colorsConfig)

  override def withPos(newPos: Position): Attachments { type Pos = DelegatingPosition.this.Pos } =
    delegate.withPos(newPos)

  @scala.deprecated("use `line`")
  override def safeLine: Int = delegate.safeLine

  override def isTransparent: Boolean = delegate.isTransparent

  override def contains[T](implicit evidence$3: ClassManifest[T]): Boolean = delegate.contains(evidence$3)

  override def isOffset: Boolean = delegate.isOffset

  @scala.deprecated("use `showDebug`")
  override def dbgString: String = delegate.dbgString

  override def isOpaqueRange: Boolean = delegate.isOpaqueRange

  override def update[T](attachment: T)(
      implicit evidence$4: ClassManifest[T]
  ): Attachments { type Pos = DelegatingPosition.this.Pos } = delegate.update(attachment)(evidence$4)

  override def pointOrElse(alt: Int): Int = delegate.pointOrElse(alt)

  @scala.deprecated("use `finalPosition`")
  override def inUltimateSource(source: SourceFile): Position =
    DelegatingPosition.wrap(delegate.inUltimateSource(source), colorsConfig)

  override def isDefined: Boolean = delegate.isDefined

  override def makeTransparent: Position = DelegatingPosition.wrap(delegate.makeTransparent, colorsConfig)

  override def isRange: Boolean = delegate.isRange

  override def source: SourceFile = delegate.source

  override def remove[T](
      implicit evidence$5: ClassManifest[T]
  ): Attachments { type Pos = DelegatingPosition.this.Pos } = delegate.remove(evidence$5)

  override def withStart(start: Int): Position = DelegatingPosition.wrap(delegate.withStart(start), colorsConfig)

  @scala.deprecated("use `lineCaret`")
  override def lineWithCarat(maxWidth: Int): (String, String) = delegate.lineWithCarat(maxWidth)

  override def start: Int = delegate.start

  override def withPoint(point: Int): Position = DelegatingPosition.wrap(delegate.withPoint(point), colorsConfig)

  override def point: Int = delegate.point

  override def isEmpty: Boolean = delegate.isEmpty

  override def end: Int = delegate.end

  override def withEnd(end: Int): Position = DelegatingPosition.wrap(delegate.withEnd(end), colorsConfig)

  @scala.deprecated("Use `withSource(source)` and `withShift`")
  override def withSource(source: SourceFile, shift: Int): Position =
    DelegatingPosition.wrap(delegate.withSource(source, shift), colorsConfig)

  override def withSource(source: SourceFile): Position =
    DelegatingPosition.wrap(delegate.withSource(source), colorsConfig)

  @scala.deprecated("Use `start` instead")
  override def startOrPoint: Int = delegate.startOrPoint

  override def withShift(shift: Int): Position = DelegatingPosition.wrap(delegate.withShift(shift), colorsConfig)

  @scala.deprecated("Use `end` instead")
  override def endOrPoint: Int = delegate.endOrPoint

  override def focusStart: Position = DelegatingPosition.wrap(delegate.focusStart, colorsConfig)

  override def focus: Position = DelegatingPosition.wrap(delegate.focus, colorsConfig)

  override def focusEnd: Position = DelegatingPosition.wrap(delegate.focusEnd, colorsConfig)

  override def |(that: Position, poses: Position*): Position =
    DelegatingPosition.wrap(delegate.|(that, poses: _*), colorsConfig)

  override def |(that: Position): Position = DelegatingPosition.wrap(delegate.|(that), colorsConfig)

  override def ^(point: Int): Position = DelegatingPosition.wrap(delegate.^(point), colorsConfig)

  override def |^(that: Position): Position = DelegatingPosition.wrap(delegate.|^(that), colorsConfig)

  override def ^|(that: Position): Position = DelegatingPosition.wrap(delegate.^|(that), colorsConfig)

  override def union(pos: Position): Position = DelegatingPosition.wrap(delegate.union(pos), colorsConfig)

  override def includes(pos: Position): Boolean = delegate.includes(pos)

  override def properlyIncludes(pos: Position): Boolean = delegate.properlyIncludes(pos)

  override def precedes(pos: Position): Boolean = delegate.precedes(pos)

  override def properlyPrecedes(pos: Position): Boolean = delegate.properlyPrecedes(pos)

  override def sameRange(pos: Position): Boolean = delegate.sameRange(pos)

  override def overlaps(pos: Position): Boolean = delegate.overlaps(pos)

  override def line: Int = delegate.line

  override def column: Int = delegate.column

  override def lineCaret: String = delegate.lineCaret

  @scala.deprecated("use `lineCaret`")
  override def lineCarat: String = delegate.lineCarat

  override def showDebug: String = delegate.showDebug

  override def show: String = delegate.show
}

object DelegatingPosition {
  def wrap(pos: Position, colorsConfig: ColorsConfig): Position =
    pos match {
      case NoPosition                  => pos
      case wrapped: DelegatingPosition => wrapped
      case _                           => new DelegatingPosition(pos, colorsConfig)
    }
}


================================================
FILE: plugin/src/main/scala-2.12/com/softwaremill/clippy/DelegatingReporter.scala
================================================
package com.softwaremill.clippy

import scala.reflect.internal.util.Position
import scala.tools.nsc.reporters.Reporter

class DelegatingReporter(r: Reporter, handleError: (Position, String) => String, colorsConfig: ColorsConfig)
    extends Reporter {
  override protected def info0(pos: Position, msg: String, severity: Severity, force: Boolean) = {
    val wrapped = DelegatingPosition.wrap(pos, colorsConfig)

    // cannot delegate to info0 as it's protected, hence special-casing on the possible severity values
    if (severity == INFO) {
      r.info(wrapped, msg, force)
    } else if (severity == WARNING) {
      warning(wrapped, msg)
    } else if (severity == ERROR) {
      error(wrapped, msg)
    } else {
      error(wrapped, s"UNKNOWN SEVERITY: $severity\n$msg")
    }
  }

  override def echo(msg: String)                   = r.echo(msg)
  override def comment(pos: Position, msg: String) = r.comment(DelegatingPosition.wrap(pos, colorsConfig), msg)
  override def hasErrors                           = r.hasErrors || cancelled
  override def reset() = {
    cancelled = false
    r.reset()
  }

  //

  override def echo(pos: Position, msg: String)     = r.echo(DelegatingPosition.wrap(pos, colorsConfig), msg)
  override def warning(pos: Position, msg: String)  = r.warning(DelegatingPosition.wrap(pos, colorsConfig), msg)
  override def errorCount                           = r.errorCount
  override def warningCount                         = r.warningCount
  override def hasWarnings                          = r.hasWarnings
  override def flush()                              = r.flush()
  override def count(severity: Severity): Int       = r.count(conv(severity))
  override def resetCount(severity: Severity): Unit = r.resetCount(conv(severity))

  //

  private def conv(s: Severity): r.Severity = s match {
    case INFO    => r.INFO
    case WARNING => r.WARNING
    case ERROR   => r.ERROR
  }

  //

  override def error(pos: Position, msg: String) = {
    val wrapped = DelegatingPosition.wrap(pos, colorsConfig)
    r.error(wrapped, handleError(wrapped, msg))
  }
}


================================================
FILE: plugin-sbt/src/main/scala/com/softwaremill/clippy/ClippySbtPlugin.scala
================================================
package com.softwaremill.clippy

import sbt._
import sbt.Keys._

import scala.collection.mutable.ListBuffer

object ClippySbtPlugin extends AutoPlugin {
  object ClippyColor extends Enumeration {
    val Black        = Value("black")
    val LightGray    = Value("light-gray")
    val DarkGray     = Value("dark-gray")
    val Red          = Value("red")
    val LightRed     = Value("light-red")
    val Green        = Value("green")
    val LightGreen   = Value("light-green")
    val Yellow       = Value("yellow")
    val LightYellow  = Value("light-yellow")
    val Blue         = Value("blue")
    val LightBlue    = Value("light-blue")
    val Magenta      = Value("magenta")
    val LightMagenta = Value("light-magenta")
    val Cyan         = Value("cyan")
    val LightCyan    = Value("light-cyan")
    val White        = Value("white")
    val None         = Value("none")
  }

  object WarningPatterns {
    val NonExhaustiveMatch = "match may not be exhaustive[\\s\\S]*"
  }

  object autoImport {
    val clippyColorsEnabled = settingKey[Boolean]("Should Clippy color type mismatch diffs and highlight syntax")
    val clippyColorDiff     = settingKey[Option[ClippyColor.Value]]("The color to use for diffs, if other than default")
    val clippyColorComment =
      settingKey[Option[ClippyColor.Value]]("The color to use for comments, if other than default")
    val clippyColorType = settingKey[Option[ClippyColor.Value]]("The color to use for types, if other than default")
    val clippyColorLiteral =
      settingKey[Option[ClippyColor.Value]]("The color to use for literals, if other than default")
    val clippyColorKeyword =
      settingKey[Option[ClippyColor.Value]]("The color to use for keywords, if other than default")
    val clippyColorReset =
      settingKey[Option[ClippyColor.Value]]("The color to use for resetting to neutral, if other than default")
    val clippyUrl = settingKey[Option[String]]("Url from which to fetch advice, if other than default")
    val clippyLocalStoreDir =
      settingKey[Option[String]]("Directory where cached advice data should be stored, if other than default")
    val clippyProjectRoot =
      settingKey[Option[String]]("Project root in which project-specific advice is stored, if any")
    val clippyFatalWarnings =
      settingKey[List[String]]("Regular expressions of warning messages which should fail compilation")
    val NonExhaustiveMatch = "match may not be exhaustive[\\s\\S]*"
  }

  // in ~/.sbt auto import doesn't work, so providing aliases here for convenience
  val clippyColorsEnabled = autoImport.clippyColorsEnabled
  val clippyColorDiff     = autoImport.clippyColorDiff
  val clippyColorComment  = autoImport.clippyColorComment
  val clippyColorType     = autoImport.clippyColorType
  val clippyColorLiteral  = autoImport.clippyColorLiteral
  val clippyColorKeyword  = autoImport.clippyColorKeyword
  val clippyColorReset    = autoImport.clippyColorReset
  val clippyUrl           = autoImport.clippyUrl
  val clippyLocalStoreDir = autoImport.clippyLocalStoreDir
  val clippyProjectRoot   = autoImport.clippyProjectRoot
  val clippyFatalWarnings = autoImport.clippyFatalWarnings

  override def projectSettings = Seq(
    clippyColorsEnabled := false,
    clippyColorDiff := None,
    clippyColorComment := None,
    clippyColorType := None,
    clippyColorLiteral := None,
    clippyColorKeyword := None,
    clippyColorReset := None,
    clippyUrl := None,
    clippyLocalStoreDir := None,
    clippyProjectRoot := None,
    clippyFatalWarnings := Nil,
    addCompilerPlugin("com.softwaremill.clippy" %% "plugin" % ClippyBuildInfo.version classifier "bundle"),
    scalacOptions := {
      val result = ListBuffer(scalacOptions.value: _*)
      if (clippyColorsEnabled.value) result += "-P:clippy:colors=true"
      clippyColorDiff.value.foreach(c => result += s"-P:clippy:colors-diff=$c")
      clippyColorComment.value.foreach(c => result += s"-P:clippy:colors-comment=$c")
      clippyColorType.value.foreach(c => result += s"-P:clippy:colors-type=$c")
      clippyColorLiteral.value.foreach(c => result += s"-P:clippy:colors-literal=$c")
      clippyColorKeyword.value.foreach(c => result += s"-P:clippy:colors-keyword=$c")
      clippyColorReset.value.foreach(c => result += s"-P:clippy:colors-reset=$c")
      clippyUrl.value.foreach(c => result += s"-P:clippy:url=$c")
      clippyLocalStoreDir.value.foreach(c => result += s"-P:clippy:store=$c")
      clippyProjectRoot.value.foreach(c => result += s"-P:clippy:projectRoot=$c")
      if (clippyFatalWarnings.value.nonEmpty)
        result += s"-P:clippy:fatalWarnings=${clippyFatalWarnings.value.mkString("|")}"
      result.toList
    }
  )

  override def trigger = allRequirements
}


================================================
FILE: project/build.properties
================================================
sbt.version=0.13.17


================================================
FILE: project/plugins.sbt
================================================
resolvers += Resolver.typesafeRepo("releases")
// Workaround for the bug: https://github.com/sbt/sbt-assembly/issues/236
resolvers += "JBoss" at "https://repository.jboss.org"

addSbtPlugin("org.scalariform" % "sbt-scalariform" % "1.4.0")

addSbtPlugin("com.updateimpact" % "updateimpact-sbt-plugin" % "2.1.1")

addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.4.0")

addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.1")

addSbtPlugin("com.heroku" % "sbt-heroku" % "0.5.4")

addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "0.6.8")

// The Play plugin
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.4.6")

// web plugins

addSbtPlugin("com.typesafe.sbt" % "sbt-less"   % "1.1.0")
addSbtPlugin("com.typesafe.sbt" % "sbt-jshint" % "1.0.3")
addSbtPlugin("com.typesafe.sbt" % "sbt-digest" % "1.1.0")
addSbtPlugin("com.typesafe.sbt" % "sbt-mocha"  % "1.1.0")

// scalajs

addSbtPlugin("com.vmunier"  % "sbt-web-scalajs" % "1.0.3")
addSbtPlugin("org.scala-js" % "sbt-scalajs"     % "0.6.15")


================================================
FILE: tests/src/test/scala/org/softwaremill/clippy/CompileTests.scala
================================================
package org.softwaremill.clippy

import java.io.{File, FileOutputStream}
import java.util.zip.GZIPOutputStream
import scala.reflect.runtime.currentMirror
import com.softwaremill.clippy._
import org.scalatest.{BeforeAndAfterAll, FlatSpec, Matchers}
import scala.tools.reflect.ToolBox
import scala.tools.reflect.ToolBoxError

class CompileTests extends FlatSpec with Matchers with BeforeAndAfterAll {

  val localStoreDir = new File(System.getProperty("user.home"), ".clippy")
  val localStore    = new File(localStoreDir, "clippy.json.gz")
  val localStore2   = new File(localStoreDir, "clippy2.json.gz")

  /**
    * Writing test json data to where the plugin will expect to have it cached.
    */
  override protected def beforeAll() = {
    super.beforeAll()
    localStoreDir.mkdirs()
    if (localStore.exists()) {
      localStore.renameTo(localStore2)
    }

    val advices = List(
      Advice(
        TypeMismatchError(ExactT("slick.dbio.DBIOAction[*]"), None, ExactT("slick.lifted.Rep[Option[*]]"), None, None).asRegex,
        "Perhaps you forgot to call .result on your Rep[]? This will give you a DBIOAction that you can compose with other DBIOActions.",
        Library("com.typesafe.slick", "slick", "3.1.0")
      ),
      Advice(
        TypeMismatchError(
          ExactT("akka.http.scaladsl.server.StandardRoute"),
          None,
          ExactT(
            "akka.stream.scaladsl.Flow[akka.http.scaladsl.model.HttpRequest,akka.http.scaladsl.model.HttpResponse,Any]"
          ),
          None,
          None
        ).asRegex,
        "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",
        Library("com.typesafe.akka", "akka-http-experimental", "2.0.0")
      ),
      Advice(
        NotFoundError(ExactT("value wire")).asRegex,
        "you need to import com.softwaremill.macwire._",
        Library("com.softwaremill.macwire", "macros", "2.0.0")
      ),
      Advice(
        NotFoundError(ExactT("value wire")).asRegex,
        "If you need further help check out the macwire readme at https://github.com/adamw/macwire",
        Library("com.softwaremill.macwire", "macros", "2.0.0")
      ),
      Advice(
        TypeArgumentsDoNotConformToOverloadedBoundsError(
          ExactT("*"),
          ExactT("value apply"),
          Set(
            ExactT("[E <: slick.lifted.AbstractTable[_]]=> slick.lifted.TableQuery[E]"),
            ExactT("[E <: slick.lifted.AbstractTable[_]](cons: slick.lifted.Tag => E)slick.lifted.TableQuery[E]")
          )
        ).asRegex,
        "incorrect class name passed to TableQuery",
        Library("com.typesafe.slick", "slick", "3.1.1")
      ),
      Advice(
        TypeclassNotFoundError(
          ExactT("Ordering"),
          ExactT("java.time.LocalDate")
        ).asRegex,
        "implicit val localDateOrdering: Ordering[java.time.LocalDate] = Ordering.by(_.toEpochDay)",
        Library("java-lang", "time", "8+")
      )
    )

    import org.json4s.native.JsonMethods._
    val data = compact(render(Clippy("0.1", advices, Nil).toJson))

    val os = new GZIPOutputStream(new FileOutputStream(localStore))
    try os.write(data.getBytes("UTF-8"))
    finally os.close()
  }

  override protected def afterAll() = {
    localStore.delete()
    if (localStore2.exists()) {
      localStore2.renameTo(localStore)
    }

    super.afterAll()
  }

  val snippets = Map(
    "akka http" ->
      """
        |import akka.actor.ActorSystem
        |import akka.http.scaladsl.Http
        |
        |import akka.http.scaladsl.server.Directives._
        |
        |implicit val system = ActorSystem()
        |
        |val r = complete("ok")
        |
        |Http().bindAndHandle(r, "localhost", 8080)
      """.stripMargin,
    "macwire" ->
      """
        |class A()
        |val a = wire[A]
      """.stripMargin,
    "slick" ->
      """
        |case class User(id1: Long, id2: Long)
        |trait TestSchema {
        |
        |  val db: slick.jdbc.JdbcBackend#DatabaseDef
        |  val driver: slick.driver.JdbcProfile
        |
        |  import driver.api._
        |
        |  protected val users = TableQuery[User]
        |
        |  protected class Users(tag: Tag) extends Table[User](tag, "users") {
        |    def id1 = column[Long]("id")
        |    def id2 = column[Long]("id")
        |
        |    def * = (id1, id2) <> (User.tupled, User.unapply)
        |  }
        |}
      """.stripMargin,
    "Type mismatch pretty diff" ->
      """
        |class Test {
        |
        |  type Cool = (String, String, Int, Option[String], Long)
        |  type Bool = (String, String, Int, String, Long)
        |
        |  def test(cool: Cool): Bool = cool
        |
        |}
      """.stripMargin
  )

  val tb = {
    val cpp = sys.env("CLIPPY_PLUGIN_PATH")
    currentMirror.mkToolBox(
      options = s"-Xplugin:$cpp -Xplugin-require:clippy -P:clippy:colors=true -P:clippy:testmode=true"
    )
  }

  def tryCompile(snippet: String) = tb.compile(tb.parse(snippet))

  for ((name, s) <- snippets) {
    name should "compile with errors" in {
      (the[ToolBoxError] thrownBy tryCompile(s)).message should include("Clippy advises")
    }
  }

  "Clippy" should "return all matching advice" in {
    (the[ToolBoxError] thrownBy tryCompile(snippets("macwire"))).message should include(
      "Clippy advises you to try one of these"
    )
  }

}


================================================
FILE: ui/app/ClippyApplicationLoader.scala
================================================
import api.UiApiImpl
import com.softwaremill.id.DefaultIdGenerator
import controllers._
import dal.AdvicesRepository
import play.api.ApplicationLoader.Context
import play.api._
import play.api.i18n.I18nComponents
import play.api.mvc.EssentialFilter
import router.Routes
import util.{DatabaseConfig, SqlDatabase}
import util.email.{DummyEmailService, SendgridEmailService}

class ClippyApplicationLoader extends ApplicationLoader {
  def load(context: Context) = {
    Logger.configure(context.environment)
    val c = new ClippyComponents(context)
    c.database.updateSchema()
    c.application
  }
}

class ClippyComponents(context: Context) extends BuiltInComponentsFromContext(context) with I18nComponents {

  implicit val ec = scala.concurrent.ExecutionContext.Implicits.global

  lazy val router =
    new Routes(httpErrorHandler, applicationController, assets, webJarAssets, advicesController, autowireController)

  lazy val contactEmail = configuration.getString("email.contact").getOrElse("?")
  lazy val emailService = SendgridEmailService
    .createFromEnv(contactEmail)
    .getOrElse(new DummyEmailService)

  lazy val webJarAssets = new WebJarAssets(httpErrorHandler, configuration, environment)
  lazy val assets       = new controllers.Assets(httpErrorHandler)

  lazy val idGenerator = new DefaultIdGenerator()

  lazy val applicationController = new ApplicationController()

  lazy val database          = SqlDatabase.create(new DatabaseConfig { override val rootConfig = configuration.underlying })
  lazy val advicesRepository = new AdvicesRepository(database, idGenerator)

  lazy val uiApiImpl          = new UiApiImpl(advicesRepository, emailService, contactEmail)
  lazy val autowireController = new AutowireController(uiApiImpl)

  lazy val advicesController = new AdvicesController(advicesRepository)

  override lazy val httpFilters: Seq[EssentialFilter] = List(new HttpsFilter())
}


================================================
FILE: ui/app/api/UiApiImpl.scala
================================================
package api

import com.softwaremill.clippy._
import dal.AdvicesRepository
import util.email.EmailService

import scala.concurrent.{ExecutionContext, Future}

class UiApiImpl(
    advicesRepository: AdvicesRepository,
    emailService: EmailService,
    contactEmail: String
)(implicit ec: ExecutionContext)
    extends UiApi {

  override def sendCannotParse(errorText: String, contributorEmail: String) =
    emailService.send(
      contactEmail,
      "Clippy: unparseable message",
      s"""
         |Contributor email: $contributorEmail
         |
         |Error text:
         |$errorText
       """.stripMargin
    )

  override def sendAdviceProposal(ap: AdviceProposal): Future[Unit] =
    advicesRepository
      .store(
        ap.errorTextRaw,
        ap.patternRaw,
        ap.compilationError,
        ap.advice,
        AdviceState.Pending,
        ap.library,
        ap.contributor,
        ap.comment
      )
      .flatMap { a =>
        emailService.send(contactEmail, "Clippy: new advice proposal", s"""
             |Advice proposal:
             |$a
             |""".stripMargin)
      }

  override def listAccepted() =
    advicesRepository.findAll().map(_.map(_.toAdviceListing))

  override def sendSuggestEdit(text: String, contactEmail: String, adviceListing: AdviceListing) =
    emailService.send(
      contactEmail,
      "Clippy: edit suggestion",
      s"""
         |Edit suggestion for: $adviceListing
         |Contact email: $contactEmail
         |
         |Suggestion:
         |$text
       """.stripMargin
    )

  override def feedback(text: String, contactEmail: String) =
    emailService.send(contactEmail, "Clippy: feedback", s"""
         |Contact email: $contactEmail
         |
         |Feedback:
         |$text
       """.stripMargin)
}


================================================
FILE: ui/app/assets/stylesheets/main.less
================================================
#reactmain {
  padding-top: 60px;
}

html {
  position: relative;
  min-height: 100%;
}
.cout {
  width: 100%;
  padding-top: 5px;
  margin-bottom: 5px;
}
body {
  margin-bottom: 70px;
}
.footer {
  position: absolute;
  bottom: 0;
  width: 100%;
  height: 60px;
  background-color: #f5f5f5;
  margin-top: 10px;
}
.glyphicon-edit {
  cursor: pointer;
}
.advice-listing {
  table-layout: fixed;
  word-wrap: break-word;
}

================================================
FILE: ui/app/controllers/AdvicesController.scala
================================================
package controllers

import com.softwaremill.clippy.{Advice, Clippy}
import dal.AdvicesRepository
import play.api.mvc.{Action, Controller}
import util.{ClippyBuildInfo, Zip}

import scala.concurrent.{ExecutionContext, Future}

class AdvicesController(advicesRepository: AdvicesRepository)(implicit ec: ExecutionContext) extends Controller {
  def get = Action.async {
    gzippedAdvices.map(a => Ok(a).withHeaders("Content-Encoding" -> "gzip"))
  }

  def gzippedAdvices: Future[Array[Byte]] =
    advicesRepository.findAll().map { storedAdvices =>
      // TODO, once there's an admin: filter out not accepted advice
      val advices = storedAdvices
      //.filter(_.accepted)
        .map(_.toAdvice)
      Zip.compress(toJsonString(advices.toList))
    }

  private def toJsonString(advices: List[Advice]): String = {
    import org.json4s.native.JsonMethods.{render => r, compact}
    compact(r(Clippy(ClippyBuildInfo.version, advices, Nil).toJson))
  }
}


================================================
FILE: ui/app/controllers/ApplicationController.scala
================================================
package controllers

import play.api.mvc.{Action, Controller}

class ApplicationController extends Controller {

  def index = Action {
    Ok(views.html.index())
  }
}


================================================
FILE: ui/app/controllers/AutowireController.scala
================================================
package controllers

import com.softwaremill.clippy.UiApi
import play.api.mvc.{Action, Controller}

import upickle.default._
import upickle.Js

import scala.concurrent.ExecutionContext

class AutowireController(uiApi: UiApi)(implicit ec: ExecutionContext) extends Controller {

  def autowireApi(path: String) = Action.async { implicit request =>
    val b = request.body.asText.getOrElse("")

    AutowireServer
      .route[UiApi](uiApi)(
        autowire.Core.Request(
          path.split("/"),
          upickle.json.read(b).asInstanceOf[Js.Obj].value.toMap
        )
      )
      .map(jsv => Ok(upickle.json.write(jsv)))
  }
}

object AutowireServer extends autowire.Server[Js.Value, Reader, Writer] {
  def read[Result: Reader](p: Js.Value) = upickle.default.readJs[Result](p)
  def write[Result: Writer](r: Result)  = upickle.default.writeJs(r)
}


================================================
FILE: ui/app/controllers/HttpsFilter.scala
================================================
package controllers

import play.api.http.Status
import play.api.mvc.{Filter, RequestHeader, Result, Results}

import scala.concurrent.Future

class HttpsFilter extends Filter {
  def apply(nextFilter: (RequestHeader) => Future[Result])(requestHeader: RequestHeader): Future[Result] =
    requestHeader.headers.get("x-forwarded-proto") match {
      case Some(header) =>
        if (header == "https") {
          nextFilter(requestHeader)
        } else {
          Future.successful(
            Results.Redirect("https://" + requestHeader.host + requestHeader.uri, Status.MOVED_PERMANENTLY)
          )
        }
      case None => nextFilter(requestHeader)
    }
}


================================================
FILE: ui/app/dal/AdvicesRepository.scala
================================================
package dal

import com.softwaremill.clippy.AdviceState.AdviceState
import com.softwaremill.clippy._
import com.softwaremill.id.IdGenerator
import util.SqlDatabase

import scala.concurrent.{ExecutionContext, Future}

class AdvicesRepository(database: SqlDatabase, idGenerator: IdGenerator)(implicit ec: ExecutionContext) {
  import database._
  import database.driver.api._

  private class AdvicesTable(tag: Tag) extends Table[StoredAdvice](tag, "advices") {
    def id                 = column[Long]("id", O.PrimaryKey)
    def errorTextRaw       = column[String]("error_text_raw")
    def patternRaw         = column[String]("pattern_raw")
    def compilationError   = column[String]("compilation_error")
    def advice             = column[String]("advice")
    def state              = column[Int]("state")
    def libraryGroupId     = column[String]("library_group_id")
    def libraryArtifactId  = column[String]("library_artifact_id")
    def libraryVersion     = column[String]("library_version")
    def contributorEmail   = column[Option[String]]("contributor_email")
    def contributorTwitter = column[Option[String]]("contributor_twitter")
    def contributorGithub  = column[Option[String]]("contributor_github")
    def comment            = column[Option[String]]("comment")

    def * =
      (
        id,
        errorTextRaw,
        patternRaw,
        compilationError,
        advice,
        state,
        (libraryGroupId, libraryArtifactId, libraryVersion),
        (contributorEmail, contributorGithub, contributorTwitter),
        comment
      ).shaped <> ({ t =>
        StoredAdvice(
          t._1,
          t._2,
          t._3,
          CompilationError.fromJsonString(t._4).get,
          t._5,
          AdviceState(t._6),
          (Library.apply _).tupled(t._7),
          Contributor.tupled(t._8),
          t._9
        )
      }, { (a: StoredAdvice) =>
        Some(
          (
            a.id,
            a.errorTextRaw,
            a.patternRaw,
            a.compilationError.toJsonString,
            a.advice,
            a.state.id,
            Library.unapply(a.library).get,
            Contributor.unapply(a.contributor).get,
            a.comment
          )
        )
      })
  }

  private val advices = TableQuery[AdvicesTable]

  def store(
      errorTextRaw: String,
      patternRaw: String,
      compilationError: CompilationError[RegexT],
      advice: String,
      state: AdviceState,
      library: Library,
      contributor: Contributor,
      comment: Option[String]
  ): Future[StoredAdvice] = {

    val a = StoredAdvice(
      idGenerator.nextId(),
      errorTextRaw,
      patternRaw,
      compilationError,
      advice,
      state,
      library,
      contributor,
      comment
    )

    db.run(advices += a).map(_ => a)
  }

  def findAll(): Future[Seq[StoredAdvice]] =
    db.run(advices.result)
}

case class StoredAdvice(
    id: Long,
    errorTextRaw: String,
    patternRaw: String,
    compilationError: CompilationError[RegexT],
    advice: String,
    state: AdviceState,
    library: Library,
    contributor: Contributor,
    comment: Option[String]
) {

  def toAdvice = Advice(compilationError, advice, library)
  def toAdviceListing =
    AdviceListing(id, compilationError, advice, library, ContributorListing(contributor.github, contributor.twitter))
}


================================================
FILE: ui/app/util/ConfigWithDefault.scala
================================================
package util

import java.util.concurrent.TimeUnit

import com.typesafe.config.Config

trait ConfigWithDefault {

  def rootConfig: Config

  def getBoolean(path: String, default: Boolean) = ifHasPath(path, default) { _.getBoolean(path) }
  def getString(path: String, default: String)   = ifHasPath(path, default) { _.getString(path) }
  def getInt(path: String, default: Int)         = ifHasPath(path, default) { _.getInt(path) }
  def getConfig(path: String, default: Config)   = ifHasPath(path, default) { _.getConfig(path) }
  def getMilliseconds(path: String, default: Long) = ifHasPath(path, default) {
    _.getDuration(path, TimeUnit.MILLISECONDS)
  }
  def getOptionalString(path: String, default: Option[String] = None) = getOptional(path) { _.getString(path) }

  private def ifHasPath[T](path: String, default: T)(get: Config => T): T =
    if (rootConfig.hasPath(path)) get(rootConfig) else default

  private def getOptional[T](fullPath: String, default: Option[T] = None)(get: Config => T) =
    if (rootConfig.hasPath(fullPath)) {
      Some(get(rootConfig))
    } else {
      default
    }

}


================================================
FILE: ui/app/util/DatabaseConfig.scala
================================================
package util

import com.typesafe.config.Config

trait DatabaseConfig extends ConfigWithDefault {
  def rootConfig: Config

  import DatabaseConfig._

  lazy val dbH2Url              = getString(s"db.h2.properties.url", "jdbc:h2:file:./data")
  lazy val dbPostgresServerName = getString(PostgresServerNameKey, "")
  lazy val dbPostgresPort       = getString(PostgresPortKey, "5432")
  lazy val dbPostgresDbName     = getString(PostgresDbNameKey, "")
  lazy val dbPostgresUsername   = getString(PostgresUsernameKey, "")
  lazy val dbPostgresPassword   = getString(PostgresPasswordKey, "")
}

object DatabaseConfig {
  val PostgresDSClass       = "db.postgres.dataSourceClass"
  val PostgresServerNameKey = "db.postgres.properties.serverName"
  val PostgresPortKey       = "db.postgres.properties.portNumber"
  val PostgresDbNameKey     = "db.postgres.properties.databaseName"
  val PostgresUsernameKey   = "db.postgres.properties.user"
  val PostgresPasswordKey   = "db.postgres.properties.password"
}


================================================
FILE: ui/app/util/SqlDatabase.scala
================================================
package util

import java.net.URI

import com.typesafe.config.ConfigValueFactory._
import com.typesafe.config.{Config, ConfigFactory}
import com.typesafe.scalalogging.StrictLogging
import org.flywaydb.core.Flyway
import slick.driver.JdbcProfile
import slick.jdbc.JdbcBackend._

case class SqlDatabase(
    db: slick.jdbc.JdbcBackend#Database,
    driver: JdbcProfile,
    connectionString: JdbcConnectionString
) {
  def updateSchema() {
    val flyway = new Flyway()
    flyway.setDataSource(connectionString.url, connectionString.username, connectionString.password)
    flyway.migrate()
  }

  def close() {
    db.close()
  }
}

case class JdbcConnectionString(url: String, username: String = "", password: String = "")

object SqlDatabase extends StrictLogging {

  def create(config: DatabaseConfig): SqlDatabase = {
    val envDatabaseUrl = System.getenv("DATABASE_URL")

    if (config.dbPostgresServerName.length > 0)
      createPostgresFromConfig(config)
    else if (envDatabaseUrl != null)
      createPostgresFromEnv(envDatabaseUrl)
    else
      createEmbedded(config)
  }

  def createEmbedded(connectionString: String): SqlDatabase = {
    val db = Database.forURL(connectionString)
    SqlDatabase(db, slick.driver.H2Driver, JdbcConnectionString(connectionString))
  }

  private def createPostgresFromEnv(envDatabaseUrl: String) = {
    import DatabaseConfig._
    /*
      The DATABASE_URL is set by Heroku (if deploying there) and must be converted to a proper object
      of type Config (for Slick). Expected format:
      postgres://<username>:<password>@<host>:<port>/<dbname>
     */
    val dbUri    = new URI(envDatabaseUrl)
    val username = dbUri.getUserInfo.split(":")(0)
    val password = dbUri.getUserInfo.split(":")(1)
    val intermediaryConfig = new DatabaseConfig {
      override def rootConfig: Config =
        ConfigFactory
          .empty()
          .withValue(PostgresDSClass, fromAnyRef("org.postgresql.ds.PGSimpleDataSource"))
          .withValue(PostgresServerNameKey, fromAnyRef(dbUri.getHost))
          .withValue(PostgresPortKey, fromAnyRef(dbUri.getPort))
          .withValue(PostgresDbNameKey, fromAnyRef(dbUri.getPath.tail))
          .withValue(PostgresUsernameKey, fromAnyRef(username))
          .withValue(PostgresPasswordKey, fromAnyRef(password))
          .withFallback(ConfigFactory.load())
    }
    createPostgresFromConfig(intermediaryConfig)
  }

  private def postgresUrl(host: String, port: String, dbName: String) =
    s"jdbc:postgresql://$host:$port/$dbName"

  private def postgresConnectionString(config: DatabaseConfig) = {
    val host     = config.dbPostgresServerName
    val port     = config.dbPostgresPort
    val dbName   = config.dbPostgresDbName
    val username = config.dbPostgresUsername
    val password = config.dbPostgresPassword
    JdbcConnectionString(postgresUrl(host, port, dbName), username, password)
  }

  private def createPostgresFromConfig(config: DatabaseConfig) = {
    val db = Database.forConfig("db.postgres", config.rootConfig)
    SqlDatabase(db, slick.driver.PostgresDriver, postgresConnectionString(config))
  }

  private def createEmbedded(config: DatabaseConfig): SqlDatabase = {
    val db = Database.forConfig("db.h2")
    SqlDatabase(db, slick.driver.H2Driver, JdbcConnectionString(embeddedConnectionStringFromConfig(config)))
  }

  private def embeddedConnectionStringFromConfig(config: DatabaseConfig): String = {
    val url      = config.dbH2Url
    val fullPath = url.split(":")(3)
    logger.info(s"Using an embedded database, with data files located at: $fullPath")
    url
  }
}


================================================
FILE: ui/app/util/Zip.scala
================================================
package util

import java.io.{ByteArrayInputStream, ByteArrayOutputStream}
import java.util.zip.{GZIPInputStream, GZIPOutputStream}

object Zip {
  private val BufferSize = 512

  def compress(string: String): Array[Byte] = {
    val os  = new ByteArrayOutputStream(string.length() / 5)
    val gos = new GZIPOutputStream(os)
    gos.write(string.getBytes("UTF-8"))
    gos.close()
    os.close()
    os.toByteArray
  }

  def decompress(compressed: Array[Byte]): String = {
    val is        = new ByteArrayInputStream(compressed)
    val gis       = new GZIPInputStream(is, BufferSize)
    val string    = new StringBuilder()
    val data      = new Array[Byte](BufferSize)
    var bytesRead = gis.read(data)
    while (bytesRead != -1) {
      string.append(new String(data, 0, bytesRead, "UTF-8"))
      bytesRead = gis.read(data)
    }
    gis.close()
    is.close()
    string.toString()
  }
}


================================================
FILE: ui/app/util/email/DummyEmailService.scala
================================================
package util.email

import com.typesafe.scalalogging.StrictLogging

import scala.collection.mutable.ListBuffer
import scala.concurrent.Future

class DummyEmailService extends EmailService with StrictLogging {
  private val sentEmails: ListBuffer[(String, String, String)] = ListBuffer()

  logger.info("Using dummy email service")

  def reset() {
    sentEmails.clear()
  }

  override def send(to: String, subject: String, body: String) = {
    this.synchronized {
      sentEmails.+=((to, subject, body))
    }

    logger.info(s"Would send email to $to, with subject: $subject, body: $body")
    Future.successful(())
  }

  def wasEmailSent(to: String, subject: String): Boolean =
    sentEmails.exists(email => email._1.contains(to) && email._2 == subject)
}


================================================
FILE: ui/app/util/email/EmailService.scala
================================================
package util.email

import scala.concurrent.Future

trait EmailService {
  def send(to: String, subject: String, body: String): Future[Unit]
}


================================================
FILE: ui/app/util/email/SendgridEmailService.scala
================================================
package util.email

import com.sendgrid.SendGrid
import com.typesafe.scalalogging.StrictLogging

import scala.concurrent.Future
import scala.util.Properties

class SendgridEmailService(sendgridUsername: String, sendgridPassword: String, emailFrom: String)
    extends EmailService
    with StrictLogging {

  private lazy val sendgrid = new SendGrid(sendgridUsername, sendgridPassword)

  override def send(to: String, subject: String, body: String) = {
    val email = new SendGrid.Email()
    email.addTo(to)
    email.setFrom(emailFrom)
    email.setSubject(subject)
    email.setText(body)

    val response = sendgrid.send(email)
    if (response.getStatus) {
      logger.info(s"Email to $to sent")
    } else {
      logger.error(
        s"Email to $to, subject: $subject, body: $body, not sent: " +
          s"${response.getCode}/${response.getMessage}"
      )
    }

    Future.successful(())
  }
}

object SendgridEmailService extends StrictLogging {
  def createFromEnv(emailFrom: String): Option[SendgridEmailService] =
    for {
      u <- Properties.envOrNone("SENDGRID_USERNAME")
      p <- Properties.envOrNone("SENDGRID_PASSWORD")
    } yield {
      logger.info("Using SendGrid email service")
      new SendgridEmailService(u, p, emailFrom)
    }
}


================================================
FILE: ui/app/views/index.scala.html
================================================
<!DOCTYPE html>

<html lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Scala Clippy</title>

    <link rel='stylesheet' href='@routes.WebJarAssets.at(WebJarAssets.locate("css/bootstrap.min.css"))'>
    <link rel="stylesheet" href="@routes.Assets.versioned("stylesheets/main.css")"/>

    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
    <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
    <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->

    <link rel="shortcut icon" type="image/png" href="@routes.Assets.versioned("images/favicon.png")"/>

    <link rel="canonical" href="https://scala-clippy.org">
</head>
<body>

<!-- http://stackoverflow.com/questions/21324395/bootstrap-3-flush-footer-to-bottom-not-fixed -->
<div class="container">
    <div id="reactmain"></div>

    <div id="use" style="display: none">@use()</div>

    <script type='text/javascript' src='@routes.WebJarAssets.at(WebJarAssets.locate("jquery.min.js"))'></script>
    <script type='text/javascript' src='@routes.WebJarAssets.at(WebJarAssets.locate("bootstrap.min.js"))'></script>
    @playscalajs.html.scripts("uiclient")

    <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>
</div>
<footer class="footer">
    <div>&nbsp;</div>
    <div class="text-center">
        Copyright 2016. Created & maintained by
        <a href="http://softwaremill.com">SoftwareMill</a>.
    </div>
</footer>

<!-- twitter -->
<script>window.twttr = (function(d, s, id) {
  var js, fjs = d.getElementsByTagName(s)[0],
    t = window.twttr || {};
  if (d.getElementById(id)) return t;
  js = d.createElement(s);
  js.id = id;
  js.src = "https://platform.twitter.com/widgets.js";
  fjs.parentNode.insertBefore(js, fjs);

  t._e = [];
  t.ready = function(f) {
    t._e.push(f);
  };

  return t;
}(document, "script", "twitter-wjs"));</script>

<!-- google analytics -->
<script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

  ga('create', 'UA-11235106-14', 'auto');
  ga('send', 'pageview');

</script>

</body>
</html>


================================================
FILE: ui/app/views/use.scala.html
================================================
<h2>
    Scala Clippy
    <a class="twitter-share-button" href="https://twitter.com/intent/tweet?text=Scala%20Clippy%3A%20programmer-friendly%20compiler%20errors%20">Tweet</a>

    <a href="https://gitter.im/softwaremill/scala-clippy?utm_source=badge&amp;utm_medium=badge&amp;utm_campaign=pr-badge&amp;utm_content=badge">
        <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;" />
    </a>
</h2>

<p>
    Did you ever see a Scala compiler error such as:
</p>
<img class="cout" src="assets/img/clippy-akka-err.png">
<p>
    and had no idea what to do next? Well in this case, you need to provide an implicit instance of an <code>ActorMaterializer</code>,
    but the compiler isn't smart enough to be able to tell you that. Luckily, <strong>Scala Clippy is here to help</strong>!
</p>

<p>
    Just add the Scala Clippy compiler plugin, and you'll see this additional helpful message, with optional additional colors:
</p>
<img class="cout" src="assets/img/clippy-akka-err-rich.png">

<h2>Adding the plugin</h2>

<p>
    The easiest to use Clippy is via an SBT plugin. If you'd like Clippy to be enabled for all projects, without
    the need to modify each project's build, add the following to <code>~/.sbt/0.13/plugins/build.sbt</code>:
</p>

<pre>
addSbtPlugin("com.softwaremill.clippy" % "plugin-sbt" % "0.6.1")
</pre>

<p>
    Upon first use, the plugin will download the advice dataset from <code>https://scala-clippy.org</code> and store it in the
    <code>$HOME/.clippy</code> directory. The dataset will be updated at most once a day, in the background. You can customize the
    dataset URL and local store by setting the <code>clippyUrl</code> and <code>clippyLocalStoreDir</code> sbt options to non-<code>None</code>-values.
</p>

<p>
    Note: to customize a global sbt plugin (a plugin which is added via `~/.sbt/0.13/plugins/build.sbt`) keep in mind
    that:
</p>

<ul>
    <li>customize the plugin settings in <code>~/.sbt/0.13/build.sbt</code> (one directory up!). These settings will be
    automatically added to all of your projects.</li>
    <li>you'll need to add <code>import com.softwaremill.clippy.ClippySbtPlugin._</code> to access the setting names as auto-imports
    don't work in the global settings</li>
    <li>the clippy sbt settings are just a convenient syntax for adding compiler options (e.g., enabling colors is same
    as <code>scalacOptions += "-P:clippy:colors=true"</code>)</li>
</ul>

<h2>(NEW!) Turning selected compilation warnings into errors</h2>
<p>
    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,
    use the <code>clippyFatalWarnings</code> setting, for example:
</p>
<pre>
    import com.softwaremill.clippy.ClippySbtPlugin.WarningPatterns._
    // ...
    settings(
      clippyFatalWarnings ++= List(
        NonExhaustiveMatch,
        ".*\\[wartremover:.*\\].*[\\s\\S]*"
      )
    )
</pre>
<p>
    You can also define fatal warnings in your .clippy.json file (see further sections for examples).
</p>

<h2>Enabling syntax and type mismatch diffs highlighting</h2>

<p>
    Clippy can highlight:
</p>

<ul>
    <li>
        in type mismatch errors, the diff between expected and actual types. This may be especially helpful for long type
        signatures
    </li>
    <li>
        syntax when displaying code fragments with errors. Example:
        <img class="cout" src="assets/img/clippy-syntax-highlight.png">
    </li>
</ul>

<p>
    If you'd like to enable this feature in sbt globally, add the following to `~/.sbt/0.13/build.sbt`: (see also notes
    above)
</p>

<pre>
import com.softwaremill.clippy.ClippySbtPlugin._ // needed in global configuration only
clippyColorsEnabled := true
</pre>

<p>
    To customize the colors, set any of `clippyColorDiff`, `clippyColorComment`,
    `clippyColorType`, `clippyColorLiteral`, `clippyColorKeyword` to `Some(ClippyColor.[name])`, where `[name]` can be:
    `Black`, `Red`, `Green`, `Yellow`, `Blue`, `Magenta`, `Cyan`, `White` or `None`.
</p>

<p>
    You can of course add clippy on a per-project basis as well.
</p>
<h3>Windows users</h3>
<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>
<h2>Contributing advice</h2>

<p>
    Help others users by submitting an advice for a compilation error that you have encountered!
    Just click "contribute" above and paste in your error!
</p>

<p>
    Alternatively, create an issue on <a href="https://github.com/softwaremill/scala-clippy">GitHub</a> or chat with
    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.
</p>

<p>
    Speaking of GitHub, you are also welcome to check out (and improve!) the plugin's &amp; website's
    <a href="https://github.com/softwaremill/scala-clippy">source code</a>.
</p>

<h2>Project-specific advices and fatal warnings</h2>

<p>If you have advice that you feel is too specific to be worth sharing publicly, you can add
  it to your project specific advice file. First set your project root:
</p>
<pre>
clippyProjectRoot := Some((baseDirectory in ThisBuild).value) 
</pre>

Then create a file named .clippy.json in the root of your project directory and add the advice json in the format illustrated below:

<pre>
{
  "version": "1",
  "advices": [
    {
      "error": {
        "type": "typeMismatch",
        "found": "scala\\.concurrent\\.Future\\[Int\\]",
        "required": "Int"
      },
      "text": "Maybe you used map where you should have used flatMap?",
      "library": {
        "groupId": "scala.lang",
        "artifactId": "Future",
        "version": "1.0"
      }
    }
  ],
  "fatalWarnings": [
    {
      "pattern": "match may not be exhaustive[\\s\\S]*",
      "text": "Additional optional text to be displayed"
    }
  ]
}
</pre>
<h2>Library-specific advice</h2>
If you have advice that is specific to a library or library version you can also bundle the advice with your library.
If your users have Scala-Clippy installed they will see your advice if your library is inclued in their project.
This can be helpful in the common case where users of your library need specific imports to be able to use your functionality.
To bundle clippy advice with your library just put it in a file named clippy.json in your resources directory.

<h2>Alternative ways to use Clippy</h2>

<p>
    You can also use Clippy directly as a compiler plugin. If you use SBT, add the following setting to your
    project's <code>.sbt</code> file:
</p>

<pre>
addCompilerPlugin("com.softwaremill.clippy" %% "plugin" % "0.6.1" classifier "bundle")
</pre>

<p>
    If you are using <code>scalac</code> directly, add the following option:
</p>

<pre>
-Xplugin:clippy-plugin_2.11-0.6.1-bundle.jar
</pre>

<p>
    See clippy <a href="https://github.com/softwaremill/scala-clippy">README</a> for more details.</p>
</p>


================================================
FILE: ui/conf/application.conf
================================================
# This is the main configuration file for the application.
# ~~~~~

play.application.loader = "ClippyApplicationLoader"

# Secret key
# ~~~~~
# The secret key is used to secure cryptographics functions.
#
# This must be changed for production, but we recommend not changing it in this file.
#
# See http://www.playframework.com/documentation/latest/ApplicationSecret for more details.
play.crypto.secret = "changeme"
play.crypto.secret = ${?PLAY_CRYPTO_SECRET}

# The application languages
# ~~~~~
play.i18n.langs = [ "en" ]

# Router
# ~~~~~
# Define the Router object to use for this application.
# This router will be looked up first when the application is starting up,
# so make sure this is the entry point.
# Furthermore, it's assumed your route file is named properly.
# So for an application router like `my.application.Router`,
# you may need to define a router file `conf/my.application.routes`.
# Default to Routes in the root package (and conf/routes)
# play.http.router = my.application.Routes

db {
  h2 {
    dataSourceClass = "org.h2.jdbcx.JdbcDataSource"
    properties = {
      url = "jdbc:h2:file:./data/clippy"
    }
  }
  postgres {
    dataSourceClass = "org.postgresql.ds.PGSimpleDataSource"
    maxConnections = 16
    properties = {
      serverName = ""
      portNumber = "5432"
      databaseName = ""
      user = ""
      password = ""
    }
  }
}

email.contact="clippy@scala-clippy.org"

play.server.http.port=${?PORT}

================================================
FILE: ui/conf/db/migration/V1__create_schema.sql
================================================
create table "advices" (
  "id" bigint not null primary key,
  "error_text_raw" varchar not null,
  "compilation_error" varchar not null,
  "advice" varchar not null,
  "state" smallint not null,
  "library_group_id" varchar not null,
  "library_artifact_id" varchar not null,
  "library_version" varchar not null,
  "contributor_email" varchar,
  "contributor_twitter" varchar,
  "contributor_github" varchar,
  "comment" varchar
);


================================================
FILE: ui/conf/db/migration/V2__add_pattern.sql
================================================
alter table "advices" add column "pattern_raw" varchar not null default '';


================================================
FILE: ui/conf/logback.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>

<configuration>

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!-- encoders are assigned the type
             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n%rEx</pattern>
        </encoder>
    </appender>

    <logger name="application" level="DEBUG" additivity="false">
        <appender-ref ref="STDOUT" />
    </logger>

    <root level="INFO">
        <appender-ref ref="STDOUT" />
    </root>

</configuration>

================================================
FILE: ui/conf/routes
================================================
# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~

# Home page
GET     /                           controllers.ApplicationController.index

# Map static resources from the /public folder to the /assets URL path
GET     /assets/*file               controllers.Assets.versioned(path="/public", file: Asset)

# Webjars
GET     /webjars/*file              controllers.WebJarAssets.at(file)

GET     /api/advices                controllers.AdvicesController.get

# Autowire calls
POST    /ui-api/*path               controllers.AutowireController.autowireApi(path: String)

================================================
FILE: ui/test/dal/AdvicesRepositoryTest.scala
================================================
package dal

import com.softwaremill.clippy._
import com.softwaremill.id.DefaultIdGenerator
import util.BaseSqlSpec
import org.scalatest.concurrent.ScalaFutures
import org.scalatest.concurrent.IntegrationPatience

class AdvicesRepositoryTest extends BaseSqlSpec with ScalaFutures with IntegrationPatience {
  it should "store & read an advice" in {
    // given
    val ar = new AdvicesRepository(database, new DefaultIdGenerator())

    // when
    val stored = ar
      .store(
        "zzz",
        "yyy",
        TypeMismatchError[RegexT](RegexT("x"), None, RegexT("y"), None, None),
        "z",
        AdviceState.Pending,
        Library("g", "a", "1"),
        Contributor(None, None, Some("t")),
        Some("c")
      )
      .futureValue

    // then
    val r = ar.findAll().futureValue
    r should have size (1)

    val found = r.head

    stored should be(found)
    found.errorTextRaw should be("zzz")
    found.patternRaw should be("yyy")
    found.compilationError should be(TypeMismatchError(RegexT("x"), None, RegexT("y"), None, None))
    found.advice should be("z")
    found.state should be(AdviceState.Pending)
    found.library should be(Library("g", "a", "1"))
    found.contributor should be(Contributor(None, None, Some("t")))
    found.comment should be(Some("c"))
  }
}


================================================
FILE: ui/test/util/BaseSqlSpec.scala
================================================
package util

import org.scalatest.concurrent.ScalaFutures
import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach, FlatSpec, Matchers}

import scala.concurrent.ExecutionContext

trait BaseSqlSpec extends FlatSpec with Matchers with BeforeAndAfterAll with BeforeAndAfterEach with ScalaFutures {

  private val connectionString = "jdbc:h2:mem:clippy_test" + this.getClass.getSimpleName + ";DB_CLOSE_DELAY=-1"

  lazy val database = SqlDatabase.createEmbedded(connectionString)

  override protected def beforeAll() {
    super.beforeAll()
    createAll()
  }

  override protected def afterAll() {
    super.afterAll()
    dropAll()
    database.close()
  }

  private def dropAll() {
    import database.driver.api._
    database.db.run(sqlu"DROP ALL OBJECTS").futureValue
  }

  private def createAll() {
    database.updateSchema()
  }

  override protected def afterEach() {
    try {
      dropAll()
      createAll()
    } catch {
      case e: Exception => e.printStackTrace()
    }

    super.afterEach()
  }

  implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global
}


================================================
FILE: ui-client/src/main/scala/com/softwaremill/clippy/App.scala
================================================
package com.softwaremill.clippy

import japgolly.scalajs.react._
import autowire._
import japgolly.scalajs.react.vdom.prefix_<^._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.util.{Failure, Success}

object App {
  sealed trait Page
  case object UsePage                                                                       extends Page
  case object ContributeStep1InputError                                                     extends Page
  case class ContributeParseError(errorText: String)                                        extends Page
  case class ContributeStep2EditPattern(errorTextRaw: String, ce: CompilationError[ExactT]) extends Page
  case class ContributeStep3SubmitAdvice(
      errorTextRaw: String,
      patternText: String,
      ceFromPattern: CompilationError[ExactT]
  ) extends Page
  case object ListingPage  extends Page
  case object FeedbackPage extends Page

  case class State(page: Page, errorMsgs: List[String], infoMsgs: List[String])

  class Backend($ : BackendScope[Unit, State]) {
    private val handleReset: Callback = clearMsgs >> $.modState(_.copy(page = ContributeStep1InputError))

    private def handleErrorTextSubmitted(errorText: String): Callback =
      CompilationErrorParser.parse(errorText) match {
        case None     => clearMsgs >> $.modState(_.copy(page = ContributeParseError(errorText)))
        case Some(ce) => clearMsgs >> $.modState(_.copy(page = ContributeStep2EditPattern(errorText, ce)))
      }

    private def handleSendParseError(errorText: String)(email: String): Callback =
      handleFuture(
        AutowireClient[UiApi].sendCannotParse(errorText, email).call(),
        Some("Error submitted successfully! We'll get in touch soon."),
        Some((_: Unit) => $.modState(s => s.copy(page = ContributeStep1InputError)))
      )

    private def handlePatternSubmitted(
        errorTextRaw: String,
        patternText: String,
        ceFromPattern: CompilationError[ExactT]
    ): Callback =
      $.modState(s => s.copy(page = ContributeStep3SubmitAdvice(errorTextRaw, patternText, ceFromPattern)))

    private def handleSendAdviceProposal(ap: AdviceProposal): Callback =
      handleFuture(
        AutowireClient[UiApi].sendAdviceProposal(ap).call(),
        Some("Advice submitted successfully!"),
        Some((_: Unit) => $.modState(s => s.copy(page = ContributeStep1InputError)))
      )

    private lazy val handleFuture = new HandleFuture {
      override def apply[T](f: Future[T], successMsg: Option[String], successCallback: Option[(T) => Callback]) =
        CallbackTo(f onComplete {
          case Success(v) =>
            val msgCallback = successMsg map handleShowInfo
            val vCallback   = successCallback map (_(v))

            (msgCallback.getOrElse(Callback.empty) >> vCallback.getOrElse(Callback.empty)).runNow()

          case Failure(e) => handleShowError("Error communicating with the server").runNow()
        })
    }

    private def handleShowError(error: String): Callback =
      clearMsgs >> $.modState(s => s.copy(errorMsgs = error :: s.errorMsgs))

    private def handleShowInfo(info: String): Callback =
      clearMsgs >> $.modState(s => s.copy(infoMsgs = info :: s.infoMsgs))

    private def clearMsgs = $.modState(_.copy(errorMsgs = Nil, infoMsgs = Nil))

    private def handleSwitchPage(newPage: Page): Callback =
      clearMsgs >> $.modState { s =>
        def isContribute(p: Page) = p != UsePage && p != ListingPage && p != FeedbackPage
        if (s.page == newPage || (isContribute(s.page) && isContribute(newPage))) s else s.copy(page = newPage)
      }

    private def showMsgs(s: State) = <.span(
      s.infoMsgs.map(m => <.div(^.cls := "alert alert-success", ^.role := "alert")(m)) ++
        s.errorMsgs.map(m => <.div(^.cls := "alert alert-danger", ^.role := "alert")(m)): _*
    )

    private def showPage(s: State) = s.page match {
      case UsePage =>
        Use.component()

      case ContributeStep1InputError =>
        Contribute.Step1InputError.component(
          Contribute.Step1InputError.Props(handleErrorTextSubmitted, handleShowError)
        )

      case ContributeParseError(et) =>
        Contribute.ParseError.component(
          Contribute.ParseError.Props(handleReset, handleSendParseError(et), handleShowError)
        )

      case ContributeStep2EditPattern(errorTextRaw, ce) =>
        Contribute.Step2EditPattern.component(
          Contribute.Step2EditPattern.Props(errorTextRaw, ce, handleReset, handlePatternSubmitted, handleShowError)
        )

      case ContributeStep3SubmitAdvice(errorTextRaw, pattern, ceFromPattern) =>
        Contribute.Step3SubmitAdvice.component(
          Contribute.Step3SubmitAdvice
            .Props(errorTextRaw, pattern, ceFromPattern, handleReset, handleSendAdviceProposal, handleShowError)
        )

      case ListingPage =>
        Listing.component(Listing.Props(handleShowError, clearMsgs, handleFuture))

      case FeedbackPage =>
        Feedback.component(Feedback.Props(handleShowError, clearMsgs, handleFuture))
    }

    def render(s: State) = <.span(
      Menu.component((s.page, handleSwitchPage)),
      <.div(^.cls := "container")(
        showMsgs(s),
        showPage(s)
      )
    )
  }
}

trait HandleFuture {
  def apply[T](f: Future[T], successMsg: Option[String], successCallback: Option[T => Callback]): Callback
}


================================================
FILE: ui-client/src/main/scala/com/softwaremill/clippy/AutowireClient.scala
================================================
package com.softwaremill.clippy

import org.scalajs.dom
import scala.concurrent.Future
import scalajs.concurrent.JSExecutionContext.Implicits.queue
import upickle.default._
import upickle.Js

object AutowireClient extends autowire.Client[Js.Value, Reader, Writer] {
  override def doCall(req: Request): Future[Js.Value] =
    dom.ext.Ajax
      .post(
        url = "/ui-api/" + req.path.mkString("/"),
        data = upickle.json.write(Js.Obj(req.args.toSeq: _*))
      )
      .map(_.responseText)
      .map(upickle.json.read)

  def read[Result: Reader](p: Js.Value) = readJs[Result](p)
  def write[Result: Writer](r: Result)  = writeJs(r)
}


================================================
FILE: ui-client/src/main/scala/com/softwaremill/clippy/BsUtils.scala
================================================
package com.softwaremill.clippy

import japgolly.scalajs.react._
import japgolly.scalajs.react.extra.ExternalVar
import japgolly.scalajs.react.vdom.prefix_<^._

object BsUtils {
  def bsPanel(body: TagMod*) = <.div(^.cls := "panel panel-default") {
    <.div(^.cls := "panel-body")(body)
  }

  // Maybe this could be a component? But a function works for now
  def bsFormEl(ev: ExternalVar[FormField])(body: Seq[TagMod] => ReactTag) = {
    val formField = ev.value
    val elId      = Utils.randomString(8)
    <.div(^.cls := "form-group", formField.error ?= (^.cls := "has-error"))(
      <.label(^.htmlFor := elId, if (formField.required) <.strong(formField.label) else formField.label),
      body(
        Seq(
          ^.id := elId,
          formField.required ?= (^.required := "required"),
          ^.value := formField.v,
          ^.onChange ==> ((e: ReactEventI) => ev.set(formField.copy(v = e.target.value)))
        )
      )
    )
  }
}


================================================
FILE: ui-client/src/main/scala/com/softwaremill/clippy/Contribute.scala
================================================
package com.softwaremill.clippy

import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.prefix_<^._
import BsUtils._
import Utils._
import monocle.macros.Lenses

object Contribute {
  object Step1InputError {
    case class Props(next: String => Callback, showError: String => Callback)

    @Lenses
    case class State(errorText: FormField)
    implicit val stateVal = new Validatable[State] {
      override def validated(s: State) = s.copy(errorText = s.errorText.validated)
      override def fields(s: State)    = List(s.errorText)
    }

    class Backend($ : BackendScope[Props, State]) {
      def render(s: State, p: Props) = <.div(
        bsPanel(
          <.p(
            "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!"
          ),
          <.p("First, paste in the error and we'll see if we can parse it. Only the error message is needed, e.g.:"),
          <.pre(
            """[error] type mismatch;
              |[error] found   : akka.http.scaladsl.server.StandardRoute
              |[error] required: akka.stream.scaladsl.Flow[akka.http.scaladsl.model.HttpRequest,akka.http.scaladsl.model.HttpResponse,Any]""".stripMargin
          ),
          <.p("You will be able to edit the pattern that will be used when matching errors later.")
        ),
        <.form(
          ^.onSubmit ==> FormField.submitValidated($, p.showError)(s => p.next(s.errorText.v)),
          bsFormEl(externalVar($, s, State.errorText))(mods => <.textarea(^.cls := "form-control", ^.rows := 5)(mods)),
          <.button(^.`type` := "submit", ^.cls := "btn btn-primary")("Next")
        )
      )
    }

    val component = ReactComponentB[Props]("Step1InputError")
      .initialState(State(FormField("Error text", required = true)))
      .renderBackend[Backend]
      .build
  }

  object Step2EditPattern {
    case class Props(
        errorTextRaw: String,
        ce: CompilationError[ExactT],
        reset: Callback,
        next: (String, String, CompilationError[ExactT]) => Callback,
        showError: String => Callback
    )

    @Lenses
    case class State(patternText: FormField)
    implicit val stateVal = new Validatable[State] {
      override def validated(s: State) = s.copy(patternText = s.patternText.validated)
      override def fields(s: State)    = List(s.patternText)
    }

    class Backend($ : BackendScope[Props, State]) {
      def render(s: State, p: Props) = {
        val parsedPattern       = CompilationErrorParser.parse(s.patternText.v)
        val errorMatchesPattern = parsedPattern.map(_.asRegex).map(_.matches(p.ce))

        val alert = errorMatchesPattern match {
          case None =>
            <.div(^.cls := "alert alert-danger", ^.role := "alert")("Cannot parse the error")
          case Some(false) =>
            <.div(^.cls := "alert alert-danger", ^.role := "alert")("The pattern doesn't match the original error")
          case Some(true) =>
            <.div(^.cls := "alert alert-success", ^.role := "alert")("The pattern matches the original error")
        }

        val canProceed = errorMatchesPattern.getOrElse(false)

        val nextCallback = parsedPattern match {
          case None                => Callback.empty
          case Some(ceFromPattern) => p.next(p.errorTextRaw, s.patternText.v, ceFromPattern)
        }

        <.div(
          bsPanel(
            <.p("Parsing successfull! Here's what we've found:"),
            <.pre(p.ce.toString),
            <.p(
              "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 *:"
            ),
            <.pre("cats.Monad[List] -> cats.Monad[*]"),
            <.p("Or, you can just click next and leave the submitted error to be an exact pattern.")
          ),
          alert,
          <.form(
            ^.onSubmit ==> FormField.submitValidated($, p.showError)(_ => nextCallback),
            bsFormEl(externalVar($, s, State.patternText))(
              mods => <.textarea(^.cls := "form-control", ^.rows := 5)(mods)
            ),
            <.button(^.`type` := "reset", ^.cls := "btn btn-default", ^.onClick --> p.reset)("Reset"),
            <.span(" "),
            <.button(^.`type` := "submit", ^.cls := "btn btn-primary", ^.disabled := !canProceed)("Next")
          )
        )
      }
    }

    val component = ReactComponentB[Props]("Step2EditPattern")
      .initialState_P { p =>
        State(FormField("Pattern", required = true, p.errorTextRaw, error = false))
      }
      .renderBackend[Backend]
      .build
  }

  object Step3SubmitAdvice {
    case class Props(
        errorTextRaw: String,
        patternRaw: String,
        ceFromPattern: CompilationError[ExactT],
        reset: Callback,
        send: AdviceProposal => Callback,
        showError: String => Callback
    )

    @Lenses
    case class State(
        advice: FormField,
        libraryGroupId: FormField,
        libraryArtifactId: FormField,
        libraryVersion: FormField,
        email: FormField,
        twitter: FormField,
        github: FormField,
        comment: FormField
    )

    implicit val stateVal = new Validatable[State] {
      override def validated(s: State) = s.copy(
        advice = s.advice.validated,
        libraryGroupId = s.libraryGroupId.validated,
        libraryArtifactId = s.libraryArtifactId.validated,
        libraryVersion = s.libraryVersion.validated,
        email = s.email.validated,
        twitter = s.twitter.validated,
        github = s.github.validated,
        comment = s.comment.validated
      )
      override def fields(s: State) =
        List(
          s.advice,
          s.libraryGroupId,
          s.libraryArtifactId,
          s.libraryVersion,
          s.email,
          s.twitter,
          s.github,
          s.comment
        )
    }

    class Backend($ : BackendScope[Props, State]) {
      def render(s: State, p: Props) = <.div(
        bsPanel(
          <.p("You can now define the advice associated with the error. Here's the pattern you have entered:"),
          <.pre(p.ceFromPattern.toString),
          <.p(
            "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."
          )
        ),
        <.form(
          ^.onSubmit ==> FormField.submitValidated($, p.showError)(
            s =>
              p.send(
                AdviceProposal(
                  p.errorTextRaw,
                  p.patternRaw,
                  p.ceFromPattern.asRegex,
                  s.advice.v,
                  Library(s.libraryGroupId.v, s.libraryArtifactId.v, s.libraryVersion.v),
                  Contributor(s.email.vOpt, s.twitter.vOpt, s.github.vOpt),
                  s.comment.vOpt
                )
            )
          ),
          bsFormEl(externalVar($, s, State.advice))(mods => <.textarea(^.cls := "form-control", ^.rows := 3)(mods)),
          <.hr,
          bsFormEl(externalVar($, s, State.libraryGroupId))(
            mods => <.input(^.`type` := "text", ^.cls := "form-control", ^.placeholder := "org.scala")(mods)
          ),
          bsFormEl(externalVar($, s, State.libraryArtifactId))(
            mods => <.input(^.`type` := "text", ^.cls := "form-control", ^.placeholder := "scala-lang")(mods)
          ),
          bsFormEl(externalVar($, s, State.libraryVersion))(
            mods => <.input(^.`type` := "text", ^.cls := "form-control", ^.placeholder := "2.11-M3")(mods)
          ),
          <.hr,
          bsFormEl(externalVar($, s, State.email))(
            mods =>
              <.input(^.`type` := "email", ^.cls := "form-control", ^.placeholder := "scalacoder@company.com")(mods)
          ),
          bsFormEl(externalVar($, s, State.twitter))(
            mods =>
              <.div(^.cls := "input-group")(
                <.div(^.cls := "input-group-addon")("@"),
                <.input(^.`type` := "text", ^.cls := "form-control", ^.placeholder := "twitter")(mods)
            )
          ),
          bsFormEl(externalVar($, s, State.github))(
            mods =>
              <.div(^.cls := "input-group")(
                <.div(^.cls := "input-group-addon")("@"),
                <.input(^.`type` := "text", ^.cls := "form-control", ^.placeholder := "github")(mods)
            )
          ),
          <.hr,
          bsFormEl(externalVar($, s, State.comment))(mods => <.textarea(^.cls := "form-control", ^.rows := 3)(mods)),
          <.button(^.`type` := "reset", ^.cls := "btn btn-default", ^.onClick --> p.reset)("Reset"),
          <.span(" "),
          <.button(^.`type` := "submit", ^.cls := "btn btn-primary")("Send")
        )
      )
    }

    val component = ReactComponentB[Props]("Step3SubmitAdvice")
      .initialState(
        State(
          FormField("Advice", required = true),
          FormField("Library group id", required = true),
          FormField("Library artifact id", required = true),
          FormField("Library version", required = true),
          FormField("E-mail (optional)", required = false),
          FormField("Twitter handle (optional)", required = false),
          FormField("Github handle (optional)", required = false),
          FormField("Comment (optional)", required = false)
        )
      )
      .renderBackend[Backend]
      .build
  }

  object ParseError {
    case class Props(reset: Callback, send: String => Callback, showError: String => Callback)

    @Lenses
    case class State(email: FormField)
    implicit val stateVal = new Validatable[State] {
      override def validated(s: State) = s.copy(email = s.email.validated)
      override def fields(s: State)    = List(s.email)
    }

    class Backend($ : BackendScope[Props, State]) {
      def render(s: State, p: Props) = <.div(
        bsPanel(
          <.p(
            "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."
          )
        ),
        <.form(
          ^.onSubmit ==> FormField.submitValidated($, p.showError)(s => p.send(s.email.v)),
          bsFormEl(externalVar($, s, State.email))(
            mods =>
              <.input(^.`type` := "email", ^.cls := "form-control", ^.placeholder := "scalacoder@company.com")(mods)
          ),
          <.button(^.`type` := "reset", ^.cls := "btn btn-default", ^.onClick --> p.reset)("Reset"),
          <.span(" "),
          <.button(^.`type` := "submit", ^.cls := "btn btn-primary")("Send")
        )
      )
    }

    val component = ReactComponentB[Props]("ParseError")
      .initialState(State(FormField("Email", required = true)))
      .renderBackend[Backend]
      .build
  }
}


================================================
FILE: ui-client/src/main/scala/com/softwaremill/clippy/Feedback.scala
================================================
package com.softwaremill.clippy

import autowire._
import com.softwaremill.clippy.BsUtils._
import com.softwaremill.clippy.Utils._
import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.prefix_<^._
import monocle.macros.Lenses
import scala.concurrent.ExecutionContext.Implicits.global

object Feedback {
  case class Props(showError: String => Callback, clearMsgs: Callback, handleFuture: HandleFuture)

  @Lenses
  case class State(contact: FormField, feedback: FormField)
  implicit val stateVal = new Validatable[State] {
    override def validated(s: State) = s.copy(
      contact = s.contact.validated,
      feedback = s.feedback.validated
    )
    override def fields(s: State) = List(s.contact, s.feedback)
  }

  class Backend($ : BackendScope[Props, State]) {
    def render(s: State, p: Props) = {
      def sendFeedbackCallback() =
        p.handleFuture(
          AutowireClient[UiApi].feedback(s.feedback.v, s.contact.v).call(),
          Some("Feedback sent, thank you!"),
          Some(
            (_: Unit) => $.modState(s => s.copy(contact = s.contact.copy(v = ""), feedback = s.feedback.copy(v = "")))
          )
        )

      <.form(
        ^.onSubmit ==> FormField.submitValidated($, p.showError)(s => sendFeedbackCallback()),
        bsFormEl(externalVar($, s, State.contact))(
          mods =>
            <.input(^.`type` := "email", ^.cls := "form-control", ^.placeholder := "scalacoder@company.com")(mods)
        ),
        bsFormEl(externalVar($, s, State.feedback))(mods => <.textarea(^.cls := "form-control", ^.rows := "3")(mods)),
        <.button(^.`type` := "send", ^.cls := "btn btn-primary")("Send")
      )
    }
  }

  val component = ReactComponentB[Props]("Use")
    .initialState(
      State(
        FormField("Contact email", required = true),
        FormField("Feedback", required = true)
      )
    )
    .renderBackend[Backend]
    .build
}


================================================
FILE: ui-client/src/main/scala/com/softwaremill/clippy/FormField.scala
================================================
package com.softwaremill.clippy

import japgolly.scalajs.react._

case class FormField(label: String, required: Boolean, v: String, error: Boolean) {
  def validated =
    if (required) {
      if (v.isEmpty) copy(error = true) else copy(error = false)
    } else this
  def vOpt: Option[String] = if (v.isEmpty) None else Some(v)
}
object FormField {
  def apply(label: String, required: Boolean): FormField = FormField(label, required, "", error = false)
  def errorMsgIfAny(fields: Seq[FormField]): Option[String] =
    fields.find(_.error).map(ff => s"${ff.label} is required") // required is the only type of error there could be

  def submitValidated[P, S: Validatable](
      $ : BackendScope[P, S],
      showError: String => Callback
  )(submit: S => Callback)(e: ReactEventI): Callback =
    for {
      _     <- e.preventDefaultCB
      props <- $.props
      s     <- $.state
      v  = implicitly[Validatable[S]]
      s2 = v.validated(s)
      _ <- $.setState(s2)
      fields = v.fields(s2)
      em     = errorMsgIfAny(fields)
      _ <- em.fold(submit(s2))(showError)
    } yield ()
}

trait Validatable[S] {
  def validated(s: S): S
  def fields(s: S): Seq[FormField]
}


================================================
FILE: ui-client/src/main/scala/com/softwaremill/clippy/Listing.scala
================================================
package com.softwaremill.clippy

import com.softwaremill.clippy.BsUtils._
import com.softwaremill.clippy.Utils._
import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.prefix_<^._
import autowire._
import monocle.macros.Lenses
import scala.concurrent.ExecutionContext.Implicits.global

object Listing {
  case class Props(showError: String => Callback, clearMsgs: Callback, handleFuture: HandleFuture)

  @Lenses
  case class State(
      advices: Seq[AdviceListing],
      suggestEditId: Option[Long],
      suggestContact: FormField,
      suggestText: FormField
  )
  implicit val stateVal = new Validatable[State] {
    override def validated(s: State) = s.copy(
      suggestContact = s.suggestContact.validated,
      suggestText = s.suggestText.validated
    )
    override def fields(s: State) = List(s.suggestContact, s.suggestText)
  }

  class Backend($ : BackendScope[Props, State]) {
    def render(s: State, p: Props) = {
      def suggestEditCallback(a: AdviceListing) =
        p.clearMsgs >> $.modState(s => s.copy(suggestEditId = Some(a.id), suggestText = s.suggestText.copy(v = "")))
      def cancelSuggestEditCallback(a: AdviceListing) = $.modState(_.copy(suggestEditId = None))
      def sendSuggestEditCallback(a: AdviceListing) =
        cancelSuggestEditCallback(a) >> p.handleFuture(
          AutowireClient[UiApi].sendSuggestEdit(s.suggestText.v, s.suggestContact.v, a).call(),
          Some("Suggestion sent, thank you!"),
          None
        )

      def rowForAdvice(a: AdviceListing) = <.tr(
        <.td(<.pre(a.compilationError.toString)),
        <.td(a.advice),
        <.td(a.library.toString),
        <.td(<.span(^.cls := "glyphicon glyphicon-edit", ^.onClick --> suggestEditCallback(a)))
      )

      def suggestEdit(a: AdviceListing) = <.tr(
        <.td(^.colSpan := 4)(
          <.form(
            ^.onSubmit ==> FormField.submitValidated($, p.showError)(s => sendSuggestEditCallback(a)),
            bsFormEl(externalVar($, s, State.suggestContact))(
              mods =>
                <.input(^.`type` := "email", ^.cls := "form-control", ^.placeholder := "scalacoder@company.com")(mods)
            ),
            bsFormEl(externalVar($, s, State.suggestText))(
              mods => <.textarea(^.cls := "form-control", ^.rows := "3")(mods)
            ),
            <.button(^.`type` := "reset", ^.cls := "btn btn-default", ^.onClick --> cancelSuggestEditCallback(a))(
              "Cancel"
            ),
            <.span(" "),
            <.button(^.`type` := "send", ^.cls := "btn btn-primary")("Send")
          )
        )
      )

      <.table(^.cls := "table table-striped advice-listing")(
        <.thead(
          <.tr(
            <.th("Compilation error (as regex)"),
            <.th(^.width := "25%")("Advice"),
            <.th(^.width := "20%")("Library"),
            <.th(^.width := "8%")("Suggest edit")
          )
        ),
        <.tbody(
          s.advices.flatMap(
            a => List(rowForAdvice(a)) ++ s.suggestEditId.filter(_ == a.id).map(_ => suggestEdit(a)).toList
          ): _*
        )
      )
    }

    def initAdvices(p: Props): Callback =
      p.handleFuture(
        AutowireClient[UiApi].listAccepted().call(),
        None,
        Some((s: Seq[AdviceListing]) => $.modState(_.copy(advices = s)))
      )
  }

  val component = ReactComponentB[Props]("Use")
    .initialState(
      State(
        Nil,
        None,
        FormField("Contact email (optional)", required = false),
        FormField("Suggestion", required = true)
      )
    )
    .renderBackend[Backend]
    .componentDidMount(ctx => ctx.backend.initAdvices(ctx.props))
    .build
}


================================================
FILE: ui-client/src/main/scala/com/softwaremill/clippy/Main.scala
================================================
package com.softwaremill.clippy

import japgolly.scalajs.react._
import org.scalajs.jquery._
import scala.scalajs.js
import japgolly.scalajs.react.vdom.prefix_<^._

object Main extends js.JSApp {
  type HtmlId = String

  def main(): Unit =
    jQuery(setupUI _)

  def setupUI(): Unit = {
    val mountNode = org.scalajs.dom.document.getElementById("reactmain")

    val app = ReactComponentB[Unit]("App")
      .initialState(App.State(App.UsePage, Nil, Nil))
      .renderBackend[App.Backend]
      .build

    ReactDOM.render(app(), mountNode)
  }
}


================================================
FILE: ui-client/src/main/scala/com/softwaremill/clippy/Menu.scala
================================================
package com.softwaremill.clippy

import com.softwaremill.clippy.App._
import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.prefix_<^._

object Menu {
  val component = ReactComponentB[(Page, Page => Callback)]("Menu").render { $ =>
    val isUsePage                         = $.props._1 == UsePage
    val isListingPage                     = $.props._1 == ListingPage
    val isFeedbackPage                    = $.props._1 == FeedbackPage
    val isContributePage                  = !isUsePage && !isListingPage && !isFeedbackPage
    def switchTo(p: Page)(e: ReactEventI) = e.preventDefaultCB >> $.props._2(p)

    <.nav(^.cls := "navbar navbar-inverse navbar-fixed-top")(
      <.div(^.cls := "container")(
        <.div(^.cls := "navbar-header")(
          <.button(
            ^.`type` := "button",
            ^.cls := "navbar-toggle collapsed",
            "data-toggle".reactAttr := "collapse",
            "data-target".reactAttr := "navbar",
            "aria-expanded".reactAttr := "false",
            "aria-controls".reactAttr := "navbar"
          )(
            <.span(^.cls := "sr-only")("Toggle navigation"),
            <.span(^.cls := "icon-bar"),
            <.span(^.cls := "icon-bar"),
            <.span(^.cls := "icon-bar")
          ),
          <.a(^.cls := "navbar-brand", ^.onClick ==> switchTo(UsePage), ^.href := "#")("Scala Clippy")
        ),
        <.div(^.id := "navbar", ^.cls := "collapse navbar-collapse")(
          <.ul(^.cls := "nav navbar-nav")(
            <.li(isUsePage ?= (^.cls := "active"))(
              <.a("Use", ^.onClick ==> switchTo(UsePage), ^.href := "#")
            ),
            <.li(isContributePage ?= (^.cls := "active"))(
              <.a("Contribute", ^.onClick ==> switchTo(ContributeStep1InputError), ^.href := "#")
            ),
            <.li(isListingPage ?= (^.cls := "active"))(
              <.a("Browse", ^.onClick ==> switchTo(ListingPage), ^.href := "#")
            ),
            <.li(isFeedbackPage ?= (^.cls := "active"))(
              <.a("Send feedback", ^.onClick ==> switchTo(FeedbackPage), ^.href := "#")
            )
          )
        )
      )
    )
  }.build
}


================================================
FILE: ui-client/src/main/scala/com/softwaremill/clippy/Use.scala
================================================
package com.softwaremill.clippy

import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.prefix_<^._

object Use {
  val component = ReactComponentB[Unit]("Use").render { $ =>
    val html = org.scalajs.dom.document.getElementById("use").innerHTML
    <.span(^.dangerouslySetInnerHtml(html))
  }.build
}


================================================
FILE: ui-client/src/main/scala/com/softwaremill/clippy/Utils.scala
================================================
package com.softwaremill.clippy

import japgolly.scalajs.react.BackendScope
import japgolly.scalajs.react.extra.ExternalVar
import monocle._

import scala.util.Random

object Utils {
  def randomString(length: Int) = Random.alphanumeric take length mkString ""

  // ExternalVar companion has only methods for creating a var from AccessRD (read direct), here we are reading
  // through callbacks, so we need that extra method
  def externalVar[S, A]($ : BackendScope[_, 
Download .txt
gitextract_gwq_0d06/

├── .gitignore
├── .scalafmt.conf
├── .travis.yml
├── LICENSE.txt
├── README.md
├── build.sbt
├── model/
│   └── src/
│       ├── main/
│       │   └── scala/
│       │       └── com/
│       │           └── softwaremill/
│       │               └── clippy/
│       │                   ├── Advice.scala
│       │                   ├── Clippy.scala
│       │                   ├── CompilationError.scala
│       │                   ├── CompilationErrorParser.scala
│       │                   ├── Library.scala
│       │                   ├── StringDiff.scala
│       │                   ├── Template.scala
│       │                   └── Warning.scala
│       └── test/
│           └── scala/
│               └── com/
│                   └── softwaremill/
│                       └── clippy/
│                           ├── CompilationErrorParserTest.scala
│                           ├── CompilationErrorTest.scala
│                           ├── LibraryProperties.scala
│                           ├── RegexTTest.scala
│                           ├── StringDiffSpecification.scala
│                           ├── StringDiffTest.scala
│                           └── TypeNamesGenerators.scala
├── package.json
├── plugin/
│   └── src/
│       └── main/
│           ├── resources/
│           │   └── scalac-plugin.xml
│           ├── scala/
│           │   └── com/
│           │       └── softwaremill/
│           │           └── clippy/
│           │               ├── AdviceLoader.scala
│           │               ├── ClippyPlugin.scala
│           │               ├── ColorsConfig.scala
│           │               ├── FailOnWarningsReporter.scala
│           │               ├── Highlighter.scala
│           │               ├── InjectReporter.scala
│           │               ├── RestoreReporter.scala
│           │               └── Utils.scala
│           ├── scala-2.11/
│           │   └── com/
│           │       └── softwaremill/
│           │           └── clippy/
│           │               ├── DelegatingPosition.scala
│           │               └── DelegatingReporter.scala
│           └── scala-2.12/
│               └── com/
│                   └── softwaremill/
│                       └── clippy/
│                           ├── DelegatingPosition.scala
│                           └── DelegatingReporter.scala
├── plugin-sbt/
│   └── src/
│       └── main/
│           └── scala/
│               └── com/
│                   └── softwaremill/
│                       └── clippy/
│                           └── ClippySbtPlugin.scala
├── project/
│   ├── build.properties
│   └── plugins.sbt
├── tests/
│   └── src/
│       └── test/
│           └── scala/
│               └── org/
│                   └── softwaremill/
│                       └── clippy/
│                           └── CompileTests.scala
├── ui/
│   ├── app/
│   │   ├── ClippyApplicationLoader.scala
│   │   ├── api/
│   │   │   └── UiApiImpl.scala
│   │   ├── assets/
│   │   │   └── stylesheets/
│   │   │       └── main.less
│   │   ├── controllers/
│   │   │   ├── AdvicesController.scala
│   │   │   ├── ApplicationController.scala
│   │   │   ├── AutowireController.scala
│   │   │   └── HttpsFilter.scala
│   │   ├── dal/
│   │   │   └── AdvicesRepository.scala
│   │   ├── util/
│   │   │   ├── ConfigWithDefault.scala
│   │   │   ├── DatabaseConfig.scala
│   │   │   ├── SqlDatabase.scala
│   │   │   ├── Zip.scala
│   │   │   └── email/
│   │   │       ├── DummyEmailService.scala
│   │   │       ├── EmailService.scala
│   │   │       └── SendgridEmailService.scala
│   │   └── views/
│   │       ├── index.scala.html
│   │       └── use.scala.html
│   ├── conf/
│   │   ├── application.conf
│   │   ├── db/
│   │   │   └── migration/
│   │   │       ├── V1__create_schema.sql
│   │   │       └── V2__add_pattern.sql
│   │   ├── logback.xml
│   │   └── routes
│   └── test/
│       ├── dal/
│       │   └── AdvicesRepositoryTest.scala
│       └── util/
│           └── BaseSqlSpec.scala
├── ui-client/
│   └── src/
│       └── main/
│           └── scala/
│               └── com/
│                   └── softwaremill/
│                       └── clippy/
│                           ├── App.scala
│                           ├── AutowireClient.scala
│                           ├── BsUtils.scala
│                           ├── Contribute.scala
│                           ├── Feedback.scala
│                           ├── FormField.scala
│                           ├── Listing.scala
│                           ├── Main.scala
│                           ├── Menu.scala
│                           ├── Use.scala
│                           └── Utils.scala
└── ui-shared/
    └── src/
        └── main/
            └── scala/
                └── com/
                    └── softwaremill/
                        └── clippy/
                            ├── AdviceState.scala
                            ├── Contributor.scala
                            └── UiApi.scala
Download .txt
SYMBOL INDEX (1 symbols across 1 files)

FILE: ui/conf/db/migration/V1__create_schema.sql
  type "advices" (line 1) | create table "advices" (
Condensed preview — 77 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (204K chars).
[
  {
    "path": ".gitignore",
    "chars": 78,
    "preview": "node_modules\n*.iml\n*.ipr\n*.iws\n.idea/\ntarget/\ndata/\n*.log\n.DS_Store\nlocal.sbt\n"
  },
  {
    "path": ".scalafmt.conf",
    "chars": 315,
    "preview": "style = defaultWithAlign\nmaxColumn = 120\nalign.openParenCallSite = false\nalign.openParenDefnSite = false\ndanglingParenth"
  },
  {
    "path": ".travis.yml",
    "chars": 964,
    "preview": "sudo: false\nlanguage: scala\njdk:\n- oraclejdk8\nscala:\n- 2.11.8\ninstall:\n- \". $HOME/.nvm/nvm.sh\"\n- nvm install stable\n- nv"
  },
  {
    "path": "LICENSE.txt",
    "chars": 11348,
    "preview": "\n                                 Apache License\n                           Version 2.0, January 2004\n                  "
  },
  {
    "path": "README.md",
    "chars": 2975,
    "preview": "# Scala clippy\n\n[![Join the chat at https://gitter.im/softwaremill/scala-clippy](https://badges.gitter.im/softwaremill/s"
  },
  {
    "path": "build.sbt",
    "chars": 9596,
    "preview": "import sbt._\nimport Keys._\nimport sbtassembly.AssemblyKeys\n\nimport scala.xml.transform.RuleTransformer\nimport scala.xml."
  },
  {
    "path": "model/src/main/scala/com/softwaremill/clippy/Advice.scala",
    "chars": 915,
    "preview": "package com.softwaremill.clippy\n\nimport org.json4s.JsonAST._\n\nfinal case class Advice(compilationError: CompilationError"
  },
  {
    "path": "model/src/main/scala/com/softwaremill/clippy/Clippy.scala",
    "chars": 1330,
    "preview": "package com.softwaremill.clippy\n\nimport org.json4s.JsonAST._\n\ncase class Clippy(version: String, advices: List[Advice], "
  },
  {
    "path": "model/src/main/scala/com/softwaremill/clippy/CompilationError.scala",
    "chars": 10032,
    "preview": "package com.softwaremill.clippy\n\nimport org.json4s.JValue\nimport org.json4s.JsonAST.{JArray, JField, JObject, JString}\ni"
  },
  {
    "path": "model/src/main/scala/com/softwaremill/clippy/CompilationErrorParser.scala",
    "chars": 4314,
    "preview": "package com.softwaremill.clippy\n\nimport java.util.regex.Pattern\n\nobject CompilationErrorParser {\n  private val FoundRege"
  },
  {
    "path": "model/src/main/scala/com/softwaremill/clippy/Library.scala",
    "chars": 759,
    "preview": "package com.softwaremill.clippy\n\nimport org.json4s.JsonAST.{JField, JObject, JString, JValue}\n\ncase class Library(groupI"
  },
  {
    "path": "model/src/main/scala/com/softwaremill/clippy/StringDiff.scala",
    "chars": 1699,
    "preview": "package com.softwaremill.clippy\n\nobject StringDiff {\n  val separators                       = List(' ', ',', '(', ')', '"
  },
  {
    "path": "model/src/main/scala/com/softwaremill/clippy/Template.scala",
    "chars": 1080,
    "preview": "package com.softwaremill.clippy\n\nimport java.util.regex.Pattern\n\nimport scala.util.Try\nimport scala.util.matching.Regex\n"
  },
  {
    "path": "model/src/main/scala/com/softwaremill/clippy/Warning.scala",
    "chars": 821,
    "preview": "package com.softwaremill.clippy\n\nimport org.json4s.JsonAST._\n\nfinal case class Warning(pattern: RegexT, text: Option[Str"
  },
  {
    "path": "model/src/test/scala/com/softwaremill/clippy/CompilationErrorParserTest.scala",
    "chars": 10669,
    "preview": "package com.softwaremill.clippy\n\nimport org.scalatest.{FlatSpec, Matchers}\n\nclass CompilationErrorParserTest extends Fla"
  },
  {
    "path": "model/src/test/scala/com/softwaremill/clippy/CompilationErrorTest.scala",
    "chars": 6102,
    "preview": "package com.softwaremill.clippy\n\nimport org.scalacheck.Prop._\nimport org.scalacheck.Properties\nimport org.scalatest.{Fla"
  },
  {
    "path": "model/src/test/scala/com/softwaremill/clippy/LibraryProperties.scala",
    "chars": 319,
    "preview": "package com.softwaremill.clippy\n\nimport org.scalacheck.Prop._\nimport org.scalacheck.Properties\n\nclass LibraryProperties "
  },
  {
    "path": "model/src/test/scala/com/softwaremill/clippy/RegexTTest.scala",
    "chars": 1238,
    "preview": "package com.softwaremill.clippy\n\nimport org.scalatest.{FlatSpec, Matchers}\n\nclass RegexTTest extends FlatSpec with Match"
  },
  {
    "path": "model/src/test/scala/com/softwaremill/clippy/StringDiffSpecification.scala",
    "chars": 2668,
    "preview": "package com.softwaremill.clippy\n\nimport org.scalacheck.Prop.forAll\nimport org.scalacheck.Properties\n\nclass StringDiffSpe"
  },
  {
    "path": "model/src/test/scala/com/softwaremill/clippy/StringDiffTest.scala",
    "chars": 1682,
    "preview": "package com.softwaremill.clippy\n\nimport org.scalatest.{FlatSpec, Matchers}\n\nclass StringDiffTest extends FlatSpec with M"
  },
  {
    "path": "model/src/test/scala/com/softwaremill/clippy/TypeNamesGenerators.scala",
    "chars": 3494,
    "preview": "package com.softwaremill.clippy\n\nimport org.scalacheck.Gen\n\ntrait TypeNamesGenerators {\n\n  import Gen._\n\n  def typeNameC"
  },
  {
    "path": "package.json",
    "chars": 88,
    "preview": "{\n  \"name\": \"scala-clippy-site\",\n    \"dependencies\": {\n      \"jsdom\": \"9.12.0\"\n    }\n\n}\n"
  },
  {
    "path": "plugin/src/main/resources/scalac-plugin.xml",
    "chars": 106,
    "preview": "<plugin>\n    <name>clippy</name>\n    <classname>com.softwaremill.clippy.ClippyPlugin</classname>\n</plugin>"
  },
  {
    "path": "plugin/src/main/scala/com/softwaremill/clippy/AdviceLoader.scala",
    "chars": 4797,
    "preview": "package com.softwaremill.clippy\n\nimport java.io._\nimport java.net.{HttpURLConnection, URL}\nimport java.util.zip.GZIPInpu"
  },
  {
    "path": "plugin/src/main/scala/com/softwaremill/clippy/ClippyPlugin.scala",
    "chars": 8600,
    "preview": "package com.softwaremill.clippy\n\nimport java.io.File\nimport java.net.{URL, URLClassLoader}\nimport java.util.concurrent.T"
  },
  {
    "path": "plugin/src/main/scala/com/softwaremill/clippy/ColorsConfig.scala",
    "chars": 510,
    "preview": "package com.softwaremill.clippy\n\nsealed trait ColorsConfig\n\nobject ColorsConfig {\n  case object Disabled extends ColorsC"
  },
  {
    "path": "plugin/src/main/scala/com/softwaremill/clippy/FailOnWarningsReporter.scala",
    "chars": 2351,
    "preview": "package com.softwaremill.clippy\n\nimport scala.reflect.internal.util.Position\nimport scala.tools.nsc.reporters.Reporter\n\n"
  },
  {
    "path": "plugin/src/main/scala/com/softwaremill/clippy/Highlighter.scala",
    "chars": 4302,
    "preview": "package com.softwaremill.clippy\n\nimport fastparse.all._\n\nimport scalaparse.Scala._\nimport scalaparse.syntax.Identifiers."
  },
  {
    "path": "plugin/src/main/scala/com/softwaremill/clippy/InjectReporter.scala",
    "chars": 1188,
    "preview": "package com.softwaremill.clippy\n\nimport scala.reflect.internal.util.Position\nimport scala.tools.nsc.plugins.PluginCompon"
  },
  {
    "path": "plugin/src/main/scala/com/softwaremill/clippy/RestoreReporter.scala",
    "chars": 1035,
    "preview": "package com.softwaremill.clippy\n\nimport scala.tools.nsc.plugins.PluginComponent\nimport scala.tools.nsc.{Global, Phase}\n\n"
  },
  {
    "path": "plugin/src/main/scala/com/softwaremill/clippy/Utils.scala",
    "chars": 1926,
    "preview": "package com.softwaremill.clippy\n\nimport java.io.{ByteArrayOutputStream, InputStream}\nimport java.io.Closeable\nimport sca"
  },
  {
    "path": "plugin/src/main/scala-2.11/com/softwaremill/clippy/DelegatingPosition.scala",
    "chars": 6346,
    "preview": "package com.softwaremill.clippy\n\nimport scala.reflect.internal.util.{NoPosition, Position, SourceFile}\nimport scala.refl"
  },
  {
    "path": "plugin/src/main/scala-2.11/com/softwaremill/clippy/DelegatingReporter.scala",
    "chars": 2098,
    "preview": "package com.softwaremill.clippy\n\nimport scala.reflect.internal.util.Position\nimport scala.tools.nsc.reporters.Reporter\n\n"
  },
  {
    "path": "plugin/src/main/scala-2.12/com/softwaremill/clippy/DelegatingPosition.scala",
    "chars": 6346,
    "preview": "package com.softwaremill.clippy\n\nimport scala.reflect.internal.util.{NoPosition, Position, SourceFile}\nimport scala.refl"
  },
  {
    "path": "plugin/src/main/scala-2.12/com/softwaremill/clippy/DelegatingReporter.scala",
    "chars": 2098,
    "preview": "package com.softwaremill.clippy\n\nimport scala.reflect.internal.util.Position\nimport scala.tools.nsc.reporters.Reporter\n\n"
  },
  {
    "path": "plugin-sbt/src/main/scala/com/softwaremill/clippy/ClippySbtPlugin.scala",
    "chars": 4742,
    "preview": "package com.softwaremill.clippy\n\nimport sbt._\nimport sbt.Keys._\n\nimport scala.collection.mutable.ListBuffer\n\nobject Clip"
  },
  {
    "path": "project/build.properties",
    "chars": 20,
    "preview": "sbt.version=0.13.17\n"
  },
  {
    "path": "project/plugins.sbt",
    "chars": 997,
    "preview": "resolvers += Resolver.typesafeRepo(\"releases\")\n// Workaround for the bug: https://github.com/sbt/sbt-assembly/issues/236"
  },
  {
    "path": "tests/src/test/scala/org/softwaremill/clippy/CompileTests.scala",
    "chars": 5544,
    "preview": "package org.softwaremill.clippy\n\nimport java.io.{File, FileOutputStream}\nimport java.util.zip.GZIPOutputStream\nimport sc"
  },
  {
    "path": "ui/app/ClippyApplicationLoader.scala",
    "chars": 1913,
    "preview": "import api.UiApiImpl\nimport com.softwaremill.id.DefaultIdGenerator\nimport controllers._\nimport dal.AdvicesRepository\nimp"
  },
  {
    "path": "ui/app/api/UiApiImpl.scala",
    "chars": 1797,
    "preview": "package api\n\nimport com.softwaremill.clippy._\nimport dal.AdvicesRepository\nimport util.email.EmailService\n\nimport scala."
  },
  {
    "path": "ui/app/assets/stylesheets/main.less",
    "chars": 420,
    "preview": "#reactmain {\n  padding-top: 60px;\n}\n\nhtml {\n  position: relative;\n  min-height: 100%;\n}\n.cout {\n  width: 100%;\n  padding"
  },
  {
    "path": "ui/app/controllers/AdvicesController.scala",
    "chars": 962,
    "preview": "package controllers\n\nimport com.softwaremill.clippy.{Advice, Clippy}\nimport dal.AdvicesRepository\nimport play.api.mvc.{A"
  },
  {
    "path": "ui/app/controllers/ApplicationController.scala",
    "chars": 169,
    "preview": "package controllers\n\nimport play.api.mvc.{Action, Controller}\n\nclass ApplicationController extends Controller {\n\n  def i"
  },
  {
    "path": "ui/app/controllers/AutowireController.scala",
    "chars": 856,
    "preview": "package controllers\n\nimport com.softwaremill.clippy.UiApi\nimport play.api.mvc.{Action, Controller}\n\nimport upickle.defau"
  },
  {
    "path": "ui/app/controllers/HttpsFilter.scala",
    "chars": 669,
    "preview": "package controllers\n\nimport play.api.http.Status\nimport play.api.mvc.{Filter, RequestHeader, Result, Results}\n\nimport sc"
  },
  {
    "path": "ui/app/dal/AdvicesRepository.scala",
    "chars": 3355,
    "preview": "package dal\n\nimport com.softwaremill.clippy.AdviceState.AdviceState\nimport com.softwaremill.clippy._\nimport com.software"
  },
  {
    "path": "ui/app/util/ConfigWithDefault.scala",
    "chars": 1112,
    "preview": "package util\n\nimport java.util.concurrent.TimeUnit\n\nimport com.typesafe.config.Config\n\ntrait ConfigWithDefault {\n\n  def "
  },
  {
    "path": "ui/app/util/DatabaseConfig.scala",
    "chars": 1001,
    "preview": "package util\n\nimport com.typesafe.config.Config\n\ntrait DatabaseConfig extends ConfigWithDefault {\n  def rootConfig: Conf"
  },
  {
    "path": "ui/app/util/SqlDatabase.scala",
    "chars": 3610,
    "preview": "package util\n\nimport java.net.URI\n\nimport com.typesafe.config.ConfigValueFactory._\nimport com.typesafe.config.{Config, C"
  },
  {
    "path": "ui/app/util/Zip.scala",
    "chars": 900,
    "preview": "package util\n\nimport java.io.{ByteArrayInputStream, ByteArrayOutputStream}\nimport java.util.zip.{GZIPInputStream, GZIPOu"
  },
  {
    "path": "ui/app/util/email/DummyEmailService.scala",
    "chars": 765,
    "preview": "package util.email\n\nimport com.typesafe.scalalogging.StrictLogging\n\nimport scala.collection.mutable.ListBuffer\nimport sc"
  },
  {
    "path": "ui/app/util/email/EmailService.scala",
    "chars": 143,
    "preview": "package util.email\n\nimport scala.concurrent.Future\n\ntrait EmailService {\n  def send(to: String, subject: String, body: S"
  },
  {
    "path": "ui/app/util/email/SendgridEmailService.scala",
    "chars": 1271,
    "preview": "package util.email\n\nimport com.sendgrid.SendGrid\nimport com.typesafe.scalalogging.StrictLogging\n\nimport scala.concurrent"
  },
  {
    "path": "ui/app/views/index.scala.html",
    "chars": 3015,
    "preview": "<!DOCTYPE html>\n\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=e"
  },
  {
    "path": "ui/app/views/use.scala.html",
    "chars": 7246,
    "preview": "<h2>\n    Scala Clippy\n    <a class=\"twitter-share-button\" href=\"https://twitter.com/intent/tweet?text=Scala%20Clippy%3A%"
  },
  {
    "path": "ui/conf/application.conf",
    "chars": 1452,
    "preview": "# This is the main configuration file for the application.\n# ~~~~~\n\nplay.application.loader = \"ClippyApplicationLoader\"\n"
  },
  {
    "path": "ui/conf/db/migration/V1__create_schema.sql",
    "chars": 434,
    "preview": "create table \"advices\" (\n  \"id\" bigint not null primary key,\n  \"error_text_raw\" varchar not null,\n  \"compilation_error\" "
  },
  {
    "path": "ui/conf/db/migration/V2__add_pattern.sql",
    "chars": 76,
    "preview": "alter table \"advices\" add column \"pattern_raw\" varchar not null default '';\n"
  },
  {
    "path": "ui/conf/logback.xml",
    "chars": 608,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleA"
  },
  {
    "path": "ui/conf/routes",
    "chars": 607,
    "preview": "# Routes\n# This file defines all application routes (Higher priority routes first)\n# ~~~~\n\n# Home page\nGET     /        "
  },
  {
    "path": "ui/test/dal/AdvicesRepositoryTest.scala",
    "chars": 1304,
    "preview": "package dal\n\nimport com.softwaremill.clippy._\nimport com.softwaremill.id.DefaultIdGenerator\nimport util.BaseSqlSpec\nimpo"
  },
  {
    "path": "ui/test/util/BaseSqlSpec.scala",
    "chars": 1112,
    "preview": "package util\n\nimport org.scalatest.concurrent.ScalaFutures\nimport org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach, "
  },
  {
    "path": "ui-client/src/main/scala/com/softwaremill/clippy/App.scala",
    "chars": 5425,
    "preview": "package com.softwaremill.clippy\n\nimport japgolly.scalajs.react._\nimport autowire._\nimport japgolly.scalajs.react.vdom.pr"
  },
  {
    "path": "ui-client/src/main/scala/com/softwaremill/clippy/AutowireClient.scala",
    "chars": 646,
    "preview": "package com.softwaremill.clippy\n\nimport org.scalajs.dom\nimport scala.concurrent.Future\nimport scalajs.concurrent.JSExecu"
  },
  {
    "path": "ui-client/src/main/scala/com/softwaremill/clippy/BsUtils.scala",
    "chars": 955,
    "preview": "package com.softwaremill.clippy\n\nimport japgolly.scalajs.react._\nimport japgolly.scalajs.react.extra.ExternalVar\nimport "
  },
  {
    "path": "ui-client/src/main/scala/com/softwaremill/clippy/Contribute.scala",
    "chars": 10911,
    "preview": "package com.softwaremill.clippy\n\nimport japgolly.scalajs.react._\nimport japgolly.scalajs.react.vdom.prefix_<^._\nimport B"
  },
  {
    "path": "ui-client/src/main/scala/com/softwaremill/clippy/Feedback.scala",
    "chars": 1912,
    "preview": "package com.softwaremill.clippy\n\nimport autowire._\nimport com.softwaremill.clippy.BsUtils._\nimport com.softwaremill.clip"
  },
  {
    "path": "ui-client/src/main/scala/com/softwaremill/clippy/FormField.scala",
    "chars": 1189,
    "preview": "package com.softwaremill.clippy\n\nimport japgolly.scalajs.react._\n\ncase class FormField(label: String, required: Boolean,"
  },
  {
    "path": "ui-client/src/main/scala/com/softwaremill/clippy/Listing.scala",
    "chars": 3676,
    "preview": "package com.softwaremill.clippy\n\nimport com.softwaremill.clippy.BsUtils._\nimport com.softwaremill.clippy.Utils._\nimport "
  },
  {
    "path": "ui-client/src/main/scala/com/softwaremill/clippy/Main.scala",
    "chars": 553,
    "preview": "package com.softwaremill.clippy\n\nimport japgolly.scalajs.react._\nimport org.scalajs.jquery._\nimport scala.scalajs.js\nimp"
  },
  {
    "path": "ui-client/src/main/scala/com/softwaremill/clippy/Menu.scala",
    "chars": 2172,
    "preview": "package com.softwaremill.clippy\n\nimport com.softwaremill.clippy.App._\nimport japgolly.scalajs.react._\nimport japgolly.sc"
  },
  {
    "path": "ui-client/src/main/scala/com/softwaremill/clippy/Use.scala",
    "chars": 315,
    "preview": "package com.softwaremill.clippy\n\nimport japgolly.scalajs.react._\nimport japgolly.scalajs.react.vdom.prefix_<^._\n\nobject "
  },
  {
    "path": "ui-client/src/main/scala/com/softwaremill/clippy/Utils.scala",
    "chars": 569,
    "preview": "package com.softwaremill.clippy\n\nimport japgolly.scalajs.react.BackendScope\nimport japgolly.scalajs.react.extra.External"
  },
  {
    "path": "ui-shared/src/main/scala/com/softwaremill/clippy/AdviceState.scala",
    "chars": 145,
    "preview": "package com.softwaremill.clippy\n\nobject AdviceState extends Enumeration {\n  type AdviceState = Value\n  val Pending, Acce"
  },
  {
    "path": "ui-shared/src/main/scala/com/softwaremill/clippy/Contributor.scala",
    "chars": 128,
    "preview": "package com.softwaremill.clippy\n\ncase class Contributor(email: Option[String], twitter: Option[String], github: Option[S"
  },
  {
    "path": "ui-shared/src/main/scala/com/softwaremill/clippy/UiApi.scala",
    "chars": 1047,
    "preview": "package com.softwaremill.clippy\n\nimport scala.concurrent.Future\n\ntrait UiApi extends ContributeApi with ListingApi with "
  }
]

About this extraction

This page contains the full source code of the softwaremill/scala-clippy GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 77 files (185.5 KB), approximately 50.1k tokens, and a symbol index with 1 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!