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 custom rewrites, using e.g. libcst #22

Open
15 of 27 tasks
Zac-HD opened this issue Sep 14, 2021 · 23 comments
Open
15 of 27 tasks

Add custom rewrites, using e.g. libcst #22

Zac-HD opened this issue Sep 14, 2021 · 23 comments

Comments

@Zac-HD
Copy link
Owner

Zac-HD commented Sep 14, 2021

  • Split up assert a and b into two separate assertions, for easier debugging. Preserve comments.
  • Break heavily-annotated function arguments over multiple lines, like Add custom rewrites, using e.g. libcst #22 (comment) - this should be in a separate PR so I can evaluate the heuristic on some real large code corpora
  • NoRedundantLambdaRule (impl): "de-proxy" patterns like lambda x: whatever(x) to just whatever.
  • pie801: prefer simple return
  • In sorted(list(...)), remove the inner call to list() (etc; see flake8-comprehensions for more)
    • likewise in "...".join(list(...)), remove the call to list() (or tuple())
    • ... and similarly in all(), dict(), sum(), min(), and max()
  • pie787: no len condition
  • pie788: no bool condition
  • raise NotImplemented should be raise NotImplementedError
  • Remove redundant parens directly around a function call, or around the argument to a function
  • Replace assert False with raise AssertionError, handling the optional arg
  • Reorder Union and Literal contents so that None is last
  • Flatten syntatically nested Union or Literal instances
  • Reorder new-style unions using | so that None is last
  • Expand convert_none_cmp() to handle True and False too, ala CompareSingletonPrimitivesByIsRule
  • Replace builtin calls with literals: we do this for comprehensions, but should also do e.g. tuple()->(), list("")->[], etc.
  • Remove list() (or tuple) call in comprehensions such as [x**2 for x in list(...)] - we're about to iterate anyway. Only applies to list, set, or dict comprehensions; generator expressions might not consume the whole iterable.
  • Replace map(lambda ..., x) with a comprehension, like Add C417 - Unnecessary use of map adamchainz/flake8-comprehensions#409
    • And likewise rewrite filter(lambda ..., x)
  • Nontrivial cases of CollapseIsinstanceChecksRule (impl): isinstance(x, y) or isinstance(x, z) -> isinstance(x, (y, z))
  • If the last except ...: clause just does raise, remove it and repeat for the preceeding clause. If it's the only except clause and there's no finally:, remove the try: block entirely.
  • pie797: no unnecessary ternary
  • pie800: no unnecessary spread
  • pie804: no unnecessary dict kwargs
  • Replace sorted(...)[0] with min(...) (or [-1] with max(...)). Invert min/max if passed reverse=True (or truthy constant); ignore reverse=False (or falsey); skip if the reverse= argument is present with any non-constant value.

Variously inspired by linters (flake8, flake8-comprehensions, fixit, etc.) and my own observations of some typed code.

@Zac-HD Zac-HD changed the title Replace bare except: with except BaseException: Add custom rewrites, using e.g. libcst Sep 20, 2021
@mcarans
Copy link

mcarans commented Oct 8, 2021

@Zac-HD Are these rewrites always run or only with --refactor?

@Zac-HD
Copy link
Owner Author

Zac-HD commented Oct 8, 2021

So far they're always enabled; --refactor only exists for slow+rare changes and fortunately writing my own rewrite rules means I can pack them into a single pass over the syntax tree. Hopefully it'll stay fast as I add more.

Update: it wasn't as fast as I'd hoped, so all these rewrites are only run in --refactor mode

@Zac-HD

This comment was marked as outdated.

@Zac-HD
Copy link
Owner Author

Zac-HD commented Mar 19, 2022

Another pattern I've noticed occasionally is that functions with parametrized-generic-type-annoted args are much easier to read with one-param-per-line. I'm still unsure of the exact cutoff for forcing this, but something like:

  • six or more annotated arguments, always one-per-line
  • four or more arguments annotated with parametrized generics like list[int]
    • three args like list[list[int]], or two like list[list[list[int]]]
    • a single argument doesn't need to be split, no matter how complex

There's probably a nice way of aggregating these factors as a score so that it works nicely in mixed-annotation-depth situations, but I'm happy to look at that when developing a prototype. dict[int, str] might also count for slightly more complexity, as might tuple[...]?

@Zac-HD

This comment was marked as outdated.

@Cheukting
Copy link
Contributor

@Zac-HD I want to try Reorder new-style unions using | so that None is last. By the way, is Flatten syntatically nested Union or Literal instances covered by #37 already?

@Zac-HD
Copy link
Owner Author

Zac-HD commented May 9, 2022

Go for it, and no, it's not 🙂

@Cheukting
Copy link
Contributor

I think the Reorder new-style unions using | so that None is last case is done with #39 I will then try Flatten syntactically nested Union or Literal instances then

@Cheukting
Copy link
Contributor

@Zac-HD I guess this it the nested Union example:
Union[int, Optional[str], bool] # Union[int, str, bool, None]

and this is the nested Literal example:
Literal[1, 2] | None # Literal[1, 2, None]

As these are the 2 in the example that is not covered yet?

Correct me if I am wrong, then I will start playing with it.

@Zac-HD
Copy link
Owner Author

Zac-HD commented May 10, 2022

Yep, that's it 😊

@Cheukting
Copy link
Contributor

I think the last task is not ready till 2023?

@Zac-HD
Copy link
Owner Author

