astral-sh / ruff

An extremely fast Python linter and code formatter, written in Rust.
https://docs.astral.sh/ruff
MIT License
30.73k stars 1.02k forks source link

Implement Pylint #970

Open charliermarsh opened 1 year ago

charliermarsh commented 1 year ago

This is the parent issue for tracking parity with Pylint. Below, we've enumerated all Pylint rules.

Rules that are checked off have been implemented in Ruff already (either as a Pylint rule, e.g., PLE0237, or by way of overlap with another linter like Pyflakes, as with missing-format-string-key).

Rules that are crossed out have been removed some consideration. (In most cases, crossed-out rules represent Pylint specific rules, e.g., rules related to Pylint configuration.)

At time of writing, many of the remaining rules require type inference and/or multi-file analysis, and aren't ready to be implemented in Ruff. (See: https://github.com/astral-sh/ruff/issues/970#issuecomment-1565594417 for an enumeration.) If you're looking to start work on a specific rule, I'd suggest commenting in the issue, to get some input on whether Ruff is capable of supporting it at present.

For guidance on getting started, see the Contributing documentation.

Note: Don't implement rules that are part of Pylint extension until #1774 is completed. Don't implement rules that mainly target Python 2.

Error

Warning

Convention

Refactor

chanman3388 commented 1 year ago

Hey folks, Thanks a lot for this awesome project!

I have scrubbed over the list of pylint checks and it looks like (at least) no-name-in-module / E0611 would require leveraging the new ImportMap feature.

Also, producing a proper implementation of no-member / E1101 seems significantly more involved.

Are there any plans to work on these? I wish I could help but I am a bit clueless about rust...

It's something I would like to do after finishing the work on detecting cyclic imports. The ImportMap only shows what a module imports, not exports, but I suppose a similar type of approach would be needed whereby after traversing a module, I guess we could have anExportMap if you will of the names available.

rjarry commented 1 year ago

Chris Chan, Apr 18, 2023 at 14:46:

It's something I would like to do after finishing the work on detecting cyclic imports. The ImportMap only shows what a module imports, not exports, but I suppose a similar type of approach would be needed whereby after traversing a module, I guess we could have an ExportMap if you will of the names available.

Awesome.

I guess it also requires to determine where these imports should be resolved, check if there is a virtualenv, that sort of thing.

How far do you intend to go in code inspection to be able to do type checking? Do you plan on introspecting compiled extensions? How about type stubs (.pyi)?

Pylint relies on astroid to do such things. And I remember having to patch it to support compiled extensions: https://github.com/pylint-dev/astroid/commit/2da60a08de6f146f5dff78db3d01bee10ed375dc. Which (by the way) was easily broken a bit later on: https://github.com/pylint-dev/pylint/issues/7399.

I will gladly help if I can.

chanman3388 commented 1 year ago

@rjarry I have to admit, I wasn't planning on doing it, I don't know what the wider plans are for ruff, I do know that ruff uses RustPython's parser, and to be honest, I don't know how t RustPython works, but if has an analogue to .pyc files, maybe we could. But it would be something I would need to learn/understand in order to do so.

youknowone commented 1 year ago

Can RustPython make a very small interpreter enough to import names? The import rules of python are very complicated and depending on comparably bigger library _imp, _frozen_importlib etc

hmc-cs-mdrissi commented 1 year ago

Performance hit from using real interpreter for just imports can be pretty high especially as some python files run logic at module level (metaclasses/init_subclass/any global code). There are major libraries like tensorflow where just importing them takes several seconds.

sanmai-NL commented 1 year ago

Can we and/or PyLint instead use CPython audit events to record imports? https://docs.python.org/3/library/audit_events.html

gandhis1 commented 1 year ago

Linters don't run code. Any runtime construct is likely not useful here.

sanmai-NL commented 1 year ago

Why do you think that is necessarily true? Have you read https://github.com/charliermarsh/ruff/issues/970#issuecomment-1516569335?

hmc-cs-mdrissi commented 1 year ago

Some linters run code. Various others do not for multiple reasons. In ruff’s case one major core feature is performance. Running real imports can be very slow and for some files could immediately make ruff orders of magnitude slower. Python files are allowed to run whatever code they want at import time and some libraries do non trivial initialization when imported. It may also be mild security concern although most use cases if you lint a file you typically trust content in that file.

pyright is one static analysis tool that avoids almost all imports/runtime execution for those reasons. I think mypy is the same. Some tools do choose to accept trade offs of runtime imports and use them. Given ruff’s focus on performance I don’t think it makes sense for ruff to do them.

dciborow commented 1 year ago

