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 lint-v2 rule and implement it for Python with black. #8452

Conversation

pierrechevalier83
Copy link
Contributor

@pierrechevalier83 pierrechevalier83 commented Oct 11, 2019

Problem

In the near future, we will want to add a pre-commit hook that helps us remember to automatically format our code with black.
Currently, the version of black that's integrated in pants will always modify the user's code, which may not be desirable in a pre_commit hook situation.

Solution

Add a lint-v2 rule which uses black with the --check flag to highlight unformatted files without actually modifying them.

Result

See integration tests for examples: User can call ./pants lint-v2 .... The command will fail if the files would be formatted, but won't rewrite the files.

Copy link
Contributor

@illicitonion illicitonion left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@@ -16,3 +16,5 @@ def register_options(cls, register):
super().register_options(register)
register('--config', advanced=True, type=file_option, fingerprint=True,
help="Path to formatting tool's config file")
register('--check', advanced=True, type=bool, fingerprint=True,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Long-term, there's an interesting question of direction here.

Historically, we had the lint goal, which is basically fmt-without-formatting - in the future, would we like:

  1. A lint goal alongside the fmt goal?
  2. A fmt-wide flag called something like --check
  3. A per-formatter flag called --check

I suspect long-term we want 1 or 2, and we should work that out before we name the v2 console_rule fmt, but while this is experimental until then, this works :)

Copy link
Member

@stuhood stuhood left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pattern so far has been to have ./pants lint check for things like this, rather than to have ./pants fmt with whitelisted options.

The reasoning for this is that ./pants lint "never has sideeffects", while ./pants fmt obviously might. And as more side-effecting tasks are added to fmt, the chances of forgetting to add one of these "disable sideffects" options increases.

So as with lint/fmt in v1, would recommend using the existing "reformat this code" logic under a new lint-v2 rule (or under

# TODO: Switch this to `lint` once we figure out a good way for v1 tasks and v2 rules
# to share goal names.
@console_rule
def validate(
console: Console, hydrated_targets: HydratedTargets, validate_options: Validate.Options
) -> Validate:
), but then adding an intermediate @rule comparing the resulting digest to the target's input digest to see whether the lint failed.

@pierrechevalier83 pierrechevalier83 force-pushed the pchevalier/black_check branch 2 times, most recently from f4f2091 to 82788a9 Compare October 16, 2019 11:22
@pierrechevalier83 pierrechevalier83 changed the title Add --check argument to the black subsystem. Add lint-v2 rule and implement it for Python with black. Oct 16, 2019
@illicitonion
Copy link
Contributor

The code here looks good to me, but I'm still unsure of the long-term direction; I think that ignoring history, I probably would design these goals as fmt an fmt --check rather than fmt and lint - maybe this isn't where we want to make that change, but I think it's worth a discussion.

@stuhood stuhood requested a review from benjyw October 16, 2019 15:52
@stuhood
Copy link
Member

stuhood commented Oct 16, 2019

The code here looks good to me, but I'm still unsure of the long-term direction; I think that ignoring history, I probably would design these goals as fmt an fmt --check rather than fmt and lint - maybe this isn't where we want to make that change, but I think it's worth a discussion.

The issue with ./pants fmt --check is that you probably also want to check other lints that are not formatting related. So then you need to run ./pants fmt --check lint. Maybe that's fine, and the distinction we draw is that only autoformatters go in fmt, and that the style checking that they represent is sufficiently distinct from other lints to be worth having split out?

My feeling though is that all realistic cases will want to run "all of the lints" (assuming that they are fast enough).

Copy link
Member

@stuhood stuhood left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks.

But please wait for Benjy to chime in on the fmt --check vs lint distinction.

@benjyw
Copy link
Contributor

benjyw commented Oct 17, 2019

Thanks - code looks good. After some thought, I come down on the side of putting this in a lint goal. For the reason Stu stated - we may have other linters that are not tied to formatters. And it makes more sense to run ./pants lint than ./pants lint fmt --check in order to get all the lint checks.

Another way to think of it is that "formatter in check mode" is an implementation detail of a lint rule.

@Eric-Arellano
Copy link
Contributor

+1 to this being a lint goal.

(Haven’t checked the code at all.)

Copy link
Contributor

@illicitonion illicitonion left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Two minor things :)

results = yield [
Get(LintResult, FmtTarget, target.adaptor)
for target in targets
# @union assumes that all targets passed implement the union, so we manually
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #8368 - you can now inject a UnionMembership to the rule, and call union_membership.is_member(FmtTarget, target.adaptor)

(At some point we'll probably want to also create a LintTarget Union, but piggybacking off of FmtTarget is probably sufficient until we either add a non-fmt lint, or rename this goal to be lint instead of lint-v2)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will address in a separate PR to hopefully get this one merged today (I'm also dragging #8454 which is based on top of this, so would be keen on iterating fast if possible)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done here: #8490

if result.stderr:
console.print_stderr(result.stderr)
if result.exit_code != 0:
yield Lint(result.exit_code)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A console rule should only yield an output exactly one time.

Can you make your test_black_lint_should_fail_but_not_format_python_code test lint two incorrectly-formatted things? You should then see an error (the fix for which is probably to accumulate 0 or last-non-0 exit code, and yield that at the end)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a couple of tests: lint multiple files and lint multiple targets. With the latter, I could reproduce the issue you're describing (we don't report all failing targets). I fixed it as you described.

@pierrechevalier83
Copy link
Contributor Author

Just a comment to be explicit: the reasoning for lint vs fmt --check makes sense to me. I prepped the lint version of the PR for merging (rebased + addressed review comments)

This is a prerequisite for ultimately adding a pre-commit hook that
behaves similarly to our isort pre-commit hook (warns the user and lets
them actually modify their code once they are ready for it)

* Create a top level `lint-v2` rule;
* Implement `lint_with_black` which implements that rule for python;
* Deduplicate `lint_with_black` with `fmt_with_black`;
* Make use of `FallibleExecuteProcessRequest` to make lint output less
  spammy.
Now we introduced a lint goal which takes the same kind of targets as an
input, it makes sense to use a more general name.
@pierrechevalier83
Copy link
Contributor Author

The fight against flakey CI has been too intense. I'm reducing from 3 PRs to one to increase my likelihood of ever getting my code merged.
Closing in favour of #8553 which contains #8452, #8454 and #8490

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants