Skip to content

Commit

Permalink
scala: add scalafmt support for fmt and lint goals (pantsbuild#…
Browse files Browse the repository at this point in the history
…13814)

Add the `scalafmt` tool for `fmt` and `lint` goals.

Pants will choose the nearest ancestor `.scalafmt.conf` file as the configuration file to use for a source file. This differs from the logic used by `scalafmt` which only chooses between a `.scalafmt.conf` in the same directory as a file or one in the root of the repository. This should provide a better experience for multi-team monorepos where it could be useful to have a `.scalafmt.conf` that applies to a subtree of the repository.

Closes pantsbuild#11943.
  • Loading branch information
Tom Dyas authored Dec 7, 2021
1 parent 59abe54 commit 1c47a19
Show file tree
Hide file tree
Showing 16 changed files with 2,452 additions and 5 deletions.
4 changes: 4 additions & 0 deletions pants.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ backend_packages.add = [
"pants.backend.experimental.java.debug_goals",
"pants.backend.experimental.python",
"pants.backend.experimental.scala",
"pants.backend.experimental.scala.lint.scalafmt",
"pants.backend.experimental.scala.debug_goals",
"internal_plugins.releases",
]
Expand Down Expand Up @@ -179,6 +180,9 @@ lockfile = "src/python/pants/backend/java/test/junit.default.lockfile.txt"
[google-java-format]
lockfile = "src/python/pants/backend/java/lint/google_java_format/google_java_format.default.lockfile.txt"

[scalafmt]
lockfile = "src/python/pants/backend/scala/lint/scalafmt/scalafmt.default.lockfile.txt"

[toolchain-setup]
repo = "pants"

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

python_sources()
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).
from pants.backend.experimental.scala.register import rules as all_scala_rules
from pants.backend.scala.lint import scala_lang_fmt
from pants.backend.scala.lint.scalafmt import rules as scalafmt_rules
from pants.backend.scala.lint.scalafmt import skip_field


def rules():
return [
*all_scala_rules(),
*scala_lang_fmt.rules(),
*scalafmt_rules.rules(),
*skip_field.rules(),
]
Original file line number Diff line number Diff line change
Expand Up @@ -96,15 +96,11 @@ async def setup_google_java_format(
"--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
]

maybe_aosp_option = []
if tool.aosp:
maybe_aosp_option = ["--aosp"]

args = [
*jdk_setup.args(bash, tool_classpath.classpath_entries()),
*maybe_java16_or_higher_options,
"com.google.googlejavaformat.java.Main",
*maybe_aosp_option,
*(["--aosp"] if tool.aosp else []),
"--dry-run" if setup_request.check_only else "--replace",
*source_files.files,
]
Expand Down
2 changes: 2 additions & 0 deletions src/python/pants/backend/scala/dependency_inference/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ python_tests(name="tests", timeout=240)
scala_sources(
name="scala_parser",
compatible_resolves=["scala_parser"],
# TODO: Allow the parser files to be formatted.
skip_scalafmt=True,
)
4 changes: 4 additions & 0 deletions src/python/pants/backend/scala/lint/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

python_sources()
Empty file.
61 changes: 61 additions & 0 deletions src/python/pants/backend/scala/lint/scala_lang_fmt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).
from dataclasses import dataclass
from typing import Iterable

from pants.backend.scala.target_types import ScalaSourceField
from pants.core.goals.fmt import FmtResult, LanguageFmtResults, LanguageFmtTargets
from pants.core.goals.style_request import StyleRequest
from pants.core.util_rules.source_files import SourceFiles, SourceFilesRequest
from pants.engine.fs import Digest, Snapshot
from pants.engine.internals.selectors import Get
from pants.engine.rules import collect_rules, rule
from pants.engine.unions import UnionMembership, UnionRule, union


@dataclass(frozen=True)
class ScalaLangFmtTargets(LanguageFmtTargets):
required_fields = (ScalaSourceField,)


@union
class ScalaLangFmtRequest(StyleRequest):
pass


@rule
async def format_scala_target(
scala_fmt_targets: ScalaLangFmtTargets, union_membership: UnionMembership
) -> LanguageFmtResults:
original_sources = await Get(
SourceFiles,
SourceFilesRequest(target[ScalaSourceField] for target in scala_fmt_targets.targets),
)
prior_formatter_result = original_sources.snapshot

results = []
fmt_request_types: Iterable[type[StyleRequest]] = union_membership[ScalaLangFmtRequest]
for fmt_request_type in fmt_request_types:
request = fmt_request_type(
(
fmt_request_type.field_set_type.create(target)
for target in scala_fmt_targets.targets
if fmt_request_type.field_set_type.is_applicable(target)
),
prior_formatter_result=prior_formatter_result,
)
if not request.field_sets:
continue
result = await Get(FmtResult, ScalaLangFmtRequest, request)
results.append(result)
if result.did_change:
prior_formatter_result = await Get(Snapshot, Digest, result.output)
return LanguageFmtResults(
tuple(results),
input=original_sources.snapshot.digest,
output=prior_formatter_result.digest,
)


def rules():
return [*collect_rules(), UnionRule(LanguageFmtTargets, ScalaLangFmtTargets)]
8 changes: 8 additions & 0 deletions src/python/pants/backend/scala/lint/scalafmt/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

python_sources(dependencies=[":lockfile"])

python_tests(name="tests", timeout=360)

resource(name="lockfile", source="scalafmt.default.lockfile.txt")
Empty file.
Loading

0 comments on commit 1c47a19

Please sign in to comment.