I'm peaking at subprocess-run-check / W1510. It's not a particularly important check, but the fix is pretty simple, to just add a new parameter to the function call check=True. But it get's a little complicated to me if the function is formatted across more than one line. If anyone has any suggestions of any other similar fixes where a parameter is added to a method that I could use as an example, I'd like to take a stab at this simple case.

chanman3388 commented 1 year ago

I'm peaking at subprocess-run-check / W1510. It's not a particularly important check, but the fix is pretty simple, to just add a new parameter to the function call check=True. But it get's a little complicated to me if the function is formatted across more than one line. If anyone has any suggestions of any other similar fixes where a parameter is added to a method that I could use as an example, I'd like to take a stab at this simple case.

I've not tried it, but it should be in the 'args' member of the StmtKind::FunctionDef struct Another issue to consider is that a user might have imported subprocess under an alias, but you should test how pylint deals with it, or even look at the source for pylint to find out how it's done.

thejcannon commented 1 year ago

Pylint uses astroid, so I wouldn't be surprised if it understands aliases pretty well.

That being said, don't let perfect be the enemy of good enough. It isn't something that is usually aliased, and reporting on the simple case is worth it to do now, and improve later.

I don't think @charliermarsh or any of the Astral folks have mentioned how type information (or any information not easily gleamable from an AST) will make it into ruff, so doing the easy thing is likely the first pass at any of these

chanman3388 commented 1 year ago

Pylint uses astroid, so I wouldn't be surprised if it understands aliases pretty well.

That being said, don't let perfect be the enemy of good enough. It isn't something that is usually aliased, and reporting on the simple case is worth it to do now, and improve later.

I don't think @charliermarsh or any of the Astral folks have mentioned how type information (or any information not easily gleamable from an AST) will make it into ruff, so doing the easy thing is likely the first pass at any of these

This is true, but we could track those aliases considering the Import/ImportFrom StmtKind structs have that information, we already track imports per module as it is.

chanman3388 commented 1 year ago

https://github.com/search?q=repo%3Apylint-dev%2Fpylint%20subprocess-run-check&type=code So...pylint just does the basic check.

charliermarsh commented 1 year ago

@chanman3388 - Thankfully we do handle alias tracking and it's largely abstracted away in end-user code. If you grep around for calls to resolve_call_path, this returns the absolute path for an expression, so it handles aliased imports, import-froms, etc. This is what we tend to use across Ruff to check (e.g.) "Is this expression a call to subprocess.check_call?" (Note that this doesn't handle code like import subprocess; foo = subprocess; foo.check_call, we treat those as independent bindings right now; but it does handle code like import subprocess as foo; foo.check_call.)

charliermarsh commented 1 year ago

@dciborow - Sounds like you're mostly wondering how to construct the proper fix, even in the event that the function arguments are split over multiple lines, etc. -- is that right? We have crates/ruff/src/autofix/actions.rs#remove_argument which is similar. You could write an add_or_replace_argument helper with a similar structure.

In general, I think you want to find the last argument in the call, and then insert a comma (if there's no trailing comma), and then insert the argument -- unless the argument is already present in the call, in which case you just want to replace its value. This will require some mix of looking at the AST (to see if the argument is present) and looking the token stream (to see if there's a trailing comma). The method I mentioned above does some of that kind of stuff, as an example.

dciborow commented 1 year ago

@charliermarsh , thanks!

I had been working on adding auto-fixes to our vscode-pylint plugin, (this one got more complicated then my easy regex fixer can handle) but was recommending to check out Ruff, and find that it runs a lot faster inside of VS Code. I also like that I can use rust to auto-fix things outside of VSCode. So, while I have never written Rust before, excited to see what I can figure out.

tusharsadhwani commented 1 year ago