Zac-HD commented May 11, 2022

Correct, though if you really wanted to you could implement it early conditional on int(black.__version__.split(".")[0]) >= 23 in a separate PR. Waiting is probably easier though 😜

I've also pulled all the other ideas in comments up into the checklist.

rudyardrichter added a commit to rudyardrichter/shed that referenced this issue Feb 27, 2023
- Replace builtins with literal values
- Replace empty list constructions with []

Completes a couple tasks in Zac-HD#22.
@jakkdl
Copy link
Contributor

jakkdl commented Mar 9, 2023

request: remove empty type-checking block

if running flake8 with flake8-type-checking, you'll be prompted to move imports only used for type-checking into a type-checking block. If you then refactor to not use that type, autoflake will remove it, and you're left with an empty type-checking block a la

if TYPE_CHECKING:
    pass

this can clearly be removed without any side effects.

additional luxury request: automatically add imports for type-checking types (possibly in a type-checking block).

Doing it for all imports seems dangerous, but a whitelist for the types in collections etc, when one of those types is found in a type comment but isn't imported, should be fairly safe. This would require two passes though, once to get the types used, and once to modify imports.

@Zac-HD
Copy link
Owner Author

Zac-HD commented Mar 9, 2023

I'd be happy to remove any if-statement where the test is a name and the body is pass - this can't do anything except (rarely) raise a NameError.

Luxury request: https://libcst.readthedocs.io/en/latest/codemods.html#library-of-transforms provides some useful tools for this. Modifying imports shouldn't take a full second pass though, that can be a one-shot modification to the final tree. I'd be happy to accept a PR for this, though it's pretty low priority.

@jakkdl
Copy link
Contributor

jakkdl commented Mar 16, 2023

Is there any reason that types in Union/Literal/type annotations only put None last and don't sort completely? I just wrote:

    foo: set[cst.Return | cst.Yield] = ...
    bar: list[cst.Yield | cst.Return] = ...

which is quite irritating. I'm pretty sure there's lots of instances of it in flake8-trio.
The logic would probably have to be more complex than just sorting lexically, at least putting None last - but one can also put other builtins earlier/later, as well as differentiate between attributes and names, or bracketed/non-bracketed types.

A related rewrite is literal sets:

a = set(2, 1)

and duplicates in either:

bar: set[int | float | int] = set(1, 1)

@Zac-HD
Copy link
Owner Author

Zac-HD commented Mar 16, 2023

Why not sort? => I didn't want to break deliberate orderings, and can imagine this being pretty annoying. (otherwise, yeah, there's a lot we could do there)

Set-literal rewrites would be great, but duplicates are probably better as a lint rule since there's a good chance one element is a typo!

@jakkdl
Copy link
Contributor

jakkdl commented Mar 16, 2023

might I interest you in supporting # noqa and/or configuration to toggle specific codemods? 😉
lints on duplicates does sound superior, so that's one for flake8-bugbear

@jakkdl
Copy link
Contributor

jakkdl commented Mar 21, 2023

re pie802: prefer simple any/all - this isn't fully safe since any/all will shortcut but any([... for i in ...]) will exhaust the generator. That is kind of the point of the lint, but I recently noqad PIE802 in flake8-trio (although I've since rewritten that code so can't link it) and if an autofixer silently stopped a generator from exhausting, which has side effects in the generator or in the expression, that could definitely lead to bugs that would be tricky to track down.

@Zac-HD
Copy link
Owner Author

Zac-HD commented Mar 13, 2024

@charliermarsh I'm not sure whether you've been following this issue or https://github.com/Zac-HD/shed/blob/master/CODEMODS.md, but I'd be very very happy if I could close it as "ruff does that" 😉

@charliermarsh
Copy link

@Zac-HD -- Oh cool! I think I'd need to go through and annotate which of these are supported by Ruff. Would that be helpful? Is it mostly the unchecked items that matter?

@jakkdl
Copy link
Contributor

jakkdl commented Mar 13, 2024

Oh right, I forgot this issue existed.

@Zac-HD -- Oh cool! I think I'd need to go through and annotate which of these are supported by Ruff. Would that be helpful? Is it mostly the unchecked items that matter?

The checked ones will either be listed in CODEMODS.md, or was removed in #105 because they were covered by ruff, so it's only the unchecked ones that have unknown ruff support.

@Zac-HD
Copy link
Owner Author

Zac-HD commented Mar 13, 2024

Unchecked items here are unsupported by shed and may or may not already be in ruff. CODEMODS.md lists several additional fixes which we know are not yet supported by ruff 🙂

@charliermarsh
Copy link

PIE801: prefer-simple-return

We support this as SIM103 with a fix, though we only catch one of the two cases in the PIE docs. I made an issue here: astral-sh/ruff#10402.

Nontrivial cases of CollapseIsinstanceChecksRule (impl): isinstance(x, y) or isinstance(x, z) -> isinstance(x, (y, z))

We support this as SIM101 with a fix.

Replace map(lambda ..., x) with a comprehension, like adamchainz/flake8-comprehensions#409

We support this as C417, with a fix.

pie800: no unnecessary spread

We support this exact rule, with a fix.

pie804: no unnecessary dict kwargs

We support this exact rule, with a fix.

The rest we either don't do, or we do some but not all... We don't catch the list comprehension in min, max, or join, but we do catch them in any and all, and we do convert dict([(i, i) for i in range(3)]) to a comprehension.

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

No branches or pull requests

5 participants