From 0086b1312ff7c43e40cc3dc2b7fd1eca348dfd9b Mon Sep 17 00:00:00 2001 From: Bor Kae Hwang Date: Tue, 28 Jan 2020 12:11:22 -0700 Subject: [PATCH] Phase Scalafmt (#912) * Phase Scalafmt * Reuse formatter * Remove glob * Remove rules_jvm_external * Rename argparse * Remove executable * Use shared code * Remove imports * Add comment * Change file name * Move args to private function * Change to true * Change conf location * Change default conf * Test custom conf * Fix lint * Fix build * Remove trailing commas * Add formatted and unformatted folder * Rename test function * Remove template file * Better handle failing case * Drop argparser * Remove resolve_command * Add comments * Remove unnecessary code * Change to RUNPATH * Rename gitignore backup * Remove comment * Move conf file * Add doc to attribute * Switch to match readme * Add doc * Move doc to separate md * Fix wording * Fix lint * Add url * Add url --- .gitignore | 1 + .scalafmt.conf | 15 + BUILD | 0 README.md | 3 + WORKSPACE | 6 + docs/phase_scalafmt.md | 43 +++ scala/private/macros/scala_repositories.bzl | 5 +- scala/private/phases/phase_scalafmt.bzl | 68 ++++ scala/private/phases/phases.bzl | 4 + scala/scalafmt/BUILD | 36 +++ scala/scalafmt/phase_scalafmt_ext.bzl | 55 ++++ .../scalafmt/private/format-test.template.sh | 18 ++ scala/scalafmt/private/format.template.sh | 14 + scala/scalafmt/scalafmt/ScalafmtRunner.scala | 51 +++ scala/scalafmt/scalafmt_repositories.bzl | 291 ++++++++++++++++++ test/scalafmt/.scalafmt.conf | 1 + test/scalafmt/BUILD | 68 ++++ .../formatted/formatted-custom-conf.scala | 7 + .../formatted/formatted-encoding.scala | 12 + test/scalafmt/formatted/formatted-test.scala | 7 + test/scalafmt/phase_scalafmt_test.bzl | 16 + .../unformatted/unformatted-custom-conf.scala | 6 + .../unformatted/unformatted-encoding.scala | 15 + .../unformatted/unformatted-test.scala | 7 + test/shell/test_scalafmt.sh | 75 +++++ test_rules_scala.sh | 1 + 26 files changed, 824 insertions(+), 1 deletion(-) create mode 100644 .scalafmt.conf create mode 100644 BUILD create mode 100644 docs/phase_scalafmt.md create mode 100644 scala/private/phases/phase_scalafmt.bzl create mode 100644 scala/scalafmt/BUILD create mode 100644 scala/scalafmt/phase_scalafmt_ext.bzl create mode 100644 scala/scalafmt/private/format-test.template.sh create mode 100644 scala/scalafmt/private/format.template.sh create mode 100644 scala/scalafmt/scalafmt/ScalafmtRunner.scala create mode 100644 scala/scalafmt/scalafmt_repositories.bzl create mode 100644 test/scalafmt/.scalafmt.conf create mode 100644 test/scalafmt/BUILD create mode 100644 test/scalafmt/formatted/formatted-custom-conf.scala create mode 100644 test/scalafmt/formatted/formatted-encoding.scala create mode 100644 test/scalafmt/formatted/formatted-test.scala create mode 100644 test/scalafmt/phase_scalafmt_test.bzl create mode 100644 test/scalafmt/unformatted/unformatted-custom-conf.scala create mode 100644 test/scalafmt/unformatted/unformatted-encoding.scala create mode 100644 test/scalafmt/unformatted/unformatted-test.scala create mode 100755 test/shell/test_scalafmt.sh diff --git a/.gitignore b/.gitignore index 29fb4ca6dd..b0868fed87 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ hash2 .bazel_cache .ijwb .metals +unformatted-*.backup.scala diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 0000000000..dfb81c64b5 --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1,15 @@ +align.openParenCallSite = false +align.openParenDefnSite = false +continuationIndent.defnSite = 2 +danglingParentheses = true +docstrings = JavaDoc +importSelectors = singleLine +maxColumn = 120 +verticalMultiline.newlineBeforeImplicitKW = true +rewrite.redundantBraces.stringInterpolation = true +rewrite.rules = [ + RedundantParens, + PreferCurlyFors, + SortImports +] +unindentTopLevelOperators = false diff --git a/BUILD b/BUILD new file mode 100644 index 0000000000..e69de29bb2 diff --git a/README.md b/README.md index a2f03d7608..87af5fd106 100644 --- a/README.md +++ b/README.md @@ -201,6 +201,9 @@ Phases provide 3 major benefits: See [Customizable Phase](docs/customizable_phase.md) for more info. +### Phase extensions + - [Scala Format](docs/phase_scalafmt.md) + ## Building from source Test & Build: ``` diff --git a/WORKSPACE b/WORKSPACE index 0df99da4e0..7af1dfebc7 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -40,6 +40,12 @@ load("//specs2:specs2_junit.bzl", "specs2_junit_repositories") specs2_junit_repositories() +load("//scala/scalafmt:scalafmt_repositories.bzl", "scalafmt_default_config", "scalafmt_repositories") + +scalafmt_default_config() + +scalafmt_repositories() + load("//scala:scala_cross_version.bzl", "default_scala_major_version", "scala_mvn_artifact") MAVEN_SERVER_URLS = [ diff --git a/docs/phase_scalafmt.md b/docs/phase_scalafmt.md new file mode 100644 index 0000000000..28fdb92f23 --- /dev/null +++ b/docs/phase_scalafmt.md @@ -0,0 +1,43 @@ +# Phase Scalafmt + +## Contents +* [Overview](#overview) +* [How to set up](#how-to-set-up) + +## Overview +A phase extension `phase_scalafmt` can format Scala source code via [Scalafmt](https://scalameta.org/scalafmt/). + +## How to set up +Add this snippet to `WORKSPACE` +``` +load("//scala/scalafmt:scalafmt_repositories.bzl", "scalafmt_default_config", "scalafmt_repositories") +scalafmt_default_config() +scalafmt_repositories() +``` + +To add this phase to a rule, you have to pass the extension to a rule macro. Take `scala_binary` for example, +``` +load("//scala:advanced_usage/scala.bzl", "make_scala_binary") +load("//scala/scalafmt:phase_scalafmt_ext.bzl", "ext_scalafmt") + +scalafmt_scala_binary = make_scala_binary(ext_scalafmt) +``` +Then use `scalafmt_scala_binary` as normal. + +The extension adds 2 additional attributes to the rule + - `format`: enable formatting + - `config`: the Scalafmt configuration file + +When `format` is set to `true`, you can do +``` +bazel run .format +``` +to format the source code, and do +``` +bazel run .format-test +``` +to check the format (without modifying source code). + +The extension provides default configuration, but there are 2 ways to use custom configuration + - Put `.scalafmt.conf` at root of your workspace + - Pass `.scalafmt.conf` in via `config` attribute diff --git a/scala/private/macros/scala_repositories.bzl b/scala/private/macros/scala_repositories.bzl index ff8017f37a..f3bf218f8b 100644 --- a/scala/private/macros/scala_repositories.bzl +++ b/scala/private/macros/scala_repositories.bzl @@ -56,7 +56,10 @@ def scala_repositories( _default_scala_version(), _default_scala_version_jar_shas(), ), - maven_servers = ["https://repo.maven.apache.org/maven2"], + maven_servers = [ + "https://repo.maven.apache.org/maven2", + "https://maven-central.storage-download.googleapis.com/maven2", + ], scala_extra_jars = _default_scala_extra_jars()): (scala_version, scala_version_jar_shas) = scala_version_shas major_version = _extract_major_version(scala_version) diff --git a/scala/private/phases/phase_scalafmt.bzl b/scala/private/phases/phase_scalafmt.bzl new file mode 100644 index 0000000000..5e8284c355 --- /dev/null +++ b/scala/private/phases/phase_scalafmt.bzl @@ -0,0 +1,68 @@ +# +# PHASE: phase scalafmt +# +# Outputs to format the scala files when it is explicitly specified +# +def phase_scalafmt(ctx, p): + if ctx.attr.format: + manifest, files = _build_format(ctx) + _formatter(ctx, manifest, files, ctx.file._runner, ctx.outputs.scalafmt_runner) + _formatter(ctx, manifest, files, ctx.file._testrunner, ctx.outputs.scalafmt_testrunner) + else: + _write_empty_content(ctx, ctx.outputs.scalafmt_runner) + _write_empty_content(ctx, ctx.outputs.scalafmt_testrunner) + +def _build_format(ctx): + files = [] + manifest_content = [] + for src in ctx.files.srcs: + # only format scala source files, not generated files + if src.path.endswith(".scala") and src.is_source: + file = ctx.actions.declare_file("{}.fmt.output".format(src.short_path)) + files.append(file) + ctx.actions.run( + arguments = ["--jvm_flag=-Dfile.encoding=UTF-8", _format_args(ctx, src, file)], + executable = ctx.executable._fmt, + outputs = [file], + inputs = [ctx.file.config, src], + execution_requirements = {"supports-workers": "1"}, + mnemonic = "ScalaFmt", + ) + manifest_content.append("{} {}".format(src.short_path, file.short_path)) + + # record the source path and the formatted file path + # so that we know where to copy the formatted file to replace the source file + manifest = ctx.actions.declare_file("format/{}/manifest.txt".format(ctx.label.name)) + ctx.actions.write(manifest, "\n".join(manifest_content) + "\n") + + return manifest, files + +def _formatter(ctx, manifest, files, template, output_runner): + ctx.actions.run_shell( + inputs = [template, manifest] + files, + outputs = [output_runner], + # replace %workspace% and %manifest% in template and rewrite it to output_runner + command = "cat $1 | sed -e s#%workspace%#$2# -e s#%manifest%#$3# > $4", + arguments = [ + template.path, + ctx.workspace_name, + manifest.short_path, + output_runner.path, + ], + execution_requirements = {}, + ) + +def _write_empty_content(ctx, output_runner): + ctx.actions.write( + output = output_runner, + content = "", + ) + +def _format_args(ctx, src, file): + args = ctx.actions.args() + args.add(ctx.file.config.path) + args.add(src.path) + args.add(file.path) + args.set_param_file_format("multiline") + args.use_param_file("@%s", use_always = True) + return args diff --git a/scala/private/phases/phases.bzl b/scala/private/phases/phases.bzl index 2cba42bde8..102c9fe874 100644 --- a/scala/private/phases/phases.bzl +++ b/scala/private/phases/phases.bzl @@ -60,6 +60,7 @@ load("@io_bazel_rules_scala//scala/private:phases/phase_declare_executable.bzl", load("@io_bazel_rules_scala//scala/private:phases/phase_merge_jars.bzl", _phase_merge_jars = "phase_merge_jars") load("@io_bazel_rules_scala//scala/private:phases/phase_jvm_flags.bzl", _phase_jvm_flags = "phase_jvm_flags") load("@io_bazel_rules_scala//scala/private:phases/phase_coverage_runfiles.bzl", _phase_coverage_runfiles = "phase_coverage_runfiles") +load("@io_bazel_rules_scala//scala/private:phases/phase_scalafmt.bzl", _phase_scalafmt = "phase_scalafmt") # API run_phases = _run_phases @@ -129,3 +130,6 @@ phase_runfiles_common = _phase_runfiles_common phase_default_info_binary = _phase_default_info_binary phase_default_info_library = _phase_default_info_library phase_default_info_scalatest = _phase_default_info_scalatest + +# scalafmt +phase_scalafmt = _phase_scalafmt diff --git a/scala/scalafmt/BUILD b/scala/scalafmt/BUILD new file mode 100644 index 0000000000..0be9c98a45 --- /dev/null +++ b/scala/scalafmt/BUILD @@ -0,0 +1,36 @@ +load("//scala:scala.bzl", "scala_binary") + +filegroup( + name = "runner", + srcs = ["private/format.template.sh"], + visibility = ["//visibility:public"], +) + +filegroup( + name = "testrunner", + srcs = ["private/format-test.template.sh"], + visibility = ["//visibility:public"], +) + +scala_binary( + name = "scalafmt", + srcs = ["scalafmt/ScalafmtRunner.scala"], + main_class = "io.bazel.rules_scala.scalafmt.ScalafmtRunner", + visibility = ["//visibility:public"], + deps = [ + "//src/java/io/bazel/rulesscala/worker", + "@com_geirsson_metaconfig_core_2_11", + "@org_scalameta_parsers_2_11", + "@org_scalameta_scalafmt_core_2_11", + ], +) + +load( + "//scala/scalafmt:phase_scalafmt_ext.bzl", + "scalafmt_singleton", +) + +scalafmt_singleton( + name = "phase_scalafmt", + visibility = ["//visibility:public"], +) diff --git a/scala/scalafmt/phase_scalafmt_ext.bzl b/scala/scalafmt/phase_scalafmt_ext.bzl new file mode 100644 index 0000000000..858ee0679b --- /dev/null +++ b/scala/scalafmt/phase_scalafmt_ext.bzl @@ -0,0 +1,55 @@ +load( + "//scala:advanced_usage/providers.bzl", + _ScalaRulePhase = "ScalaRulePhase", +) +load( + "//scala/private:phases/phases.bzl", + _phase_scalafmt = "phase_scalafmt", +) + +ext_scalafmt = { + "attrs": { + "config": attr.label( + allow_single_file = [".conf"], + default = "@scalafmt_default//:config", + doc = "The Scalafmt configuration file.", + ), + "format": attr.bool( + default = False, + doc = "Switch of enabling formatting.", + ), + "_fmt": attr.label( + cfg = "host", + default = "//scala/scalafmt", + executable = True, + ), + "_runner": attr.label( + allow_single_file = True, + default = "//scala/scalafmt:runner", + ), + "_testrunner": attr.label( + allow_single_file = True, + default = "//scala/scalafmt:testrunner", + ), + }, + "outputs": { + "scalafmt_runner": "%{name}.format", + "scalafmt_testrunner": "%{name}.format-test", + }, + "phase_providers": [ + "//scala/scalafmt:phase_scalafmt", + ], +} + +def _scalafmt_singleton_implementation(ctx): + return [ + _ScalaRulePhase( + custom_phases = [ + ("$", "", "scalafmt", _phase_scalafmt), + ], + ), + ] + +scalafmt_singleton = rule( + implementation = _scalafmt_singleton_implementation, +) diff --git a/scala/scalafmt/private/format-test.template.sh b/scala/scalafmt/private/format-test.template.sh new file mode 100644 index 0000000000..0ca9d99b90 --- /dev/null +++ b/scala/scalafmt/private/format-test.template.sh @@ -0,0 +1,18 @@ +#!/bin/bash -e +WORKSPACE_ROOT="${1:-$BUILD_WORKSPACE_DIRECTORY}" +RUNPATH="${TEST_SRCDIR-$0.runfiles}"/%workspace% +RUNPATH=(${RUNPATH//bin/ }) +RUNPATH="${RUNPATH[0]}"bin + +EXIT=0 +while read original formatted; do + if [[ ! -z "$original" ]] && [[ ! -z "$formatted" ]]; then + if ! cmp -s "$WORKSPACE_ROOT/$original" "$RUNPATH/$formatted"; then + echo $original + diff "$WORKSPACE_ROOT/$original" "$RUNPATH/$formatted" || true + EXIT=1 + fi + fi +done < "$RUNPATH"/%manifest% + +exit $EXIT diff --git a/scala/scalafmt/private/format.template.sh b/scala/scalafmt/private/format.template.sh new file mode 100644 index 0000000000..63e5adc0a9 --- /dev/null +++ b/scala/scalafmt/private/format.template.sh @@ -0,0 +1,14 @@ +#!/bin/bash -e +WORKSPACE_ROOT="${1:-$BUILD_WORKSPACE_DIRECTORY}" +RUNPATH="${TEST_SRCDIR-$0.runfiles}"/%workspace% +RUNPATH=(${RUNPATH//bin/ }) +RUNPATH="${RUNPATH[0]}"bin + +while read original formatted; do + if [[ ! -z "$original" ]] && [[ ! -z "$formatted" ]]; then + if ! cmp -s "$WORKSPACE_ROOT/$original" "$RUNPATH/$formatted"; then + echo "Formatting $original" + cp "$RUNPATH/$formatted" "$WORKSPACE_ROOT/$original" + fi + fi +done < "$RUNPATH"/%manifest% diff --git a/scala/scalafmt/scalafmt/ScalafmtRunner.scala b/scala/scalafmt/scalafmt/ScalafmtRunner.scala new file mode 100644 index 0000000000..5a4a870f17 --- /dev/null +++ b/scala/scalafmt/scalafmt/ScalafmtRunner.scala @@ -0,0 +1,51 @@ +package io.bazel.rules_scala.scalafmt + +import io.bazel.rulesscala.worker.{GenericWorker, Processor}; +import java.io.File +import java.nio.file.Files +import org.scalafmt.Scalafmt +import org.scalafmt.config.Config +import org.scalafmt.util.FileOps +import scala.annotation.tailrec +import scala.collection.JavaConverters._ +import scala.io.Codec + +object ScalafmtRunner extends GenericWorker(new ScalafmtProcessor) { + def main(args: Array[String]) { + try run(args) + catch { + case x: Exception => + x.printStackTrace() + System.exit(1) + } + } +} + +class ScalafmtProcessor extends Processor { + def processRequest(args: java.util.List[String]) { + val argName = List("config", "input", "output") + val argFile = args.asScala.map{x => new File(x)} + val namespace = argName.zip(argFile).toMap + + val source = FileOps.readFile(namespace.getOrElse("input", new File("")))(Codec.UTF8) + + val config = Config.fromHoconFile(namespace.getOrElse("config", new File(""))).get + @tailrec + def format(code: String): String = { + val formatted = Scalafmt.format(code, config).get + if (code == formatted) code else format(formatted) + } + + val output = try { + format(source) + } catch { + case e @ (_: org.scalafmt.Error | _: scala.meta.parsers.ParseException) => { + System.out.println("Unable to format file due to bug in scalafmt") + System.out.println(e.toString) + source + } + } + + Files.write(namespace.getOrElse("output", new File("")).toPath, output.getBytes) + } +} diff --git a/scala/scalafmt/scalafmt_repositories.bzl b/scala/scalafmt/scalafmt_repositories.bzl new file mode 100644 index 0000000000..487f45cc0a --- /dev/null +++ b/scala/scalafmt/scalafmt_repositories.bzl @@ -0,0 +1,291 @@ +load( + "@io_bazel_rules_scala//scala:scala_maven_import_external.bzl", + _scala_maven_import_external = "scala_maven_import_external", +) + +def scalafmt_default_config(path = ".scalafmt.conf"): + build = [] + build.append("filegroup(") + build.append(" name = \"config\",") + build.append(" srcs = [\"{}\"],".format(path)) + build.append(" visibility = [\"//visibility:public\"],") + build.append(")") + native.new_local_repository(name = "scalafmt_default", build_file_content = "\n".join(build), path = "") + +def scalafmt_repositories( + maven_servers = [ + "https://repo.maven.apache.org/maven2", + "https://maven-central.storage-download.googleapis.com/maven2", + ]): + _scala_maven_import_external( + name = "com_geirsson_metaconfig_core_2_11", + artifact = "com.geirsson:metaconfig-core_2.11:0.8.3", + artifact_sha256 = "8abb4e48507486d0b323b440bb021bddd56366e502002025fdaf10025d2650c2", + licenses = ["notice"], + server_urls = maven_servers, + ) + + _scala_maven_import_external( + name = "org_scalameta_common_2_11", + artifact = "org.scalameta:common_2.11:4.2.0", + artifact_sha256 = "c2921b10ef2a06cafa48d4e0a6be4ff42c9135b5c5cf51ec3fdd9b66077f66cb", + srcjar_sha256 = "373ee3a734ae1ca8cb3361812f0702ed52d5d2fbff95e7406752be408a2d9a59", + fetch_sources = True, + licenses = ["notice"], # Apache 2.0 + deps = [ + "@com_lihaoyi_sourcecode_2_11", + "//external:io_bazel_rules_scala/dependency/scala/scala_library", + ], + server_urls = maven_servers, + ) + + _scala_maven_import_external( + name = "org_scalameta_fastparse_2_11", + artifact = "org.scalameta:fastparse_2.11:1.0.1", + artifact_sha256 = "49ecc30a4b47efc0038099da0c97515cf8f754ea631ea9f9935b36ca7d41b733", + srcjar_sha256 = "9769781eeb2980be3379c2cf6aced31f60ad32fe903a830687185e9ffc223d84", + fetch_sources = True, + licenses = ["notice"], # Apache 2.0 + deps = [ + "@com_lihaoyi_sourcecode_2_11", + "//external:io_bazel_rules_scala/dependency/scala/scala_library", + "@org_scalameta_fastparse_utils_2_11", + ], + server_urls = maven_servers, + ) + + _scala_maven_import_external( + name = "org_scalameta_fastparse_utils_2_11", + artifact = "org.scalameta:fastparse-utils_2.11:1.0.1", + artifact_sha256 = "93f58db540e53178a686621f7a9c401307a529b68e051e38804394a2a86cea94", + srcjar_sha256 = "f1c28e408d8309f6f96ea9ed0f2e0bc5661fb84d3730d1bde06207a93a3d091b", + fetch_sources = True, + licenses = ["notice"], # Apache 2.0 + deps = [ + "@com_lihaoyi_sourcecode_2_11", + "//external:io_bazel_rules_scala/dependency/scala/scala_library", + ], + server_urls = maven_servers, + ) + + _scala_maven_import_external( + name = "org_scalameta_parsers_2_11", + artifact = "org.scalameta:parsers_2.11:4.2.0", + artifact_sha256 = "acde4faa648c61f1d76f7a1152116738c0b0b80ae2fab8ceae83c061c29aadf1", + srcjar_sha256 = "212e7e9070c80fbba4680f6d0355036b6486189289515a52b013d36c6070ff1a", + fetch_sources = True, + licenses = ["notice"], # Apache 2.0 + deps = [ + "//external:io_bazel_rules_scala/dependency/scala/scala_library", + "@org_scalameta_trees_2_11", + ], + server_urls = maven_servers, + ) + + _scala_maven_import_external( + name = "org_scalameta_scalafmt_core_2_11", + artifact = "org.scalameta:scalafmt-core_2.11:2.0.0", + artifact_sha256 = "84bac5ed8c85e61851ef427f045b7bfd149d857cb543b41c85b8353fb8c47aff", + srcjar_sha256 = "6ea863beba530b8eabbb648143387f5ccba6299d0fc331046ab30dc493c984ca", + fetch_sources = True, + licenses = ["notice"], # Apache 2.0 + deps = [ + "@com_geirsson_metaconfig_core_2_11", + "@com_geirsson_metaconfig_typesafe_config_2_11", + "//external:io_bazel_rules_scala/dependency/scala/scala_library", + "//external:io_bazel_rules_scala/dependency/scala/scala_reflect", + "@org_scalameta_scalameta_2_11", + ], + server_urls = maven_servers, + ) + + _scala_maven_import_external( + name = "org_scalameta_scalameta_2_11", + artifact = "org.scalameta:scalameta_2.11:4.2.0", + artifact_sha256 = "b56038c03fcad7397c571fbbc44562a3231e275aedc7a6ad15163ddcdfaed61a", + srcjar_sha256 = "fa57cb1b1800dee3dee970250012ebf1f95b7a3f6ee7b26c7d3dd0df971ac1cb", + fetch_sources = True, + licenses = ["notice"], # Apache 2.0 + deps = [ + "//external:io_bazel_rules_scala/dependency/scala/scala_library", + "@org_scala_lang_scalap", + "@org_scalameta_parsers_2_11", + ], + server_urls = maven_servers, + ) + + _scala_maven_import_external( + name = "org_scalameta_trees_2_11", + artifact = "org.scalameta:trees_2.11:4.2.0", + artifact_sha256 = "7daf84bd9a66257e42900ac940bd6df2037f09d33ca93e619ee37377d10ee34a", + srcjar_sha256 = "13a8be06f350100754cff19c776b2d03b954677b816c0e4888752845480be358", + fetch_sources = True, + licenses = ["notice"], # Apache 2.0 + deps = [ + "@com_thesamet_scalapb_scalapb_runtime_2_11", + "//external:io_bazel_rules_scala/dependency/scala/scala_library", + "@org_scalameta_common_2_11", + "@org_scalameta_fastparse_2_11", + ], + server_urls = maven_servers, + ) + + _scala_maven_import_external( + name = "org_typelevel_paiges_core_2_11", + artifact = "org.typelevel:paiges-core_2.11:0.2.0", + artifact_sha256 = "dec1b60448c9ac7bd4a55a4fcf68e0ce6e202d0fad1a896d4501c3ebd8052b2d", + srcjar_sha256 = "952fd039b04e06cc52daf2f8232e5b3adc2009bf287a688e4c82edf3cc37d3d0", + fetch_sources = True, + licenses = ["notice"], # Apache 2.0 + deps = [ + "//external:io_bazel_rules_scala/dependency/scala/scala_library", + ], + server_urls = maven_servers, + ) + + _scala_maven_import_external( + name = "com_typesafe_config", + artifact = "com.typesafe:config:1.3.3", + artifact_sha256 = "b5f1d6071f1548d05be82f59f9039c7d37a1787bd8e3c677e31ee275af4a4621", + srcjar_sha256 = "fcd7c3070417c776b313cc559665c035d74e3a2b40a89bbb0e9bff6e567c9cc8", + fetch_sources = True, + licenses = ["notice"], # Apache 2.0 + server_urls = maven_servers, + ) + + _scala_maven_import_external( + name = "org_scala_lang_scalap", + artifact = "org.scala-lang:scalap:2.11.12", + artifact_sha256 = "a6dd7203ce4af9d6185023d5dba9993eb8e80584ff4b1f6dec574a2aba4cd2b7", + srcjar_sha256 = "50df9e4b4c996cda761f35cbc603cf145a21157a72a82d023d44b0eeeb293e29", + fetch_sources = True, + licenses = ["notice"], # Apache 2.0 + deps = [ + "@io_bazel_rules_scala_scala_compiler", + ], + server_urls = maven_servers, + ) + + _scala_maven_import_external( + name = "com_thesamet_scalapb_lenses_2_11", + artifact = "com.thesamet.scalapb:lenses_2.11:0.9.0", + artifact_sha256 = "f4809760edee6abc97a7fe9b7fd6ae5fe1006795b1dc3963ab4e317a72f1a385", + srcjar_sha256 = "57753e022b607b63a4f60a4570fbd1e524ffc76f389a444033cdea5e83424402", + fetch_sources = True, + licenses = ["notice"], # Apache 2.0 + deps = [ + "//external:io_bazel_rules_scala/dependency/scala/scala_library", + ], + server_urls = maven_servers, + ) + + _scala_maven_import_external( + name = "com_thesamet_scalapb_scalapb_runtime_2_11", + artifact = "com.thesamet.scalapb:scalapb-runtime_2.11:0.9.0", + artifact_sha256 = "ab1e449a18a9ce411eb3fec31bdbca5dd5fae4475b1557bb5e235a7b54738757", + srcjar_sha256 = "41d3c78eac7f4cadc9a785539a29730aa78432b110975ab857656804b4ca0344", + fetch_sources = True, + licenses = ["notice"], # Apache 2.0 + deps = [ + "@com_google_protobuf_protobuf_java", + "@com_lihaoyi_fastparse_2_11", + "@com_thesamet_scalapb_lenses_2_11", + "//external:io_bazel_rules_scala/dependency/scala/scala_library", + ], + server_urls = maven_servers, + ) + + _scala_maven_import_external( + name = "com_lihaoyi_fansi_2_11", + artifact = "com.lihaoyi:fansi_2.11:0.2.5", + artifact_sha256 = "1ff0a8304f322c1442e6bcf28fab07abf3cf560dd24573dbe671249aee5fc488", + srcjar_sha256 = "960df264aac81442d68bfdee9385a8af3e38e979bd568a3f9477de9e978f6d24", + fetch_sources = True, + licenses = ["notice"], # Apache 2.0 + deps = [ + "@com_lihaoyi_sourcecode_2_11", + "//external:io_bazel_rules_scala/dependency/scala/scala_library", + ], + server_urls = maven_servers, + ) + + _scala_maven_import_external( + name = "com_lihaoyi_fastparse_2_11", + artifact = "com.lihaoyi:fastparse_2.11:2.1.2", + artifact_sha256 = "5c5d81f90ada03ac5b21b161864a52558133951031ee5f6bf4d979e8baa03628", + srcjar_sha256 = "1f4509b44b9de440400b949993277d22440033bf42425f93f5b13b94dc41f63b", + fetch_sources = True, + licenses = ["notice"], # Apache 2.0 + deps = [ + "@com_lihaoyi_sourcecode_2_11", + ], + server_urls = maven_servers, + ) + + _scala_maven_import_external( + name = "com_lihaoyi_pprint_2_11", + artifact = "com.lihaoyi:pprint_2.11:0.5.3", + artifact_sha256 = "fb5e4921e7dff734d049e752a482d3a031380d3eea5caa76c991312dee9e6991", + srcjar_sha256 = "99bf854d4e5495161ec9bc6d3bb8d0c274973c0f7c607151c4848c1f85b6c82e", + fetch_sources = True, + licenses = ["notice"], # Apache 2.0 + deps = [ + "@com_lihaoyi_fansi_2_11", + "@com_lihaoyi_sourcecode_2_11", + "//external:io_bazel_rules_scala/dependency/scala/scala_library", + ], + server_urls = maven_servers, + ) + + _scala_maven_import_external( + name = "com_lihaoyi_sourcecode_2_11", + artifact = "com.lihaoyi:sourcecode_2.11:0.1.4", + artifact_sha256 = "e0edffec93ddef29c40b7c65580960062a3fa9d781eddb8c64e19e707c4a8e7c", + srcjar_sha256 = "b6a282beaca27092692197c017cbd349dccf526100af1bbd7f78cf462219f7f9", + fetch_sources = True, + licenses = ["notice"], # Apache 2.0 + deps = [ + "//external:io_bazel_rules_scala/dependency/scala/scala_library", + ], + server_urls = maven_servers, + ) + + _scala_maven_import_external( + name = "com_google_protobuf_protobuf_java", + artifact = "com.google.protobuf:protobuf-java:3.10.0", + artifact_sha256 = "161d7d61a8cb3970891c299578702fd079646e032329d6c2cabf998d191437c9", + srcjar_sha256 = "47012b36fcd7c4325e07a3a3b43c72e1b2d7a7d79d8e2605f2327b1e81348133", + fetch_sources = True, + licenses = ["notice"], # Apache 2.0 + server_urls = maven_servers, + ) + + _scala_maven_import_external( + name = "com_geirsson_metaconfig_core_2_11", + artifact = "com.geirsson:metaconfig-core_2.11:0.8.3", + artifact_sha256 = "8abb4e48507486d0b323b440bb021bddd56366e502002025fdaf10025d2650c2", + srcjar_sha256 = "5a4a2e1ec9153f79ae2747172b1ec4596b8a7788c7ac7bc3aed2b698dc0594a1", + fetch_sources = True, + licenses = ["notice"], # Apache 2.0 + deps = [ + "@com_lihaoyi_pprint_2_11", + "//external:io_bazel_rules_scala/dependency/scala/scala_library", + "@org_typelevel_paiges_core_2_11", + ], + server_urls = maven_servers, + ) + + _scala_maven_import_external( + name = "com_geirsson_metaconfig_typesafe_config_2_11", + artifact = "com.geirsson:metaconfig-typesafe-config_2.11:0.8.3", + artifact_sha256 = "410c29b2ebc842591627588d8980df507dc0eb48a0a7df312fa529fa0fe90d42", + srcjar_sha256 = "494109a773bb4470cb2f2b31831eb7582acbf30e1f0db3a88362f69ebd55a854", + fetch_sources = True, + licenses = ["notice"], # Apache 2.0 + deps = [ + "@com_geirsson_metaconfig_core_2_11", + "@com_typesafe_config", + "//external:io_bazel_rules_scala/dependency/scala/scala_library", + ], + server_urls = maven_servers, + ) diff --git a/test/scalafmt/.scalafmt.conf b/test/scalafmt/.scalafmt.conf new file mode 100644 index 0000000000..46b439c07e --- /dev/null +++ b/test/scalafmt/.scalafmt.conf @@ -0,0 +1 @@ +maxColumn = 40 diff --git a/test/scalafmt/BUILD b/test/scalafmt/BUILD new file mode 100644 index 0000000000..696bc43233 --- /dev/null +++ b/test/scalafmt/BUILD @@ -0,0 +1,68 @@ +load( + "//test/scalafmt:phase_scalafmt_test.bzl", + "scalafmt_scala_binary", + "scalafmt_scala_library", + "scalafmt_scala_test", +) + +filegroup( + name = "custom-conf", + srcs = [".scalafmt.conf"], + visibility = ["//visibility:public"], +) + +### scala_binary ### +scalafmt_scala_binary( + name = "formatted-binary", + srcs = ["formatted/formatted-encoding.scala"], + format = True, + main_class = "scalarules.test.scalafmt.Format", +) + +scalafmt_scala_binary( + name = "unformatted-binary", + srcs = ["unformatted/unformatted-encoding.scala"], + format = True, + main_class = "scalarules.test.scalafmt.Format", +) + +### scala_library ### +scalafmt_scala_library( + name = "formatted-library", + srcs = ["formatted/formatted-encoding.scala"], + format = True, +) + +scalafmt_scala_library( + name = "unformatted-library", + srcs = ["unformatted/unformatted-encoding.scala"], + format = True, +) + +### scala_test ### +scalafmt_scala_test( + name = "formatted-test", + srcs = ["formatted/formatted-test.scala"], + format = True, +) + +scalafmt_scala_test( + name = "unformatted-test", + srcs = ["unformatted/unformatted-test.scala"], + format = True, +) + +### custom config ### +scalafmt_scala_library( + name = "formatted-custom-conf", + srcs = ["formatted/formatted-custom-conf.scala"], + config = ":custom-conf", + format = True, +) + +scalafmt_scala_library( + name = "unformatted-custom-conf", + srcs = ["unformatted/unformatted-custom-conf.scala"], + config = ":custom-conf", + format = True, +) diff --git a/test/scalafmt/formatted/formatted-custom-conf.scala b/test/scalafmt/formatted/formatted-custom-conf.scala new file mode 100644 index 0000000000..9a237e6924 --- /dev/null +++ b/test/scalafmt/formatted/formatted-custom-conf.scala @@ -0,0 +1,7 @@ +package scalarules.test.scalafmt +object Format { + def main(args: Array[String]) { + val warnings: String = + "Be careful with this test. The column number is limited to 40, so it should be in new line." + } +} diff --git a/test/scalafmt/formatted/formatted-encoding.scala b/test/scalafmt/formatted/formatted-encoding.scala new file mode 100644 index 0000000000..9a5192a357 --- /dev/null +++ b/test/scalafmt/formatted/formatted-encoding.scala @@ -0,0 +1,12 @@ +package scalarules.test.scalafmt +object Format { + def main(args: Array[String]) { + val warnings: List[String] = List( + "Be careful with this test", + "小心這個測試", + "このテストに注意してください", + "이 시험에 조심하십시오", + "😁✊🚀🍟💯" + ) + } +} diff --git a/test/scalafmt/formatted/formatted-test.scala b/test/scalafmt/formatted/formatted-test.scala new file mode 100644 index 0000000000..929e655f45 --- /dev/null +++ b/test/scalafmt/formatted/formatted-test.scala @@ -0,0 +1,7 @@ +package scalarules.test.scalafmt +import org.scalatest._ +class FormatTest extends FlatSpec { + "FormatTest" should "be formatted" in { + assert(true) + } +} diff --git a/test/scalafmt/phase_scalafmt_test.bzl b/test/scalafmt/phase_scalafmt_test.bzl new file mode 100644 index 0000000000..ed5e46602a --- /dev/null +++ b/test/scalafmt/phase_scalafmt_test.bzl @@ -0,0 +1,16 @@ +load( + "//scala:advanced_usage/scala.bzl", + "make_scala_binary", + "make_scala_library", + "make_scala_test", +) +load( + "//scala/scalafmt:phase_scalafmt_ext.bzl", + "ext_scalafmt", +) + +scalafmt_scala_binary = make_scala_binary(ext_scalafmt) + +scalafmt_scala_library = make_scala_library(ext_scalafmt) + +scalafmt_scala_test = make_scala_test(ext_scalafmt) diff --git a/test/scalafmt/unformatted/unformatted-custom-conf.scala b/test/scalafmt/unformatted/unformatted-custom-conf.scala new file mode 100644 index 0000000000..46a3dd4201 --- /dev/null +++ b/test/scalafmt/unformatted/unformatted-custom-conf.scala @@ -0,0 +1,6 @@ +package scalarules.test.scalafmt +object Format { + def main(args: Array[String]) { + val warnings: String = "Be careful with this test. The column number is limited to 40, so it should be in new line." + } +} diff --git a/test/scalafmt/unformatted/unformatted-encoding.scala b/test/scalafmt/unformatted/unformatted-encoding.scala new file mode 100644 index 0000000000..57cc66d0ad --- /dev/null +++ b/test/scalafmt/unformatted/unformatted-encoding.scala @@ -0,0 +1,15 @@ + package scalarules.test.scalafmt + object Format { + def main ( args: + + + Array [String ]) { + val warnings : List [ String ] = List( + "Be careful with this test", + "小心這個測試", + "このテストに注意してください", + "이 시험에 조심하십시오", + "😁✊🚀🍟💯" + ) + } +} diff --git a/test/scalafmt/unformatted/unformatted-test.scala b/test/scalafmt/unformatted/unformatted-test.scala new file mode 100644 index 0000000000..7a696cc482 --- /dev/null +++ b/test/scalafmt/unformatted/unformatted-test.scala @@ -0,0 +1,7 @@ + package scalarules.test.scalafmt + import org.scalatest._ + class FormatTest extends FlatSpec { + "FormatTest" should "be formatted" in { + assert ( true ) + } +} diff --git a/test/shell/test_scalafmt.sh b/test/shell/test_scalafmt.sh new file mode 100755 index 0000000000..03db1a8959 --- /dev/null +++ b/test/shell/test_scalafmt.sh @@ -0,0 +1,75 @@ +# shellcheck source=./test_runner.sh +dir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +. "${dir}"/test_runner.sh +. "${dir}"/test_helper.sh +runner=$(get_test_runner "${1:-local}") + +backup_unformatted() { + FILE_PATH=$1 + FILENAME=$2 + cp $FILE_PATH/unformatted/unformatted-$FILENAME.scala $FILE_PATH/unformatted/unformatted-$FILENAME.backup.scala +} + +restore_unformatted_before_exit() { + FILE_PATH=$1 + FILENAME=$2 + cp $FILE_PATH/unformatted/unformatted-$FILENAME.backup.scala $FILE_PATH/unformatted/unformatted-$FILENAME.scala + rm -f $FILE_PATH/unformatted/unformatted-$FILENAME.backup.scala +} + +run_formatting() { + set +e + + FILE_PATH="$( dirname $( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) )"/scalafmt + RULE_TYPE=$1 + FILENAME=$2 + + bazel run //test/scalafmt:formatted-$RULE_TYPE.format-test + if [ $? -ne 0 ]; then + echo -e "${RED} formatted-$RULE_TYPE.format-test should be a formatted target. $NC" + exit 1 + fi + + bazel run //test/scalafmt:unformatted-$RULE_TYPE.format-test + if [ $? -eq 0 ]; then + echo -e "${RED} unformatted-$RULE_TYPE.format-test should be an unformatted target. $NC" + exit 1 + fi + + backup_unformatted $FILE_PATH $FILENAME + # format unformatted*.scala + bazel run //test/scalafmt:unformatted-$RULE_TYPE.format + if [ $? -ne 0 ]; then + echo -e "${RED} unformatted-$RULE_TYPE.format should run formatting. $NC" + restore_unformatted_before_exit $FILE_PATH $FILENAME + exit 1 + fi + + diff $FILE_PATH/unformatted/unformatted-$FILENAME.scala $FILE_PATH/formatted/formatted-$FILENAME.scala + if [ $? -ne 0 ]; then + echo -e "${RED} unformatted-$FILENAME.scala should be the same as formatted-$FILENAME.scala after formatting. $NC" + restore_unformatted_before_exit $FILE_PATH $FILENAME + exit 1 + fi + restore_unformatted_before_exit $FILE_PATH $FILENAME +} + +test_scalafmt_binary() { + run_formatting binary encoding +} + +test_scalafmt_library() { + run_formatting library encoding +} + +test_scalafmt_test() { + run_formatting test test +} +test_custom_conf() { + run_formatting custom-conf custom-conf +} + +$runner test_scalafmt_binary +$runner test_scalafmt_library +$runner test_scalafmt_test +$runner test_custom_conf diff --git a/test_rules_scala.sh b/test_rules_scala.sh index e8456fabfe..c57f626610 100755 --- a/test_rules_scala.sh +++ b/test_rules_scala.sh @@ -30,6 +30,7 @@ $runner bazel test //test/... --extra_toolchains="//test_expect_failure/plus_one . "${test_dir}"/test_junit.sh . "${test_dir}"/test_misc.sh . "${test_dir}"/test_phase.sh +. "${test_dir}"/test_scalafmt.sh . "${test_dir}"/test_scala_binary.sh . "${test_dir}"/test_scalac_jvm_flags.sh . "${test_dir}"/test_scala_classpath.sh