Conversation
|
Without diving into the details too much, I like the idea of "if your tool fits this description, you can use this turnkey API". Not all tools will fit, but certainly some would (even ones we have internally). Permit me some time to stew on it more, but I think the overall is a ➕ from me. |
|
Without digging into the code too much (details are less important to me right now), this is a +1 from me. I'm a fan of making plugins/APIs easy, even at the expense of technical overhead. Something of this ilk would definitely strip out a lot of code from the codebase, possibly at some extra config expense. I like the idea that these linters/formatters are more about their subsystems, rather than a decent chunk of copied/pasted rule code. Maybe from a global meta-class, maybe from a per-backend meta-linter. And if the meta-class wouldn't work well - then you fall back to exactly how it is today anyways. Alternatively, maybe some of this functionality could come in the form of additional composable rules/rule_helpers? Haven't thought this through yet. As I see it, most of the linters/formatters that I use or have worked on come down to:
Most of that feels boilerplate-y since the inputs and outputs are pretty well-defined... Being able to specify those in the Fmt/Lint subsystem seems perfectly reasonable. Some considerations:
In general, I'd love to see and use this in mainline! @lilatomic In the existing repo, do we have non-anecdotal numbers on how much boilerplate something like this would save? Or some concept of which tools it would work out of the box for, against how many would have to go custom anyways? Edit: Forgot to mention, there probably even is some world where the |
# Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels]
# Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels]
# Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels]
# Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels]
# Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels]
# Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels]
# Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels]
# Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels]
since the tool is most likely the most complicated aspect of this, it makes sense to have people fill in the options themselves. # Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels]
we'll want to be able to have users construct these themselves, so they can include their tool's arguments as configured # Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels]
# Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels]
# Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels]
I'm not sure this is necessary # Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels]
# Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels]
currently only works if the tool is created once # Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels]
since the rules created are equal by identity, Pants deduplicates them # Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels]
It's pretty gross, TBH. I couldn't find a good way to just turn it on without providing an actual lockfile. # Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels]
# Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels]
# Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels]
# Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels]
# Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels]
also not be copypaste # Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels]
|
So, doing a bit of an overview of the applicability for boilerplate reduction:
generating export (searched ExportPythonTool):
assessing the applicability of the main generated lint rule ( |
|
Relating to integration testing, that's really interesting. I wonder if the real boilerplate is in the tests, rather than the code. Obviously, any tool defined through metalint has that boilerplate tested. But if it can't be defined by metalint, we could at least run boilerplate tests. The example that comes to mind is that the "skip" flag is honoured. (Although, perhaps that's an indication that we need more rule helpers or higher-order rules to handle that behaviour). On the subject of non-python-tools, I think there are basically 4 types of tools that we could easily serve with this:
|
73ceaf6 to
603ce67
Compare
| @rule(level=LogLevel.DEBUG) | ||
| def metalint_export( | ||
| _: MetalintExportSentinel, | ||
| tool: python_tool, # type: ignore[valid-type] # python_tool: Type[MetalintTool] |
There was a problem hiding this comment.
Does this work? I'm trying something similar and the rule engine is mad at me
|
I think the existing boilerplate reductions are good enough to close this |
I was looking at one of my old projects. It was from before I discovered Pants, so it uses Tox. I was reminded that it is incredibly easy to just run a python package in tox:
I thought it would be great if it were that easy to create a linter in Pants. So I hacked together this prototype!
now it's as simple as
This MR follows (reasonably closely) the Add a linter common task walkthrough.
Initial discussion questions:
Understanding this MR
register.py contains 3 example linters. 2 of them share the same tool, which produces some surprising results but actually works.
MetalintToolextendsPythonToolBasewith the fields that make the expected actions (in particular, export and generate-lockfiles) go. This class is 1 part of the basic interface of Metalint. The other component ismake_linter, which creates all the classes and the rules and shoves them all together into theMetalintDTO.Some of the helpers could also be useful on their own. For example,
make_export_rulescan dynamically generate the boilerplate for the export task.What is even happening
MetalintTooladds all the fields to make things go.Metalintis a DTO, convenient for gathering all the rulesmake_linteris the real function here. It accepts a MetalintTool class and anargv_maker(which enables the caller to format the invocation arguments as required. This allows passing the right flags and formatting all the filenames, if needed). It optionally accepts theFieldSetandLintTargetsRequest, as escape hatches. IDK, until Mypy ruined the fun it was an easy include.make_export_rulesandmake_lockfile_rulesuse a cache to ensure that a tool used in multiple linters only has 1 set of rules generated for it. (It looks like Pants deduplicates rules if they are equal by identity.)Otherwise it's fairly standard dynamic class generation. The classes themselves are not equal by identity because they are created as variables inside the function, so each invocation of the function creates a new class. It uses a function instead of a class because the classes have dependencies on each other; but all the statements in a class have to be mostly valid on their own, so they can't depend on each other like this (at least AFAIK).
Known limitations and future work
Type[T]as a valid type annotation LMK.