A lot of good, useful Pylint lints require at least some type inference to be useful (even if it's as rudimentary as backtracking through assignments), and that's probably the thing that I'm interested in the most from Ruff.

If such an inference system starts building up, it might give the project momentum to finish through the pylint port. It'd make auto fixes much easier as well.

chanman3388 commented 1 year ago

E0102 seems to be already covered by pyflakes F811?

animalnots commented 1 year ago

no-value-for-parameter / E1120 is what I expected to be implemented, any timelines on this or how I can enable it manually(within ruff)?

Nevermind, It's not covered by Pyflakes rules so probably impossible to implement

tusharsadhwani commented 1 year ago

it's not implemented right now. I'd assume it's fairly hard to implement, at least to the degree that pylint can detect.

Skylion007 commented 1 year ago

Now that E1206, E1205 are implemented, seems like E1306, and E1305 would be good first issues if someone wants to take it up. Can we also tag this issue as good first issue @charliermarsh ? Lots of easy to implement rules here.

alonme commented 1 year ago

i'll take a look at E1306 and E1305

charliermarsh commented 1 year ago

Those specific rules might actually be covered by existing Pyflakes rules. I think E1306 is the same as F522, and E1305 is the same as F524?

alonme commented 1 year ago

@charliermarsh yup - that looks right. should we add anything to the code to make it clear that they also cover the pylint rules?

charliermarsh commented 1 year ago

@alonme - For now, I'll just mark them as completed here and reference those tasks. In the future, we will mark them as aliases in the code, but there's no support for that right now.

alonme commented 1 year ago

cool, taking a look at E0112

EDIT: oh well seems like that is also covered by F622

alonme commented 1 year ago

and i am not sure if E6004 is worth implementing as it is only relevant in python 3.7.0 and 3.7.1, and python 3.7 will be EOL in a month or so

charliermarsh commented 1 year ago

Calling out a few that look like good-first-rules: E1003, E0241, W0130, W0131.

nm-remarkable commented 1 year ago

Unfortunately E1003 is not as easy as it looked due to classes inheriting others.

So PLE1003 will be on-hold until we get a way to track down the inherited parents.

tusharsadhwani commented 1 year ago

@nm-remarkable Hey!

Can you maybe create a separate issue/discussion about all the pylint lints that seem hard to implement? I can collaborate and help create a knowledge base for other collaborators.

Or, we can talk over text or email if that suits better.

hoel-bagard commented 1 year ago

I'll take a look at W0130 and W0131.

Skylion007 commented 1 year ago

W0706 is already implemented as TRY302.

wiwa5606 commented 1 year ago

Hey folks, I'm interested in taking on so of this work. I have a rough draft for PLE1128 already, how would I go about pushing that up for review?

tusharsadhwani commented 1 year ago

@wiwa5606 creating a fork of this repository on your own account and then making a pull request should be good

chanman3388 commented 1 year ago

Hey folks, I'm interested in taking on so of this work. I have a rough draft for PLE1128 already, how would I go about pushing that up for review?

If you have it in a branch on your fork, then GitHub should give you the option to create a pull request. Normally you would do this from your fork. You can take a look at the merged issues higher up for what these look like. I hope that answers your question!

wiwa5606 commented 1 year ago

Thanks, that helps! Raised https://github.com/charliermarsh/ruff/pull/4532

Skylion007 commented 1 year ago

W0707 is an alias for TRY200

Skylion007 commented 1 year ago

W1201is also implemented as combination of several rules logging-format G

nm-remarkable commented 1 year ago

I am looking into E1111

DavideCanton commented 1 year ago

I would really need W1203, in the next days maybe I can start working at it

Skylion007 commented 1 year ago

@DavideCanton Isn't W1203 already implemented as one of the logging-format rules G?

DavideCanton commented 1 year ago

@Skylion007 oh, I didn't know there was a flake8 plugin for that too :/ thanks for your help!

antonagestam commented 1 year ago

Some notes about various listed codes:

(I combined the other comments into this one, sorry for noise).

antonagestam commented 1 year ago

Note: bad-indentation / W0311 is implemented by E111 and the pylint example gives a true positive with this configuration:

[tool.ruff]
select = ["E111"]
antonagestam commented 1 year ago

Note: boolean-datetime / W1502 also only applies to dead versions of Python (3.4 and earlier).

antonagestam commented 1 year ago

Note: unknown-option-value / W0012 probably shouldn't be implemented either, as it's specific to pylint error codes.

antonagestam commented 1 year ago

I've went through and evaluated all the rules listed here that aren't yet implemented (see the previous comment for some other findings), to try and find out which rules are redundant when using mypy. Mostly this was to find out what value Pylint brings that isn't already covered by the combination of ruff + mypy, but I think this could also be useful to ruff developers when prioritizing which of the rules listed to work on.

There was one rule that is partially implemented in mypy, because Pylint seems to have its own semantics for what to consider an abstract method:

I found these rules to have an equivalent error in mypy:

I didn't evaluate these rules:

And I found these shouldn't be implemented for various reasons, see previous comment:

I hope this is useful. Here's a repository with my categorizations, should someone be interested in the details: https://github.com/antonagestam/pylint-mypy-overlap

Skylion007 commented 1 year ago

This PR also implements one of the rules for FixMe comments (W0511): https://github.com/charliermarsh/ruff/pull/4681

hoel-bagard commented 1 year ago

I'm looking into unnecessary-ellipsis and unnecessary-pass.

Edit: unnecessary-ellipsis is partially covered by ellipsis-in-non-empty-class-body