Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for linters #291

Merged
merged 18 commits into from
Aug 18, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 23 additions & 14 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ lazy val allSettings = List(
stableVersion := version.value.replaceAll("\\+.*", ""),
resolvers += Resolver.sonatypeRepo("releases"),
triggeredMessage in ThisBuild := Watched.clearWhenTriggered,
scalacOptions ++= compilerOptions,
scalacOptions in (Compile, console) := compilerOptions :+ "-Yrepl-class-based",
scalacOptions ++= compilerOptions.value,
scalacOptions in (Compile, console) := compilerOptions.value :+ "-Yrepl-class-based",
libraryDependencies += scalatest.value % Test,
testOptions in Test += Tests.Argument("-oD"),
scalaVersion := ciScalaVersion.getOrElse(scala212),
Expand Down Expand Up @@ -197,6 +197,7 @@ lazy val `scalafix-sbt` = project
.configs(IntegrationTest)
.settings(
allSettings,
is210Only,
publishSettings,
buildInfoSettings,
Defaults.itSettings,
Expand All @@ -220,8 +221,6 @@ lazy val `scalafix-sbt` = project
"; very scalafix-sbt/scripted"
)(state.value)
},
scalaVersion := scala210,
crossScalaVersions := Seq(scala210),
moduleName := "sbt-scalafix",
scriptedLaunchOpts ++= Seq(
"-Dplugin.version=" + version.value,
Expand Down Expand Up @@ -301,6 +300,7 @@ lazy val testsOutput = project
allSettings,
noPublish,
semanticdbSettings,
scalacOptions -= warnUnusedImports,
resolvers := resolvers.in(testsInput).value,
libraryDependencies := libraryDependencies.in(testsInput).value
)
Expand All @@ -324,7 +324,7 @@ lazy val testsInputSbt = project
.settings(
logLevel := Level.Error, // avoid flood of deprecation warnings.
scalacOptions += "-Xplugin-require:sbthost",
scalaVersion := scala210,
is210Only,
sbtPlugin := true,
scalacOptions += s"-P:sbthost:sourceroot:${sourceDirectory.in(Compile).value}",
addCompilerPlugin(
Expand All @@ -336,8 +336,7 @@ lazy val testsOutputSbt = project
.settings(
allSettings,
noPublish,
scalaVersion := scala210,
crossScalaVersions := Seq(scala210),
is210Only,
sbtPlugin := true
)

Expand Down Expand Up @@ -437,17 +436,27 @@ lazy val readme = scalatex
.dependsOn(coreJVM, cli)
.enablePlugins(GhpagesPlugin)

lazy val is210Only = Seq(
scalaVersion := scala210,
crossScalaVersions := Seq(scala210),
scalacOptions -= warnUnusedImports
)

lazy val isFullCrossVersion = Seq(
crossVersion := CrossVersion.full
)

lazy val compilerOptions = Seq(
"-deprecation",
"-encoding",
"UTF-8",
"-feature",
"-unchecked"
)
lazy val warnUnusedImports = "-Ywarn-unused-import"
lazy val compilerOptions = Def.setting {
Seq(
"-deprecation",
"-encoding",
"UTF-8",
"-feature",
warnUnusedImports,
"-unchecked"
)
}

lazy val gitPushTag = taskKey[Unit]("Push to git tag")

Expand Down
2 changes: 1 addition & 1 deletion readme/Changelog03.scalatex
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,4 @@
Two rewrite rules:
@sect.ref{ProcedureSyntax}
and
@sect.ref{VolatileLazyVal}.
@sect.ref{DottyVolatileLazyVal}.
11 changes: 10 additions & 1 deletion readme/Changelog05.scalatex
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
Scala.js support for @sect.ref{scalafix-core}, by @user{gabro}.
@li
Configurable @sect.ref{ExplicitReturnTypes}, by @user{taisuke}.
@li
Configurable @sect.ref{lint} reporting.
@li
@sect.ref{sbt-scalafix} rewrite tab completion.
@li
Expand All @@ -42,6 +44,11 @@
Ability to implement rewrites for sources of sbt builds, including
@code{*.sbt} files. The API to write sbt rewrites is identical to
regular Scala rewrites.
@li
Rewrites can now emit lint messages with @code{ctx.lint}, see
@sect.ref{LintMessage}.
@li
Rewrites can have multiple names with optional deprecation warnings.
@li
Thanks to upstream improvements in the
@lnk("Scalameta v2.0 Semantic API", "http://scalameta.org/tutorial/#SemanticAPI"),
Expand Down Expand Up @@ -74,7 +81,7 @@
@code{Predef.intArrayOps} and now it resolves to
@code{IndexedSeqOptimized.head}.

@h4{Breaking changes for rewrite authors}
@h4{Breaking changes}
@p
From 0.5 onwards, our CI will check binary breaking changes in the public API
on every pull request.
Expand All @@ -96,4 +103,6 @@
@li
upgraded to Scalameta 2.0, which has several breaking changes
in the Tree api.
@li
The @code{VolatileLazyVal} rewrite is now named @sect.ref{DottyVolatileLazyVal}.

10 changes: 10 additions & 0 deletions readme/Configuration.scalatex
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@
@config
rewrite = "https://gist.githubusercontent.com/olafurpg/fc6f43a695ac996bd02000f45ed02e63/raw/80218434edb85120a9c6fd6533a4418118de8ba7/ExampleRewrite.scala"

@sect{lint}
Override the default severity level of a @sect.ref{LintMessage} with @code{lint}
@config
// Assuming 'Foo' is a rewrite and 'warningID'/'errorID' are LintCategory IDs.
lint.error = [ Foo.warningID ] // promote Foo.warnigID to an error
lint.warning = [ Foo.errorID ] // demote Foo.errorID to a warning
lint.info = [ Foo.errorID ] // demote Foo.errorID to info
lint.ignore = [ Foo.errorID ] // don't report Foo.errorID
lint.explain = true // print out detailed explanation for lint messages.

@sect{patches}
For simple use-cases, it's possible to write custom rewrites directly in
.scalafix.conf.
Expand Down
23 changes: 23 additions & 0 deletions readme/ImplementingRewrites.scalatex
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@
@li
Test failures are reported as unified diffs from the obtained output
of the rewrite and the expected output in the @code{output} project.
@li
Assert that a @sect.ref{LintMessage} is expected at a particular line
by suffixing the line with the comment @code{// scalafix: <LintCategory>}.
The test fails if there exist reported lint messages that have no
associated assertion in the input file.

@sect{Example rewrites}
The Scalafix repository contains several example rewrites and tests,
Expand Down Expand Up @@ -140,6 +145,24 @@
If you experience that it's difficult to implement something that
seems simple then don't hesitate to ask on @gitter.

@sect{LintMessage}
Rewrites are able to emit "lint messages" with info/warn/error severity
using @code{ctx.lint(lintCategory.at(String/Position)): Patch}.
To report a lint message, first create a @sect.ref{LintCategory} and then
report it as a @code{Patch}
@hl.scala
val divisionByZero = LintCategory.error("Division by zero is unsafe!")
def rewrite(ctx: RewriteCtx): Patch = {
val tree: Tree = // ...
ctx.lint(divisionByZero.at(tree.pos))
}

@sect{LintCategory}
A LintCategory is group of lint messages of the same kind.
A LintCategory has a default severity level (info/warn/error) at which
it will be reported. Scalafix users can override the default severity
with @sect.ref{lint}.

@sect{Scalameta}
Scalafix uses @lnk("Scalameta", "http://scalameta.org/") to implement
rewrites.
Expand Down
2 changes: 1 addition & 1 deletion readme/Rewrites.scalatex
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@
println("Hello world!")
}

@sect(VolatileLazyVal.toString)
@sect(DottyVolatileLazyVal.toString)
@p
Adds a @code{@@volatile} annotation to lazy vals.
The @code{@@volatile} annotation is needed to maintain thread-safe
Expand Down
1 change: 0 additions & 1 deletion readme/src/main/scala/scalafix/Readme.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import scalafix.internal.config.ScalafixMetaconfigReaders
import scalafix.reflect.ScalafixReflect
import scalatags.Text.TypedTag
import scalatags.Text.all._
import scalatex.Main
import scalatex.site.Highlighter

object Readme {
Expand Down
18 changes: 13 additions & 5 deletions scalafix-cli/src/main/scala/scalafix/cli/Cli.scala
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ object ScalafixRewriteNames {
filename.endsWith(".scala") || filename.endsWith(".sbt")
}

def parse(args: Seq[String]): CliCommand = {
def parse(args: Seq[String], common: CommonOptions): CliCommand = {
import CliCommand._
OptionsParser.withHelp.detailedParse(args) match {
case Left(err) =>
Expand All @@ -209,12 +209,16 @@ object ScalafixRewriteNames {
Files.write(path, sbtCompletions.getBytes)
PrintAndExit(s"Sbt completions installed in $path", ExitStatus.Ok)
case Right((WithHelp(_, _, options), extraFiles, _)) =>
parseOptions(options.copy(files = options.files ++ extraFiles))
parseOptions(
options.copy(
common = common,
files = options.files ++ extraFiles
))
}
}

def runMain(args: Seq[String], commonOptions: CommonOptions): ExitStatus =
runMain(parse(args), commonOptions)
def runMain(args: Seq[String], common: CommonOptions): ExitStatus =
runMain(parse(args, common), common)

def runMain(
cliCommand: CliCommand,
Expand All @@ -230,7 +234,11 @@ object ScalafixRewriteNames {
}
// This one accummulates a lot of garbage, scalameta needs to get rid of it.
PlatformTokenizerCache.megaCache.clear()
result
if (commonOptions.reporter.hasErrors) {
ExitStatus.merge(ExitStatus.InvalidCommandLineOption, result)
} else {
result
}
}

def nailMain(nGContext: NGContext): Unit = {
Expand Down
26 changes: 19 additions & 7 deletions scalafix-cli/src/main/scala/scalafix/cli/CliRunner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import java.util.regex.PatternSyntaxException
import scala.meta._
import scala.meta.inputs.Input
import scala.meta.internal.inputs._
import scala.meta.internal.tokenizers.PlatformTokenizerCache
import scala.meta.io.AbsolutePath
import scala.meta.sbthost.Sbthost
import scala.util.Try
Expand Down Expand Up @@ -80,7 +79,10 @@ sealed abstract case class CliRunner(
code
}
display.stop()
exitCode.get()
val exit = exitCode.get()
if (config.reporter.hasErrors) {
ExitStatus.merge(ExitStatus.LinterError, exit)
} else exit
}

// safeguard to verify that the original file contents have not changed since the
Expand Down Expand Up @@ -114,7 +116,7 @@ sealed abstract case class CliRunner(
}
case parsers.Parsed.Success(tree) =>
val ctx = RewriteCtx(tree, config)
val fixed = rewrite(ctx)
val fixed = rewrite.apply(ctx)
writeMode match {
case WriteMode.Stdout =>
common.out.write(fixed.getBytes)
Expand Down Expand Up @@ -170,7 +172,7 @@ sealed abstract case class CliRunner(
path: AbsolutePath,
cause: Throwable,
options: ScalafixOptions): Unit = {
options.common.reporter.error(s"Failed to fix $path")
config.reporter.error(s"Failed to fix $path")
cause.setStackTrace(cause.getStackTrace.take(options.common.stackVerbosity))
cause.printStackTrace(options.common.err)
}
Expand Down Expand Up @@ -330,12 +332,14 @@ object CliRunner {
if (kind.isSyntactic) None
else computeAndCacheDatabase()
}
private val lazySemanticCtx: LazySemanticCtx = resolveDatabase
private val lazySemanticCtx: LazySemanticCtx =
new LazySemanticCtx(resolveDatabase, common.reporter)

// expands a single file into a list of files.
def expand(matcher: FilterMatcher)(path: AbsolutePath): Seq[FixFile] = {
if (!path.toFile.exists()) {
common.err.println(s"$path does not exist.")
common.reporter.error(
s"$path does not exist. ${common.workingDirectory}")
Nil
} else if (path.isDirectory) {
val builder = Seq.newBuilder[FixFile]
Expand Down Expand Up @@ -426,7 +430,15 @@ object CliRunner {
}
}
val resolvedRewrite: Configured[Rewrite] =
resolvedRewriteAndConfig.map(_._1)
resolvedRewriteAndConfig.andThen {
case (rewrite, _) =>
if (rewrite.rewriteName.isEmpty)
ConfError
.msg(
"No rewrite was provided! Use --rewrite to specify a rewrite.")
.notOk
else Ok(rewrite)
}
val resolvedConfig: Configured[ScalafixConfig] =
resolvedRewriteAndConfig.map(_._2)

Expand Down
3 changes: 2 additions & 1 deletion scalafix-cli/src/main/scala/scalafix/cli/ExitStatus.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ object ExitStatus {
InvalidCommandLineOption,
MissingSemanticApi,
StaleSemanticDB,
TestFailed
TestFailed,
LinterError
: ExitStatus = generateExitStatus
// format: on
lazy val all: List[ExitStatus] = allInternal.toList
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,16 @@ case class CommonOptions(
@Hidden err: PrintStream = System.err,
@Hidden stackVerbosity: Int = 20
) {
def reporter: PrintStreamReporter =
ScalafixReporter.default.copy(outStream = err)
lazy val reporter: PrintStreamReporter =
ScalafixReporter.default.copy(outStream = out)
def workingPath = AbsolutePath(workingDirectory)
def workingDirectoryFile = new File(workingDirectory)
}

object CommonOptions {
lazy val default = CommonOptions()
}

// NOTE: Do not depend on this class as a library, the interface will change between
// patch versions.
@AppName("scalafix")
Expand Down
Loading