Compare commits

...

328 Commits

Author SHA1 Message Date
Charlie Marsh
e473df1fe9 Bump version to 0.0.96 2022-11-02 22:10:56 -04:00
Charlie Marsh
d448281b33 Add plugin properties to settings cache key (#559) 2022-11-02 22:10:01 -04:00
Charlie Marsh
94597fefc1 Change flake8-quotes default to double quotes (#558) 2022-11-02 22:03:54 -04:00
Charlie Marsh
add0bdeeb7 DRY up utilities in flake8_comprehensions/fixes.rs (#556) 2022-11-02 22:00:53 -04:00
Charlie Marsh
6a180b95d1 Implement autofix for dict and tuple comprehensions (#555) 2022-11-02 21:36:20 -04:00
Charlie Marsh
416c338237 Respect trailing whitespace in comprehension fixes (#554) 2022-11-02 21:08:17 -04:00
Charlie Marsh
9948be0145 Automatically fix a variety of comprehension rules (#553) 2022-11-02 20:39:35 -04:00
Charlie Marsh
f50ff61056 Expose autofix mode in public API (#551) 2022-11-02 09:44:46 -04:00
Charlie Marsh
017fec2bc5 Set override in actions-rs/toolchain@v1 (#543) 2022-11-02 09:36:22 -04:00
Charlie Marsh
f9def0a139 Bump version to 0.0.95 2022-11-02 09:03:34 -04:00
StefanBRas
d4fc485a76 Update README.md to use table for per-file-ignore (#549) 2022-11-02 09:02:11 -04:00
Charlie Marsh
0827d0beef Account for typing_extensions for annotation parsing (#550) 2022-11-02 09:01:12 -04:00
Charlie Marsh
b4a46ab6f0 Add tests for converter.rs (#542) 2022-11-01 22:36:08 -04:00
Charlie Marsh
f6e14edc3e Use max-line-length in converter.rs (#541) 2022-11-01 22:27:13 -04:00
Charlie Marsh
878a94f9cb Add a rust-toolchain.toml file (#538) 2022-11-01 20:34:58 -04:00
Charlie Marsh
79ca66ace5 Use nightly rustfmt with rustfmt.toml (#536) 2022-11-01 20:34:38 -04:00
fsouza
c68c6b5424 Make columns indices 1-based in the text output format (#539) 2022-11-01 19:06:08 -04:00
Charlie Marsh
bad5723d80 Add plugin configuration to flake8-to-ruff (#535) 2022-11-01 17:08:53 -04:00
Charlie Marsh
2d83f99dbf Bump version to 0.0.94 2022-11-01 16:38:59 -04:00
Charlie Marsh
5123b38758 Refine list of annotatable subscripts (#534) 2022-11-01 16:36:05 -04:00
Charlie Marsh
e9a4c8ba13 Track typing module imports (#533) 2022-11-01 14:01:59 -04:00
Charlie Marsh
927d716edd Enable flake8-to-ruff builds on all platforms 2022-11-01 12:15:43 -04:00
Charlie Marsh
df6a48fced Use separate tokens for each PyPI release 2022-10-31 22:43:38 -04:00
Charlie Marsh
91a8277ac0 Always release flake8-to-ruff 2022-10-31 22:16:28 -04:00
Charlie Marsh
5797884262 Represent per-file ignores as a map (#531) 2022-10-31 22:15:33 -04:00
Charlie Marsh
5aa8455258 Misc. improvements to flake8-to-ruff 2022-10-31 18:38:47 -04:00
Charlie Marsh
8fd713739b Use pretty-print for flake8-to-ruff 2022-10-31 17:52:03 -04:00
Charlie Marsh
5de1fcd653 Add to flake8-to-ruff README 2022-10-31 17:50:32 -04:00
Charlie Marsh
032f4f3f12 Set maturin path when building flake8-to-ruff 2022-10-31 16:49:45 -04:00
Charlie Marsh
621db96e7f Use more consistent Option in pyproject settings (#530) 2022-10-31 16:34:58 -04:00
Charlie Marsh
05867ef260 Set flake8-to-ruff release to workflow_dispatch 2022-10-31 16:25:09 -04:00
Charlie Marsh
0cd8b75f06 Rename release.yaml files 2022-10-31 16:23:52 -04:00
Charlie Marsh
5f07e1d6b5 Fix release.yaml task names 2022-10-31 16:22:25 -04:00
Charlie Marsh
1ce4585c88 Add a separate release job for flake8-to-ruff (#529) 2022-10-31 16:21:38 -04:00
Charlie Marsh
f3f010cdf5 Move flake8-to-ruff to a separate crate (#528) 2022-10-31 14:22:07 -04:00
Charlie Marsh
7e5e03fb15 Add a Flake8-to-Ruff configuration conversion tool (#527) 2022-10-31 11:34:40 -04:00
Charlie Marsh
062c41b6f5 Bump version to 0.0.93 2022-10-31 09:20:39 -04:00
Charlie Marsh
78889efa37 Modify public API to return Check rather than Message (#524) 2022-10-31 09:20:14 -04:00
Charlie Marsh
97fc281779 Remove erroneous foo.py file 2022-10-30 19:19:49 -04:00
Charlie Marsh
138b06c98a Bump version to 0.0.92 2022-10-30 18:04:30 -04:00
Charlie Marsh
2415d73260 Remove RustPython fork (#523) 2022-10-30 18:04:05 -04:00
Charlie Marsh
b060ae2f22 Move SourceCodeLocator to its own module (#522) 2022-10-30 15:51:59 -04:00
Charlie Marsh
9aa91d3d3c Add a cargo bench for SourceCodeLocator (#521) 2022-10-30 13:50:42 -04:00
Charlie Marsh
dcedb801e5 Tweak a few check messages (#520) 2022-10-30 13:13:37 -04:00
Charlie Marsh
d3e7fdabb5 Implement consistent newline handling for SourceCodeLocator (#519) 2022-10-30 13:11:08 -04:00
Charlie Marsh
dfa5a4f0f7 Fix 9/32 flake8-bugbear reference 2022-10-30 13:03:39 -04:00
Charlie Marsh
58a2d600da Avoid flagging D202 for inner functions and classes (#518) 2022-10-30 13:02:55 -04:00
Harutaka Kawamura
7ecbfe4f6a Implement B006 (#515) 2022-10-30 12:57:57 -04:00
Charlie Marsh
d8248104a7 Avoid re-indenting empty lines in D207 (#517) 2022-10-30 09:41:54 -04:00
Charlie Marsh
6eb09122c0 Add final newline in SourceCodeLocator 2022-10-29 19:23:16 -04:00
Charlie Marsh
f84c1f1fa1 Bump version to 0.0.91 2022-10-29 18:49:26 -04:00
Charlie Marsh
3aa9528229 Avoid flake8-comprehensions errors for dicts with kwargs (#512) 2022-10-29 18:49:06 -04:00
Charlie Marsh
5a3f06bab1 Bump version to 0.0.90 2022-10-29 18:34:38 -04:00
Charlie Marsh
db59d5b558 Use a single SourceCodeLocator everywhere (#510) 2022-10-29 18:23:24 -04:00
Charlie Marsh
2fcbf3ab62 Simplify SourceCodeLocator offset computation (#509) 2022-10-29 18:13:42 -04:00
Anders Kaseorg
fa9b10be72 Remove leading space from C416 message (#508) 2022-10-29 17:40:50 -04:00
Charlie Marsh
c495cef529 Move pyproject.toml logging to debug (#506) 2022-10-29 17:07:46 -04:00
Charlie Marsh
c0c8dff6ce Implement configuration options for pep8-naming (#505) 2022-10-29 17:00:30 -04:00
Charlie Marsh
80b00cc89f Add error code categories to table of contents (#504) 2022-10-29 16:39:55 -04:00
Charlie Marsh
934db3d179 Bump version to 0.0.89 2022-10-29 15:39:17 -04:00
Charlie Marsh
6a040a0405 Update checks_gen.rs 2022-10-29 15:39:02 -04:00
Harutaka Kawamura
2821ef0f69 Implement B013 (#503) 2022-10-29 15:36:29 -04:00
Harutaka Kawamura
343d931ddb Ignore unittest methods and functions in N802 (#502) 2022-10-29 15:36:09 -04:00
Harutaka Kawamura
3fc257f71b Implement N806, 815, 816, 818 (#501) 2022-10-29 15:35:56 -04:00
Charlie Marsh
6dbb0a17e9 Update README to reflect selection groups 2022-10-28 19:15:46 -04:00
Charlie Marsh
ae5ad6a4ac Bump version to 0.0.88 2022-10-28 19:11:04 -04:00
Charlie Marsh
549af6c584 Regenerate CheckCodePrefix 2022-10-28 19:10:45 -04:00
Charlie Marsh
9a799eb4e6 Bump version to 0.0.87 2022-10-28 19:00:03 -04:00
Anders Kaseorg
f260b873b6 Fix “not a char boundary” error with Unicode in extract_quote (#497) 2022-10-28 18:59:12 -04:00
Charlie Marsh
782a90b584 Add tests for resolve_codes (#498) 2022-10-28 18:58:46 -04:00
Charlie Marsh
7df903dc4d Move around and rename some of the Settings structs (#496) 2022-10-28 18:46:54 -04:00
Charlie Marsh
8fc5e91ec7 Enable prefix-based check code selection (#493) 2022-10-28 18:19:57 -04:00
Charlie Marsh
9ca1a2c273 Fix failing pyproject.toml test 2022-10-28 18:13:07 -04:00
Charlie Marsh
86265c1d7c Implement the flake8-quotes plugin (#495) 2022-10-28 17:52:11 -04:00
Charlie Marsh
a057c9a323 Move invalid_escape_sequence into pycodestyle (#494) 2022-10-28 12:20:11 -04:00
Trevor Gross
2e63bb6dcb Update hook id in README and in .pre-commit-config.yaml (#492) 2022-10-27 17:32:52 -04:00
Charlie Marsh
1b5db80b32 Update pre-commit invocation in README.md 2022-10-27 17:19:22 -04:00
Charlie Marsh
3f20cea402 Bump version to 0.0.86 2022-10-27 13:09:57 -04:00
Charlie Marsh
389fe1ff64 Avoid auto-fixing unused imports in __init__.py (#489) 2022-10-27 13:08:04 -04:00
Charlie Marsh
bad2d7ba85 Add example of per-file ignores to the README (#488) 2022-10-27 12:58:52 -04:00
Charlie Marsh
416aa298ac Allow whitespace in per-file ignore patterns (#487) 2022-10-27 12:55:28 -04:00
Charlie Marsh
a535b1adbf Replace compliance comments with check codes (#485) 2022-10-27 09:32:18 -04:00
Charlie Marsh
05fbd1a283 Bump version to 0.0.85 2022-10-26 19:13:04 -04:00
Charlie Marsh
63552cbc8e Implement W605 (invalid escape sequence) (#482) 2022-10-26 19:10:24 -04:00
Charlie Marsh
c00bd489f1 Fix multi-segment import removal (#480) 2022-10-26 16:43:55 -04:00
Charlie Marsh
16c2e3a995 Handle multi-segment import-from removal (#479) 2022-10-26 16:36:12 -04:00
Anders Kaseorg
650b025181 Suppress “No pyproject.toml found” message with --quiet (#478) 2022-10-26 16:03:25 -04:00
Anders Kaseorg
8fe46f7400 Rename --quiet to --silent and make --quiet only log errors (#477) 2022-10-26 16:03:10 -04:00
Charlie Marsh
a9bcc15797 Bump version to 0.0.84 2022-10-26 12:01:55 -04:00
Charlie Marsh
b6c856bd07 Implement B007 (unused loop control variable) (#473) 2022-10-26 12:01:27 -04:00
Charlie Marsh
4beea0484a Use lazy initialization for SourceCodeLocator (#472) 2022-10-26 11:27:48 -04:00
Suguru Yamamoto
2679db1d10 Correct EOL offset for lines ending with multi-byte char (#471) 2022-10-26 11:00:27 -04:00
Charlie Marsh
3e73462e04 Optimize imports 2022-10-25 22:06:51 -04:00
Charlie Marsh
f63a87737a DRY up usages of matches with fixer Mode (#470) 2022-10-25 22:02:58 -04:00
Charlie Marsh
e7472eac1c Increment flake8-bugbear to 8/32 2022-10-25 21:54:00 -04:00
Charlie Marsh
db3c847771 Bump version to 0.0.83 2022-10-25 21:24:13 -04:00
Charlie Marsh
4adbfc24a5 Increment flake8-bugbear to 6/32 2022-10-25 21:23:13 -04:00
Charlie Marsh
8f734a6562 Implement B002 (unary prefix increment) (#468) 2022-10-25 21:10:51 -04:00
Charlie Marsh
bcf7519eb3 Implement B017 (no assertRaises(Exception)) (#467) 2022-10-25 20:55:00 -04:00
Heyward Fann
66089052ee chore: typo on #283 link (#464) 2022-10-25 08:02:11 -04:00
Jeong YunWon
d50cc8ff65 Restyle flake8_comprehensions::check to reduce indent (#462) 2022-10-24 15:16:56 -04:00
Harutaka Kawamura
b75ea94f58 Fix uppercase and lowercase check (#461) 2022-10-22 12:49:13 -04:00
Harutaka Kawamura
2c24e2fd28 Enable N811, 812, 813, 814, 817 for Import (#460) 2022-10-22 08:31:02 -04:00
Charlie Marsh
f8dc208665 Tweak messages for pep8-naming rules 2022-10-21 17:06:16 -04:00
Charlie Marsh
c72b8e8d1e Bump version to 0.0.82 2022-10-21 12:15:19 -04:00
Harutaka Kawamura
b108c693fa Implement N811, 812, 813, 814, and 817 (#457) 2022-10-20 12:20:44 -04:00
Harutaka Kawamura
aac1912ea7 Implement N807 (#456) 2022-10-20 11:12:02 -04:00
Charlie Marsh
3dcd26aac3 Add SectionUnderlineAfterName to autofix list 2022-10-17 22:08:49 -04:00
Charlie Marsh
e53b9807f6 Bump version to 0.0.81 2022-10-17 21:43:49 -04:00
Charlie Marsh
36fe8b76d4 Enable autofix for over- and under-indented docstrings (#451) 2022-10-17 21:43:38 -04:00
Charlie Marsh
f832f88c75 Implement autofix support for D214, D405, D406, and D416 (#450) 2022-10-17 17:37:20 -04:00
Charlie Marsh
659a28de02 Bump version to 0.0.80 2022-10-17 17:02:44 -04:00
Charlie Marsh
583149a472 Break up autofix/fixes.rs (#449) 2022-10-17 17:01:50 -04:00
Charlie Marsh
206e6463be Implement autofix for more docstring-related rules (#448) 2022-10-17 16:56:47 -04:00
Charlie Marsh
118a9feec8 Split checks and plugins into source-related modules (#447) 2022-10-17 15:38:49 -04:00
Charlie Marsh
1f2ccb059a Break up some nested if statements 2022-10-17 13:08:33 -04:00
Charlie Marsh
f4d9d6c858 Fix typo in prev_visible_scope 2022-10-17 13:03:50 -04:00
Charlie Marsh
3477f5664a Fix README link to near-parity 2022-10-17 11:57:07 -04:00
Charlie Marsh
edefa5219c Update RustPython to get main versions of end_location etc. (#445) 2022-10-17 11:52:40 -04:00
Charlie Marsh
cf0d198365 Bump version to 0.0.79 2022-10-16 21:39:01 -04:00
Charlie Marsh
08b14ed77e Remove leading 'or' from fixable match 2022-10-16 21:38:22 -04:00
Charlie Marsh
6ee3075867 Bump version to 0.0.78 2022-10-16 21:28:25 -04:00
Charlie Marsh
cc8a945cbf Tweak messages for flake8-comprehensions rules (#444) 2022-10-16 21:27:13 -04:00
Charlie Marsh
1a1922b3fc Re-add the fix icon to README.md (#443) 2022-10-16 21:21:30 -04:00
Charlie Marsh
48bd766298 Implement autofixes for more docstring rules (#442) 2022-10-16 21:16:57 -04:00
Charlie Marsh
1ece3873cd Implement autofix for newline-related docstring rules (#441) 2022-10-16 19:40:38 -04:00
Charlie Marsh
472d902486 Rename docstring_plugins.rs to plugins.rs 2022-10-16 18:39:08 -04:00
Charlie Marsh
4ac6a18d40 Remove some Vec arguments 2022-10-16 18:17:10 -04:00
Charlie Marsh
8a47ea91ba Use CheckCategory to drive default rules 2022-10-16 18:07:03 -04:00
Charlie Marsh
bd4394aa89 Capitalize pep8-naming messages 2022-10-16 17:58:26 -04:00
Charlie Marsh
56f69ce71e Bump version to 0.0.77 2022-10-16 13:43:52 -04:00
Charlie Marsh
248a6cd50b Remove offsets hacks for docstring parsing logic (#440) 2022-10-16 13:43:30 -04:00
Charlie Marsh
d9e659d817 Revert "Remove trailing colon from messages"
This reverts commit 77e5564f4b.
2022-10-16 12:35:40 -04:00
Charlie Marsh
77e5564f4b Remove trailing colon from messages 2022-10-16 12:09:46 -04:00
Charlie Marsh
e79766d5ec Use backticks for pep8-naming messages 2022-10-16 12:09:07 -04:00
Harutaka Kawamura
c55fd76743 Implement N801 ~ N805 (#439) 2022-10-16 11:58:39 -04:00
Charlie Marsh
e2aedc5ba8 Bump version to 0.0.76 2022-10-15 17:22:12 -04:00
Charlie Marsh
1b2d085460 Mark W292 as a non-AST check 2022-10-15 17:21:52 -04:00
Charlie Marsh
cb138526b1 Add a note on pydocstyle 2022-10-15 17:05:18 -04:00
Charlie Marsh
8eac270d8f Add an FAQ 2022-10-15 16:52:20 -04:00
Charlie Marsh
6e19fd20bb Add table of contents 2022-10-15 16:37:52 -04:00
Charlie Marsh
10868445f5 Use H3 for rules sections 2022-10-15 16:33:26 -04:00
Charlie Marsh
e3ecf21287 Break rules table into sections (#437) 2022-10-15 16:29:08 -04:00
Charlie Marsh
fd849e112e Remove checkmark from rule table (#436) 2022-10-15 11:28:32 -04:00
Harutaka Kawamura
af27471c77 Fix C401 and C402 (#435) 2022-10-15 09:16:43 -04:00
konstin
bb466bc8d3 Add initial wasm32-wasi support (#416) 2022-10-14 20:58:18 -04:00
Charlie Marsh
7741a713e2 Avoid checking for updates when executing via stdin (#433) 2022-10-14 20:57:40 -04:00
Charlie Marsh
3ab1cfc6f8 Make some improvements to the README 2022-10-14 17:30:40 -04:00
Charlie Marsh
e73f13473d Add a .flake8 file for benchmarking 2022-10-14 17:05:24 -04:00
Charlie Marsh
3e8ef5b40f Bump version to 0.0.75 2022-10-14 14:42:57 -04:00
Charlie Marsh
c59610906c Optimize imports 2022-10-14 14:42:48 -04:00
Charlie Marsh
2353a52be8 Nest if 2022-10-14 14:36:45 -04:00
Charlie Marsh
bbffdd57ff Handle multi-byte chars in SourceCodeLocator (#431) 2022-10-14 14:29:18 -04:00
Charlie Marsh
3c15c578a7 Implement D206, D207, and D208 (#429) 2022-10-14 13:26:36 -04:00
Charlie Marsh
6a8e31b2ff Bump version to 0.0.74 2022-10-14 12:36:44 -04:00
Charlie Marsh
6407fd5a33 Re-arrange some docstring modules (#428) 2022-10-14 12:34:35 -04:00
Harutaka Kawamura
b64040cbb2 Implement C417 (#426) 2022-10-14 12:34:00 -04:00
Charlie Marsh
952a0eb4e3 Implement checks for Google-style docstrings (#427) 2022-10-14 11:53:29 -04:00
Charlie Marsh
3e28d6de04 Bump version to 0.0.73 2022-10-14 10:18:42 -04:00
Charlie Marsh
9bbfd1d3b2 Implement docstring argument tracking for NumPy-style docstrings (#425) 2022-10-14 10:18:07 -04:00
Charlie Marsh
6fb82ab763 Use test_case for macro-driven check tests (#424) 2022-10-13 18:51:01 -04:00
Charlie Marsh
6b286e9bc1 Add --config as a command-line option (#422) 2022-10-13 18:13:41 -04:00
Harutaka Kawamura
bcddd9e97f Implement C413 (#421) 2022-10-13 11:15:41 -04:00
Harutaka Kawamura
3e789136af Implement C411 (#420) 2022-10-13 09:48:27 -04:00
Harutaka Kawamura
07ef3b8754 Implement C416 (#415) 2022-10-13 08:34:33 -04:00
Charlie Marsh
46e1b16472 Bump version to 0.0.72 2022-10-12 22:43:29 -04:00
fsouza
720bfe0161 Implement --fix with stdin (#405) 2022-10-12 22:31:46 -04:00
Charlie Marsh
2f69be0d41 Bump version to 0.0.71 2022-10-12 17:14:28 -04:00
Charlie Marsh
54cb2eb15b Only run section checks when CheckCodes are enabled 2022-10-12 17:14:16 -04:00
Charlie Marsh
167992ad48 Implement D407, D408, D409, D412, and D414 (#413) 2022-10-12 17:12:54 -04:00
Charlie Marsh
f0dab24079 Implement D405, D406, D410, D411, and D413 (#411) 2022-10-12 16:31:14 -04:00
Charlie Marsh
77055faab6 Implement D404 and D418 for pydocstyle (#409) 2022-10-12 13:20:55 -04:00
Charlie Marsh
e08e1caf71 Bump version to 0.0.70 2022-10-12 12:59:14 -04:00
Harutaka Kawamura
0072dfd81e Implement C414 (#406) 2022-10-12 12:58:46 -04:00
Charlie Marsh
6ffe02ee05 Pass around VisibleScope 2022-10-12 12:52:48 -04:00
Charlie Marsh
688fc0cd02 Implement docstring visibility checks (#408) 2022-10-12 12:46:40 -04:00
Charlie Marsh
f30e5e45ab Remove initial field 2022-10-12 11:07:31 -04:00
Charlie Marsh
1a68a38306 Enable definition tracking for docstrings (#407) 2022-10-12 11:06:28 -04:00
Charlie Marsh
590aa92ead Implement D201, D202, D203, D204, and D211 (#404) 2022-10-11 21:08:30 -04:00
Charlie Marsh
8868f57a74 Implement D402 for pydocstyle (#403) 2022-10-11 13:19:56 -04:00
Charlie Marsh
71802f8861 Bump version to 0.0.69 2022-10-11 12:54:56 -04:00
Charlie Marsh
5b6fb8cefa Skip docstring checks for empty docstrings (#402) 2022-10-11 12:54:30 -04:00
Charlie Marsh
2ff964107c Add D212, D213, D300, D403, and D415 (#400) 2022-10-11 12:48:23 -04:00
Charlie Marsh
141132d5be Add fake setup.py (#399) 2022-10-11 12:38:48 -04:00
Harutaka Kawamura
8ba872ece4 Support linting input from stdin (#387) 2022-10-11 09:56:20 -04:00
Charlie Marsh
209dce2033 Fix missing backtick 2022-10-10 17:23:58 -04:00
Charlie Marsh
90d88dfb10 Make pyupgrade tally more precise 2022-10-10 17:18:20 -04:00
Charlie Marsh
4730911b25 Bump version to 0.0.68 2022-10-10 16:50:09 -04:00
Charlie Marsh
4e9fb9907a Implement D205, D209, and D210 (#398) 2022-10-10 16:49:51 -04:00
Charlie Marsh
b8dce8922d Implement D200 (OneLinerDocstring) (#397) 2022-10-10 16:07:51 -04:00
Charlie Marsh
30877127bc Implement D400 (DocstringEndsInNonPeriod) (#396) 2022-10-10 15:35:23 -04:00
Charlie Marsh
8b66bbdc9b Regenerate rules table 2022-10-10 15:18:18 -04:00
Charlie Marsh
71d3a84b14 Implement D410 (EmptyDocstring) (#395) 2022-10-10 15:15:38 -04:00
Charlie Marsh
323a5c857c Implement docstring tracking (#394) 2022-10-10 15:15:09 -04:00
Charlie Marsh
42cec3f5a0 Regenerate rules table 2022-10-10 14:02:31 -04:00
Charlie Marsh
ee42413e10 Enable autofix for B014 2022-10-10 13:18:43 -04:00
Charlie Marsh
765db12b84 Remove check_ prefix from check utilities (#393) 2022-10-10 12:58:40 -04:00
Charlie Marsh
e1b711d9c6 Bump version to 0.0.67 2022-10-10 12:55:04 -04:00
Charlie Marsh
35f593846e Implement B014 from flake8-bugbear (#392) 2022-10-10 12:53:42 -04:00
Charlie Marsh
c384fa513b Implement B025 from flake8-bugbear (#391) 2022-10-10 12:18:31 -04:00
Charlie Marsh
022ff64d29 Implement B011 from flake8-bugbear (#390) 2022-10-10 10:55:55 -04:00
Charlie Marsh
5a06fb28fd Bump version to 0.0.66 2022-10-10 10:03:59 -04:00
Charlie Marsh
46750a3e17 Flag unimplemented error codes in M001 (#388) 2022-10-10 10:03:40 -04:00
Charlie Marsh
9cc902b802 Avoid F821 false-positives with NameError (#386) 2022-10-10 09:39:59 -04:00
Harutaka Kawamura
c2a36ebd1e Implement C410 (#382) 2022-10-09 23:33:55 -04:00
Charlie Marsh
34ca225393 Rename flakes8 to flake8 2022-10-09 23:02:44 -04:00
Harutaka Kawamura
38c30905e6 Implement C409 (#381) 2022-10-09 22:34:52 -04:00
Charlie Marsh
2774194b03 Bump version to 0.0.65 2022-10-09 22:14:04 -04:00
Charlie Marsh
71ebd39f35 Extend assertEquals check to all deprecated unittest aliases (#380) 2022-10-09 22:13:51 -04:00
Charlie Marsh
a2f78ba2c7 Fix auto-fix for assertEquals rename 2022-10-09 22:11:32 -04:00
Charlie Marsh
b51a080a44 Rename SPR001 to U008 (#379) 2022-10-09 21:58:22 -04:00
Charlie Marsh
6a1d7d8a1c Defer string annotations even when futures annotations are enabled (#378) 2022-10-09 18:28:29 -04:00
Charlie Marsh
10b250ee57 Bump version to 0.0.64 2022-10-09 17:38:09 -04:00
Charlie Marsh
30b1b1e15a Treat TypeAlias values as annotations (#377) 2022-10-09 17:37:19 -04:00
Charlie Marsh
aafe7c0c39 Mark aliased submodule imports as used (#374) 2022-10-09 17:01:14 -04:00
Harutaka Kawamura
f060248656 Fix collapsed message (#372) 2022-10-09 13:01:36 -04:00
Harutaka Kawamura
bbe0220c72 Implement C415 (#371) 2022-10-09 10:12:58 -04:00
Charlie Marsh
129e2b6ad3 Bump version to 0.0.63 2022-10-08 22:51:49 -04:00
Charlie Marsh
73e744b1d0 Create unified Expr for PEP 604 rewrites (#370) 2022-10-08 22:13:00 -04:00
Charlie Marsh
50a3fc5a67 Move some code into helpers.rs 2022-10-08 20:45:15 -04:00
Charlie Marsh
de499f0258 Optimize imports 2022-10-08 20:39:13 -04:00
Charlie Marsh
e1abe37c6a Bump version to 0.0.62 2022-10-08 20:28:38 -04:00
Charlie Marsh
7fe5945541 Implement PEP 604 annotation rewrites (#369) 2022-10-08 20:28:00 -04:00
Charlie Marsh
806f3fd4f6 Implement PEP 585 annotation rewrites (#368) 2022-10-08 18:22:24 -04:00
Charlie Marsh
2bba643dd2 Use strum to facilitate simple enum serialization (#367) 2022-10-08 17:47:25 -04:00
Charlie Marsh
54090bd7ac Use strum to iterate over all check codes (#366) 2022-10-08 17:41:47 -04:00
Charlie Marsh
c62727db42 Bump version to 0.0.61 2022-10-08 17:25:36 -04:00
Charlie Marsh
d0e1612507 Check newline ending on contents directly (#365) 2022-10-08 17:25:22 -04:00
Harutaka Kawamura
5ccd907398 Implement C408 (#364) 2022-10-08 17:17:34 -04:00
Harutaka Kawamura
346610c2e3 Implement C406 (#363) 2022-10-08 11:15:41 -04:00
Harutaka Kawamura
307fa26515 Implement C405 (#362) 2022-10-08 09:03:21 -04:00
Harutaka Kawamura
136d412edd Add missing C400,C401, and C402 to CheckCode.from_str (#361) 2022-10-08 09:02:03 -04:00
Harutaka Kawamura
d9edec0ac9 Implement C402 (#359) 2022-10-07 22:57:04 -04:00
Chris Pryer
473675fffb Add check for W292 (#339) 2022-10-07 21:10:16 -04:00
Steven Maude
95dfc61315 Update GitHub Actions versions in README (#358) 2022-10-07 20:24:00 -04:00
Charlie Marsh
dd496c7b52 Bump version to 0.0.60 2022-10-07 17:36:33 -04:00
Charlie Marsh
78aafb4b34 Warn the user if an explicitly selected check code is ignored (#356) 2022-10-07 17:36:17 -04:00
Charlie Marsh
99c66d513a Rename refactor checks to upgrade checks (#354) 2022-10-07 16:54:55 -04:00
Charlie Marsh
04ade6a2f3 Update README.md 2022-10-07 16:50:17 -04:00
Charlie Marsh
4cf2682cda Implement type(primitive) (#353) 2022-10-07 16:47:06 -04:00
Charlie Marsh
e3a7357187 Wrap each import in its own backticks (#346) 2022-10-07 15:58:30 -04:00
Charlie Marsh
b60768debb Remove erroneous output.py file 2022-10-07 15:04:23 -04:00
Charlie Marsh
25e476639f Use or_default in lieu of or_insert 2022-10-07 14:56:56 -04:00
Charlie Marsh
4645788205 Bump version to 0.0.59 2022-10-07 14:55:23 -04:00
Charlie Marsh
0b9eda8836 Add target Python version as a configurable setting (#344) 2022-10-07 14:54:50 -04:00
Charlie Marsh
ad23d6acee Remove :: prefix for ruff imports 2022-10-07 14:24:51 -04:00
Harutaka Kawamura
e3d1d01a1f Implement C401 (#343) 2022-10-07 13:00:22 -04:00
Harutaka Kawamura
6dfdd21a7c Implement C400 (#340) 2022-10-07 12:16:23 -04:00
Charlie Marsh
f17d3b3c44 Bump version to 0.0.58 2022-10-07 12:14:03 -04:00
Charlie Marsh
da6b913317 Exit 0 if all errors are fixed (#342) 2022-10-07 12:13:15 -04:00
Harutaka Kawamura
bd34850f98 Implement C404 (#338) 2022-10-07 08:53:18 -04:00
Charlie Marsh
d5b33cdb40 Add missing snapshot for U002 2022-10-07 08:48:59 -04:00
Charlie Marsh
3fb4cf7009 Hide autoformat argument 2022-10-06 22:56:01 -04:00
Charlie Marsh
6e19539e28 Enable abspath(__file__) removal (#336) 2022-10-06 22:49:06 -04:00
Charlie Marsh
4eac7a07f5 Mention flake8-comprehensions in README 2022-10-06 16:26:21 -04:00
Harutaka Kawamura
5141285c8e Implement C403 (#335) 2022-10-06 16:24:23 -04:00
Charlie Marsh
82cc139d2d Bump version to 0.0.57 2022-10-06 09:16:56 -04:00
Adrian Garcia Badaracco
df438ba051 Support PEP 593 annotations (#333) 2022-10-06 09:16:07 -04:00
Adrian Garcia Badaracco
a70624cd47 add instructions for setting up cargo insta (#334) 2022-10-06 08:01:11 -04:00
Charlie Marsh
b307afc00c Move some accesses behind a shared function 2022-10-05 14:54:31 -04:00
Charlie Marsh
3d5bc1f51f Migrate Checker logic to independent plugins (#331) 2022-10-05 14:08:40 -04:00
Charlie Marsh
aba01745f5 Bump version to 0.0.56 2022-10-05 11:58:54 -04:00
Charlie Marsh
1eeeffab66 Add T201 and T203 to string conversion match (#332) 2022-10-05 11:58:33 -04:00
Charlie Marsh
9b564c9cf4 Bump version to 0.0.55 2022-10-04 20:07:31 -04:00
Charlie Marsh
5bf8b13644 Properly combine CLI and pyproject.toml ignores and selects (#329) 2022-10-04 20:07:17 -04:00
Anders Kaseorg
f80d5e70dd Support extend-select in pyproject.toml (#327) 2022-10-04 17:24:30 -04:00
Charlie Marsh
44897b2a5b Enable AST-to-source code generation (#292) 2022-10-04 16:27:57 -04:00
Anders Kaseorg
d1bcc919a2 Remove unnecessary Option wrapper from some pyproject::Config fields (#326) 2022-10-04 16:27:40 -04:00
Charlie Marsh
03e1397427 Bump version to 0.0.54 2022-10-04 14:32:06 -04:00
Charlie Marsh
fdb32330a9 Implement __metaclass__ = type removal (#324) 2022-10-04 14:31:52 -04:00
Charlie Marsh
4e6ae33a3a Only flag super calls in class-function scopes (#323) 2022-10-04 13:55:32 -04:00
Charlie Marsh
295ff8eb1a Add autofix and default status to README (#322) 2022-10-04 12:30:35 -04:00
Parth Shandilya
2449771d2f Fix the broken link to contribution guidelines (#321) 2022-10-04 11:10:10 -04:00
Charlie Marsh
406491a3a2 Bump version to 0.0.53 2022-10-04 08:56:46 -04:00
Charlie Marsh
bfae262359 Simplify noqa extraction logic (#320) 2022-10-04 08:56:14 -04:00
Charlie Marsh
af894f290f Disable plugin-based rules by default (#318) 2022-10-04 08:28:46 -04:00
Charlie Marsh
c901742244 Add plugins mention to README (#309) 2022-10-03 17:23:53 -04:00
Charlie Marsh
7e4faf4b69 Implement flake8-print (#308) 2022-10-03 17:19:56 -04:00
Charlie Marsh
31a0b20271 Bump version to 0.0.52 2022-10-03 15:22:58 -04:00
Charlie Marsh
0966bf2c66 Handle multi-import lines (#307) 2022-10-03 15:22:46 -04:00
Charlie Marsh
64d8e25528 Bump version to 0.0.51 2022-10-03 14:08:39 -04:00
Charlie Marsh
b049cced04 Automatically remove unused imports (#298) 2022-10-03 14:08:16 -04:00
Charlie Marsh
bc335f839e Visit lambda arguments prior to deferral (#303) 2022-10-02 20:54:02 -04:00
Charlie Marsh
4819e19ba2 Bump version to 0.0.50 2022-10-02 20:43:30 -04:00
Charlie Marsh
622b8adb79 Avoid falling back to A003 when A001 is disabled (#302) 2022-10-02 20:43:12 -04:00
Charlie Marsh
558d9fcbe3 Enable LibCST-based autofixing for SPR001 (#297) 2022-10-02 19:58:13 -04:00
Charlie Marsh
83f18193c2 Add an end location to Check (#299) 2022-10-02 12:50:42 -04:00
Charlie Marsh
46e6a1b3be Add end locations to all nodes (#296) 2022-10-02 12:49:48 -04:00
Suguru Yamamoto
4d0d433af9 fix: Make assigns to dunder exception for E402. (#294) 2022-10-01 09:43:47 -04:00
Christian Clauss
11f7532e72 pre-commit: Validate pyproject.toml (#266) 2022-09-30 19:21:12 -04:00
Charlie Marsh
417764d309 Expose a public 'check' method (#289) 2022-09-30 11:30:37 -04:00
Charlie Marsh
1e36c109c6 Bump version to 0.0.49 2022-09-30 09:15:32 -04:00
Nikita Sobolev
3960016d55 Create .editorconfig (#290) 2022-09-30 09:15:08 -04:00
Charlie Marsh
75d669fa86 Update check ordering 2022-09-30 09:14:41 -04:00
Nikita Sobolev
20989e12ba Implement flake8-super check (#291) 2022-09-30 09:12:09 -04:00
Charlie Marsh
5a1b6c32eb Add CONTRIBUTING.md (#288) 2022-09-30 07:51:30 -04:00
Charlie Marsh
46bdcb9080 Add instructions on GitHub Actions integration 2022-09-29 19:05:02 -04:00
Charlie Marsh
d16a7252af Add instructions on PyCharm integration 2022-09-29 18:52:45 -04:00
Charlie Marsh
ca6551eb37 Remove misc. unnecessary statements 2022-09-29 18:45:10 -04:00
Charlie Marsh
43a4f5749e Create CODE_OF_CONDUCT.md (#287) 2022-09-29 16:59:17 -04:00
Charlie Marsh
6fef4db433 Bump version to 0.0.48 2022-09-29 16:40:01 -04:00
Charlie Marsh
7470d6832f Add pattern matching limitation to README.md 2022-09-29 16:39:25 -04:00
Nikita Sobolev
63ba0bfeef Adds flake8-builtins (#284) 2022-09-29 16:37:43 -04:00
Anders Kaseorg
91666fcaf6 Don’t follow directory symlinks found while walking (#280) 2022-09-29 15:10:25 -04:00
Heyward Fann
643e27221d chore: fix eslint fix link (#281) 2022-09-29 07:15:07 -04:00
Charlie Marsh
c7349b69c1 Bump version to 0.0.47 2022-09-28 22:30:48 -04:00
Charlie Marsh
7f84753f3c Improve rendering of --show-settings 2022-09-28 22:30:20 -04:00
Charlie Marsh
e2ec62cf33 Misc. follow-up changes to #272 (#278) 2022-09-28 22:15:58 -04:00
Charlie Marsh
1d5592d937 Use take-while to terminate on parse errors (#279) 2022-09-28 22:06:35 -04:00
Anders Kaseorg
886def13bd Upgrade to clap 4 (#272) 2022-09-28 17:11:57 -04:00
Charlie Marsh
949e4d4077 Bump version to 0.0.46 2022-09-24 13:10:10 -04:00
Charlie Marsh
c8cb2eead2 Remove README note about noqa patterns 2022-09-24 13:09:45 -04:00
Seamooo
02ae494a0e Enable per-file ignores (#261) 2022-09-24 13:02:34 -04:00
Harutaka Kawamura
dce86e065b Make unused variable pattern configurable (#265) 2022-09-24 10:43:39 -04:00
Harutaka Kawamura
d77979429c Print warning and error messages in stderr (#267) 2022-09-24 09:27:35 -04:00
Adrian Garcia Badaracco
a3a15d2eb2 error invalid pyproject.toml configs (#264) 2022-09-23 21:16:07 -04:00
Charlie Marsh
5af95428ff Tweak import 2022-09-23 18:53:57 -04:00
Harutaka Kawamura
6338cad4e6 Remove python 3.6 classifier (#260) 2022-09-22 20:38:09 -04:00
Harutaka Kawamura
485881877f Include error code and message in JSON output (#259) 2022-09-22 20:29:21 -04:00
Charlie Marsh
b8f517c70e Bump version to 0.0.45 2022-09-22 14:11:09 -04:00
Charlie Marsh
9f601c2abd Document noqa workflows 2022-09-22 14:10:02 -04:00
Charlie Marsh
c0ce0b0c48 Enable automatic noqa insertion (#256) 2022-09-22 13:59:06 -04:00
Charlie Marsh
e5b16973a9 Enable autofix for M001 (#255) 2022-09-22 13:21:03 -04:00
Charlie Marsh
de9ceb2fe1 Only enforce multi-line noqa directives for strings (#258) 2022-09-22 13:09:02 -04:00
Charlie Marsh
38b19b78b7 Enable noqa directives on logical lines (#257) 2022-09-22 12:56:15 -04:00
Charlie Marsh
7043e15b57 Move noqa to a separate module 2022-09-22 09:04:54 -04:00
Charlie Marsh
9594079235 Add --extend-select and --extend-ignore (#254) 2022-09-21 19:56:43 -04:00
Charlie Marsh
732f208e47 Add a lint rule to enforce noqa validity (#253) 2022-09-21 19:56:38 -04:00
414 changed files with 29421 additions and 3608 deletions

14
.editorconfig Normal file
View File

@@ -0,0 +1,14 @@
# Check http://editorconfig.org for more information
# This is the main config file for this project:
root = true
[*]
charset = utf-8
trim_trailing_whitespace = true
end_of_line = lf
indent_style = space
insert_final_newline = true
indent_size = 2
[*.{rs,py}]
indent_size = 4

View File

@@ -14,7 +14,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
- uses: actions/cache@v3
env:
cache-name: cache-cargo
@@ -27,7 +27,7 @@ jobs:
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- run: cargo build --release
- run: cargo build --all --release
cargo_fmt:
name: "cargo fmt"
@@ -36,7 +36,10 @@ jobs:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
toolchain: nightly-2022-11-01
override: true
components: rustfmt
- uses: actions/cache@v3
env:
cache-name: cache-cargo
@@ -49,7 +52,7 @@ jobs:
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- run: cargo fmt --check
- run: cargo fmt --all --check
cargo_clippy:
name: "cargo clippy"
@@ -58,7 +61,10 @@ jobs:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
toolchain: nightly-2022-11-01
override: true
components: clippy
- uses: actions/cache@v3
env:
cache-name: cache-cargo
@@ -71,7 +77,7 @@ jobs:
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- run: cargo clippy -- -D warnings
- run: cargo clippy --all -- -D warnings
cargo_test:
name: "cargo test"
@@ -80,7 +86,9 @@ jobs:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
toolchain: nightly-2022-11-01
override: true
- uses: actions/cache@v3
env:
cache-name: cache-cargo
@@ -93,7 +101,7 @@ jobs:
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- run: cargo test
- run: cargo test --all
maturin_build:
name: "maturin build"
@@ -102,7 +110,9 @@ jobs:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
toolchain: nightly-2022-11-01
override: true
- uses: actions/setup-python@v4
with:
python-version: "3.10"

298
.github/workflows/flake8-to-ruff.yaml vendored Normal file
View File

@@ -0,0 +1,298 @@
name: "[flake8-to-ruff] Release"
on: workflow_dispatch
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
PACKAGE_NAME: flake8-to-ruff
CRATE_NAME: flake8_to_ruff
PYTHON_VERSION: "3.7" # to build abi3 wheels
jobs:
macos-x86_64:
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
default: true
- name: Build wheels - x86_64
uses: messense/maturin-action@v1
with:
target: x86_64
args: --release --out dist --sdist -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
maturin-version: "v0.13.0"
- name: Install built wheel - x86_64
run: |
pip install dist/${{ env.CRATE_NAME }}-*.whl --force-reinstall
- name: Upload wheels
uses: actions/upload-artifact@v2
with:
name: wheels
path: dist
macos-universal:
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
default: true
- name: Build wheels - universal2
uses: messense/maturin-action@v1
with:
args: --release --universal2 --out dist -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
maturin-version: "v0.13.0"
- name: Install built wheel - universal2
run: |
pip install dist/${{ env.CRATE_NAME }}-*universal2.whl --force-reinstall
- name: Upload wheels
uses: actions/upload-artifact@v2
with:
name: wheels
path: dist
windows:
runs-on: windows-latest
strategy:
matrix:
target: [x64, x86]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: ${{ matrix.target }}
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
default: true
- name: Build wheels
uses: messense/maturin-action@v1
with:
target: ${{ matrix.target }}
args: --release --out dist -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
maturin-version: "v0.13.0"
- name: Install built wheel
shell: bash
run: |
python -m pip install dist/${{ env.CRATE_NAME }}-*.whl --force-reinstall
- name: Upload wheels
uses: actions/upload-artifact@v2
with:
name: wheels
path: dist
linux:
runs-on: ubuntu-latest
strategy:
matrix:
target: [x86_64, i686]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- name: Build wheels
uses: messense/maturin-action@v1
with:
target: ${{ matrix.target }}
manylinux: auto
args: --release --out dist -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
maturin-version: "v0.13.0"
- name: Install built wheel
if: matrix.target == 'x86_64'
run: |
pip install dist/${{ env.CRATE_NAME }}-*.whl --force-reinstall
- name: Upload wheels
uses: actions/upload-artifact@v2
with:
name: wheels
path: dist
linux-cross:
runs-on: ubuntu-latest
strategy:
matrix:
target: [aarch64, armv7, s390x, ppc64le, ppc64]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Build wheels
uses: messense/maturin-action@v1
with:
target: ${{ matrix.target }}
manylinux: auto
args: --no-default-features --release --out dist -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
maturin-version: "v0.13.0"
- uses: uraimo/run-on-arch-action@v2.0.5
if: matrix.target != 'ppc64'
name: Install built wheel
with:
arch: ${{ matrix.target }}
distro: ubuntu20.04
githubToken: ${{ github.token }}
install: |
apt-get update
apt-get install -y --no-install-recommends python3 python3-pip
pip3 install -U pip
run: |
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
- name: Upload wheels
uses: actions/upload-artifact@v2
with:
name: wheels
path: dist
musllinux:
runs-on: ubuntu-latest
strategy:
matrix:
target:
- x86_64-unknown-linux-musl
- i686-unknown-linux-musl
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- name: Build wheels
uses: messense/maturin-action@v1
with:
target: ${{ matrix.target }}
manylinux: musllinux_1_2
args: --release --out dist -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
maturin-version: "v0.13.0"
- name: Install built wheel
if: matrix.target == 'x86_64-unknown-linux-musl'
uses: addnab/docker-run-action@v3
with:
image: alpine:latest
options: -v ${{ github.workspace }}:/io -w /io
run: |
apk add py3-pip
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links /io/dist/ --force-reinstall
- name: Upload wheels
uses: actions/upload-artifact@v2
with:
name: wheels
path: dist
musllinux-cross:
runs-on: ubuntu-latest
strategy:
matrix:
platform:
- target: aarch64-unknown-linux-musl
arch: aarch64
- target: armv7-unknown-linux-musleabihf
arch: armv7
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Build wheels
uses: messense/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
manylinux: musllinux_1_2
args: --release --out dist -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
maturin-version: "v0.13.0"
- uses: uraimo/run-on-arch-action@master
name: Install built wheel
with:
arch: ${{ matrix.platform.arch }}
distro: alpine_latest
githubToken: ${{ github.token }}
install: |
apk add py3-pip
run: |
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
- name: Upload wheels
uses: actions/upload-artifact@v2
with:
name: wheels
path: dist
pypy:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
target: [x86_64, aarch64]
python-version:
- "3.7"
- "3.8"
- "3.9"
exclude:
- os: macos-latest
target: aarch64
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: pypy${{ matrix.python-version }}
- name: Build wheels
uses: messense/maturin-action@v1
with:
maturin-version: "v0.13.0"
target: ${{ matrix.target }}
manylinux: auto
args: --release --out dist -i pypy${{ matrix.python-version }} -m ./crates/${{ env.CRATE_NAME }}/Cargo.toml
- name: Install built wheel
if: matrix.target == 'x86_64'
run: |
pip install dist/${{ env.CRATE_NAME }}-*.whl --force-reinstall
- name: Upload wheels
uses: actions/upload-artifact@v2
with:
name: wheels
path: dist
release:
name: Release
runs-on: ubuntu-latest
needs:
- macos-universal
- macos-x86_64
- windows
- linux
- linux-cross
- musllinux
- musllinux-cross
- pypy
steps:
- uses: actions/download-artifact@v2
with:
name: wheels
- uses: actions/setup-python@v4
- name: Publish to PyPi
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.FLAKE8_TO_RUFF_TOKEN }}
run: |
pip install --upgrade twine
twine upload --skip-existing *

View File

@@ -1,8 +1,6 @@
name: Release
name: "[ruff] Release"
on:
pull_request:
branches: [main]
create:
tags:
- v*
@@ -297,7 +295,7 @@ jobs:
- name: Publish to PyPi
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
TWINE_PASSWORD: ${{ secrets.RUFF_TOKEN }}
run: |
pip install --upgrade twine
twine upload --skip-existing *

View File

@@ -1,5 +1,10 @@
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.40
rev: v0.0.96
hooks:
- id: lint
- id: ruff
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.10.1
hooks:
- id: validate-pyproject

128
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
charlie.r.marsh@gmail.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

111
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,111 @@
# Contributing to ruff
Welcome! We're happy to have you here. Thank you in advance for your contribution to ruff.
## The basics
ruff welcomes contributions in the form of Pull Requests. For small changes (e.g., bug fixes), feel
free to submit a PR. For larger changes (e.g., new lint rules, new functionality, new configuration
options), consider submitting an [Issue](https://github.com/charliermarsh/ruff/issues) outlining
your proposed change.
### Prerequisites
ruff is written in Rust (1.63.0). You'll need to install the
[Rust toolchain](https://www.rust-lang.org/tools/install) for development.
You'll also need [Insta](https://insta.rs/docs/) to update snapshot tests:
```shell
cargo install cargo-insta
```
### Development
After cloning the repository, run ruff locally with:
```shell
cargo run resources/test/fixtures --no-cache
```
Prior to opening a pull request, ensure that your code has been auto-formatted, and that it passes
both the lint and test validation checks:
```shell
cargo fmt # Auto-formatting...
cargo clippy # Linting...
cargo test # Testing...
```
These checks will run on GitHub Actions when you open your Pull Request, but running them locally
will save you time and expedite the merge process.
Your Pull Request will be reviewed by a maintainer, which may involve a few rounds of iteration
prior to merging.
### Example: Adding a new lint rule
There are three phases to adding a new lint rule:
1. Define the rule in `src/checks.rs`.
2. Define the _logic_ for triggering the rule in `src/check_ast.rs` (for AST-based checks)
or `src/check_lines.rs` (for text-based checks).
3. Add a test fixture.
To define the rule, open up `src/checks.rs`. You'll need to define both a `CheckCode` and
`CheckKind`. As an example, you can grep for `E402` and `ModuleImportNotAtTopOfFile`, and follow the
pattern implemented therein.
To trigger the rule, you'll likely want to augment the logic in `src/check_ast.rs`, which defines
the Python AST visitor, responsible for iterating over the abstract syntax tree and collecting
lint-rule violations as it goes. Grep for the `Check::new` invocations to understand how other,
similar rules are implemented.
To add a test fixture, create a file under `resources/test/fixtures`, named to match the `CheckCode`
you defined earlier (e.g., `E402.py`). This file should contain a variety of violations and
non-violations designed to evaluate and demonstrate the behavior of your lint rule. Run ruff locally
with (e.g.) `cargo run resources/test/fixtures/E402.py`. Once you're satisfied with the output,
codify the behavior as a snapshot test by adding a new function to the `mod tests` section of
`src/linter.rs`, like so:
```rust
#[test]
fn e402() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/E402.py"),
&settings::Settings::for_rule(CheckCode::E402),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
```
Then, run `cargo test`. Your test will fail, but you'll be prompted to follow-up with
`cargo insta review`. Accept the generated snapshot, then commit the snapshot file alongside the
rest of your changes.
### Example: Adding a new configuration option
ruff's user-facing settings live in two places: first, the command-line options defined with
[clap](https://docs.rs/clap/latest/clap/) via the `Cli` struct in `src/main.rs`; and second, the
`Config` struct defined `src/pyproject.rs`, which is responsible for extracting user-defined
settings from a `pyproject.toml` file.
Ultimately, these two sources of configuration are merged into the `Settings` struct defined
in `src/settings.rs`, which is then threaded through the codebase.
To add a new configuration option, you'll likely want to _both_ add a CLI option to `src/main.rs`
_and_ a `pyproject.toml` parameter to `src/pyproject.rs`. If you want to pattern-match against an
existing example, grep for `dummy_variable_rgx`, which defines a regular expression to match against
acceptable unused variables (e.g., `_`).
## Release process
As of now, ruff has an ad hoc release process: releases are cut with high frequency via GitHub
Actions, which automatically generates the appropriate wheels across architectures and publishes
them to [PyPI](https://pypi.org/project/ruff/).
ruff follows the [semver](https://semver.org/) versioning standard. However, as pre-1.0 software,
even patch releases may contain [non-backwards-compatible changes](https://semver.org/#spec-item-4).

955
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,11 @@
[workspace]
members = [
"crates/flake8_to_ruff",
]
[package]
name = "ruff"
version = "0.0.44"
version = "0.0.96"
edition = "2021"
[lib]
@@ -9,32 +14,51 @@ name = "ruff"
[dependencies]
anyhow = { version = "1.0.60" }
bincode = { version = "1.3.3" }
cacache = { version = "10.0.1" }
chrono = { version = "0.4.21" }
clap = { version = "3.2.16", features = ["derive"] }
clearscreen = { version = "1.0.10" }
clap = { version = "4.0.1", features = ["derive"] }
colored = { version = "2.0.0" }
common-path = { version = "1.0.0" }
dirs = { version = "4.0.0" }
fern = { version = "0.6.1" }
filetime = { version = "0.2.17" }
glob = { version = "0.3.0" }
itertools = { version = "0.10.3" }
itertools = { version = "0.10.5" }
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "32a044c127668df44582f85699358e67803b0d73" }
log = { version = "0.4.17" }
notify = { version = "4.0.17" }
num-bigint = { version = "0.4.3" }
once_cell = { version = "1.13.1" }
path-absolutize = { version = "3.0.13", features = ["once_cell_cache"] }
path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix_paths_on_wasm"] }
rayon = { version = "1.5.3" }
regex = { version = "1.6.0" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/charliermarsh/RustPython.git", rev = "7d21c6923a506e79cc041708d83cef925efd33f4" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "77b821a1941019fe34f73ce17cea013ae1b98fd0" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "77b821a1941019fe34f73ce17cea013ae1b98fd0" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "77b821a1941019fe34f73ce17cea013ae1b98fd0" }
serde = { version = "1.0.143", features = ["derive"] }
serde_json = { version = "1.0.83" }
strum = { version = "0.24.1", features = ["strum_macros"] }
strum_macros = { version = "0.24.3" }
textwrap = { version = "0.15.1" }
titlecase = { version = "2.2.1" }
toml = { version = "0.5.9" }
update-informer = { version = "0.5.0", default_features = false, features = ["pypi"], optional = true }
walkdir = { version = "2.3.2" }
[target.'cfg(not(target_family = "wasm"))'.dependencies]
cacache = { version = "10.0.1" } # uses async-std
clearscreen = { version = "1.0.10" } # uses which
# https://docs.rs/getrandom/0.2.7/getrandom/#webassembly-support
# For (future) wasm-pack support
[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies]
getrandom = { version = "0.2.7", features = ["js"] }
[dev-dependencies]
assert_cmd = { version = "2.0.4" }
codegen = { version = "0.2.0" }
criterion = { version = "0.4.0" }
insta = { version = "1.19.1", features = ["yaml"] }
test-case = { version = "2.2.2" }
[features]
default = ["update-informer"]
@@ -51,3 +75,7 @@ opt-level = 3
[profile.dev.package.similar]
opt-level = 3
[[bench]]
name = "source_code_locator"
harness = false

687
README.md
View File

@@ -1,4 +1,4 @@
# ruff
# Ruff
[![image](https://img.shields.io/pypi/v/ruff.svg)](https://pypi.python.org/pypi/ruff)
[![image](https://img.shields.io/pypi/l/ruff.svg)](https://pypi.python.org/pypi/ruff)
@@ -20,17 +20,49 @@ An extremely fast Python linter, written in Rust.
- 🤝 Python 3.10 compatibility
- 🛠️ `pyproject.toml` support
- 📦 [ESLint](https://eslint.org/docs/latest/user-guide/command-line-interface#caching)-inspired cache support
- 🔧 [ESLint](https://eslint.org/docs/latest/user-guide/command-line-interface#caching)-inspired `--fix` support
- 👀 [TypeScript](https://www.typescriptlang.org/docs/handbook/configuring-watch.html)-inspired `--watch` support
- ⚖️ [Near-complete parity](#Parity-with-Flake8) with the built-in Flake8 rule set
- 🔧 [ESLint](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix)-inspired autofix support (e.g., automatically remove unused imports)
- 👀 [TypeScript](https://www.typescriptlang.org/docs/handbook/configuring-watch.html)-inspired `--watch` support, for continuous file monitoring
- ⚖️ [Near-parity](#how-does-ruff-compare-to-flake8) with the built-in Flake8 rule set
- 🔌 Native re-implementations of popular Flake8 plugins, like [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/) ([`pydocstyle`](https://pypi.org/project/pydocstyle/))
Ruff aims to be orders of magnitude faster than alternative tools while integrating more
functionality behind a single, common interface. Ruff can be used to replace Flake8 (plus a variety
of plugins), [`pydocstyle`](https://pypi.org/project/pydocstyle/), [`yesqa`](https://github.com/asottile/yesqa),
and even a subset of [`pyupgrade`](https://pypi.org/project/pyupgrade/) and [`autoflake`](https://pypi.org/project/autoflake/)
all while executing tens or hundreds of times faster than any individual tool.
Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
## Installation and usage
## Table of Contents
1. [Installation and Usage](#installation-and-usage)
2. [Configuration](#configuration)
3. [Supported Rules](#supported-rules)
1. [Pyflakes](#pyflakes)
2. [pycodestyle (error)](#pycodestyle-error)
3. [pycodestyle (warning)](#pycodestyle-warning)
4. [pydocstyle](#pydocstyle)
5. [pyupgrade](#pyupgrade)
6. [pep8-naming](#pep8-naming)
7. [flake8-comprehensions](#flake8-comprehensions)
8. [flake8-bugbear](#flake8-bugbear)
9. [flake8-builtins](#flake8-builtins)
10. [flake8-print](#flake8-print)
11. [flake8-quotes](#flake8-quotes)
12. [Meta rules](#meta-rules)
5. [Editor Integrations](#editor-integrations)
6. [FAQ](#faq)
7. [Development](#development)
8. [Releases](#releases)
9. [Benchmarks](#benchmarks)
10. [License](#license)
11. [Contributing](#contributing)
## Installation and Usage
### Installation
Available as [ruff](https://pypi.org/project/ruff/) on PyPI:
Available as [`ruff`](https://pypi.org/project/ruff/) on PyPI:
```shell
pip install ruff
@@ -38,7 +70,7 @@ pip install ruff
### Usage
To run ruff, try any of the following:
To run Ruff, try any of the following:
```shell
ruff path/to/code/to/check.py
@@ -46,89 +78,116 @@ ruff path/to/code/
ruff path/to/code/*.py
```
You can run ruff in `--watch` mode to automatically re-run on-change:
You can run Ruff in `--watch` mode to automatically re-run on-change:
```shell
ruff path/to/code/ --watch
```
ruff also works with [pre-commit](https://pre-commit.com):
Ruff also works with [pre-commit](https://pre-commit.com):
```yaml
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.40
rev: v0.0.96
hooks:
- id: lint
- id: ruff
```
<!-- TODO(charlie): Remove this message a few versions after v0.0.86. -->
_Note: prior to `v0.0.86`, `ruff-pre-commit` used `lint` (rather than `ruff`) as the hook ID._
## Configuration
ruff is configurable both via `pyproject.toml` and the command line.
Ruff is configurable both via `pyproject.toml` and the command line.
For example, you could configure ruff to only enforce a subset of rules with:
For example, you could configure Ruff to only enforce a subset of rules with:
```toml
[tool.ruff]
line-length = 88
select = [
"F401",
"F403",
]
select = ["E", "F"]
ignore = ["E501"]
per-file-ignores = {"__init__.py" = ["F401"], "path/to/file.py" = ["F401"]}
```
Alternatively, on the command-line:
Plugin configurations should be expressed as subsections, e.g.:
```toml
[tool.ruff]
line-length = 88
[tool.ruff.flake8-quotes]
docstring-quotes = "double"
```
Alternatively, common configuration settings can be provided via the command-line:
```shell
ruff path/to/code/ --select F401 F403
ruff path/to/code/ --select F401 --select F403
```
See `ruff --help` for more:
```shell
ruff (v0.0.44) 0.0.44
An extremely fast Python linter.
ruff: An extremely fast Python linter.
USAGE:
ruff [OPTIONS] <FILES>...
Usage: ruff [OPTIONS] <FILES>...
ARGS:
<FILES>...
Arguments:
<FILES>...
OPTIONS:
-e, --exit-zero
Exit with status code "0", even upon detecting errors
--exclude <EXCLUDE>...
List of paths, used to exclude files and/or directories from checks
--extend-exclude <EXTEND_EXCLUDE>...
Like --exclude, but adds additional files and directories on top of the excluded ones
-f, --fix
Attempt to automatically fix lint errors
--format <FORMAT>
Output serialization format for error messages [default: text] [possible values: text,
json]
-h, --help
Print help information
--ignore <IGNORE>...
List of error codes to ignore
-n, --no-cache
Disable cache reads
-q, --quiet
Disable all logging (but still exit with status code "1" upon detecting errors)
--select <SELECT>...
List of error codes to enable
--show-files
See the files ruff will be run against with the current configuration options
--show-settings
See ruff's settings
-v, --verbose
Enable verbose logging
-V, --version
Print version information
-w, --watch
Run in watch mode by re-running whenever files change
Options:
--config <CONFIG>
Path to the `pyproject.toml` file to use for configuration
-v, --verbose
Enable verbose logging
-q, --quiet
Disable all logging (but still exit with status code "1" upon detecting errors)
-e, --exit-zero
Exit with status code "0", even upon detecting errors
-w, --watch
Run in watch mode by re-running whenever files change
-f, --fix
Attempt to automatically fix lint errors
-n, --no-cache
Disable cache reads
--select <SELECT>
List of error codes to enable
--extend-select <EXTEND_SELECT>
Like --select, but adds additional error codes on top of the selected ones
--ignore <IGNORE>
List of error codes to ignore
--extend-ignore <EXTEND_IGNORE>
Like --ignore, but adds additional error codes on top of the ignored ones
--exclude <EXCLUDE>
List of paths, used to exclude files and/or directories from checks
--extend-exclude <EXTEND_EXCLUDE>
Like --exclude, but adds additional files and directories on top of the excluded ones
--per-file-ignores <PER_FILE_IGNORES>
List of mappings from file pattern to code to exclude
--format <FORMAT>
Output serialization format for error messages [default: text] [possible values: text, json]
--show-files
See the files ruff will be run against with the current settings
--show-settings
See ruff's settings
--add-noqa
Enable automatic additions of noqa directives to failing lines
--dummy-variable-rgx <DUMMY_VARIABLE_RGX>
Regular expression matching the name of dummy variables
--target-version <TARGET_VERSION>
The minimum Python version that should be supported
--stdin-filename <STDIN_FILENAME>
The name of the file when passing it through stdin
-h, --help
Print help information
-V, --version
Print version information
```
### Excluding files
Exclusions are based on globs, and can be either:
- Single-path patterns, like `.mypy_cache` (to exclude any directory named `.mypy_cache` in the
@@ -138,99 +197,470 @@ Exclusions are based on globs, and can be either:
(to exclude any Python files in `directory`). Note that these paths are relative to the
project root (e.g., the directory containing your `pyproject.toml`).
### Compatibility with Black
### Ignoring errors
ruff is compatible with [Black](https://github.com/psf/black) out-of-the-box, as long as
To omit a lint check entirely, add it to the "ignore" list via `--ignore` or `--extend-ignore`,
either on the command-line or in your `project.toml` file.
To ignore an error in-line, Ruff uses a `noqa` system similar to [Flake8](https://flake8.pycqa.org/en/3.1.1/user/ignoring-errors.html).
To ignore an individual error, add `# noqa: {code}` to the end of the line, like so:
```python
# Ignore F841.
x = 1 # noqa: F841
# Ignore E741 and F841.
i = 1 # noqa: E741, F841
# Ignore _all_ errors.
x = 1 # noqa
```
Note that, for multi-line strings, the `noqa` directive should come at the end of the string, and
will apply to the entire body, like so:
```python
"""Lorem ipsum dolor sit amet.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
""" # noqa: E501
```
Ruff supports several workflows to aid in `noqa` management.
First, Ruff provides a special error code, `M001`, to enforce that your `noqa` directives are
"valid", in that the errors they _say_ they ignore are actually being triggered on that line (and
thus suppressed). **You can run `ruff /path/to/file.py --extend-select M001` to flag unused `noqa`
directives.**
Second, Ruff can _automatically remove_ unused `noqa` directives via its autofix functionality.
**You can run `ruff /path/to/file.py --extend-select M001 --fix` to automatically remove unused
`noqa` directives.**
Third, Ruff can _automatically add_ `noqa` directives to all failing lines. This is useful when
migrating a new codebase to Ruff. **You can run `ruff /path/to/file.py --add-noqa` to automatically
add `noqa` directives to all failing lines, with the appropriate error codes.**
## Supported Rules
By default, Ruff enables all `E` and `F` error codes, which correspond to those built-in to Flake8.
The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` command-line option.
### Pyflakes
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| F401 | UnusedImport | `...` imported but unused | 🛠 |
| F402 | ImportShadowedByLoopVar | Import `...` from line 1 shadowed by loop variable | |
| F403 | ImportStarUsed | `from ... import *` used; unable to detect undefined names | |
| F404 | LateFutureImport | `from __future__` imports must occur at the beginning of the file | |
| F405 | ImportStarUsage | `...` may be undefined, or defined from star imports: `...` | |
| F406 | ImportStarNotPermitted | `from ... import *` only allowed at module level | |
| F407 | FutureFeatureNotDefined | Future feature `...` is not defined | |
| F541 | FStringMissingPlaceholders | f-string without any placeholders | |
| F601 | MultiValueRepeatedKeyLiteral | Dictionary key literal repeated | |
| F602 | MultiValueRepeatedKeyVariable | Dictionary key `...` repeated | |
| F621 | ExpressionsInStarAssignment | Too many expressions in star-unpacking assignment | |
| F622 | TwoStarredExpressions | Two starred expressions in assignment | |
| F631 | AssertTuple | Assert test is a non-empty tuple, which is always `True` | |
| F632 | IsLiteral | Use `==` and `!=` to compare constant literals | |
| F633 | InvalidPrintSyntax | Use of `>>` is invalid with `print` function | |
| F634 | IfTuple | If test is a tuple, which is always `True` | |
| F701 | BreakOutsideLoop | `break` outside loop | |
| F702 | ContinueOutsideLoop | `continue` not properly in loop | |
| F704 | YieldOutsideFunction | `yield` or `yield from` statement outside of a function | |
| F706 | ReturnOutsideFunction | `return` statement outside of a function/method | |
| F707 | DefaultExceptNotLast | An `except:` block as not the last exception handler | |
| F722 | ForwardAnnotationSyntaxError | Syntax error in forward annotation: `...` | |
| F821 | UndefinedName | Undefined name `...` | |
| F822 | UndefinedExport | Undefined name `...` in `__all__` | |
| F823 | UndefinedLocal | Local variable `...` referenced before assignment | |
| F831 | DuplicateArgumentName | Duplicate argument name in function definition | |
| F841 | UnusedVariable | Local variable `...` is assigned to but never used | |
| F901 | RaiseNotImplemented | `raise NotImplemented` should be `raise NotImplementedError` | |
### pycodestyle (error)
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| E402 | ModuleImportNotAtTopOfFile | Module level import not at top of file | |
| E501 | LineTooLong | Line too long (89 > 88 characters) | |
| E711 | NoneComparison | Comparison to `None` should be `cond is None` | |
| E712 | TrueFalseComparison | Comparison to `True` should be `cond is True` | |
| E713 | NotInTest | Test for membership should be `not in` | |
| E714 | NotIsTest | Test for object identity should be `is not` | |
| E721 | TypeComparison | Do not compare types, use `isinstance()` | |
| E722 | DoNotUseBareExcept | Do not use bare `except` | |
| E731 | DoNotAssignLambda | Do not assign a lambda expression, use a def | |
| E741 | AmbiguousVariableName | Ambiguous variable name: `...` | |
| E742 | AmbiguousClassName | Ambiguous class name: `...` | |
| E743 | AmbiguousFunctionName | Ambiguous function name: `...` | |
| E902 | IOError | IOError: `...` | |
| E999 | SyntaxError | SyntaxError: `...` | |
### pycodestyle (warning)
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| W292 | NoNewLineAtEndOfFile | No newline at end of file | |
| W605 | InvalidEscapeSequence | Invalid escape sequence: '\c' | |
### pydocstyle
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| D100 | PublicModule | Missing docstring in public module | |
| D101 | PublicClass | Missing docstring in public class | |
| D102 | PublicMethod | Missing docstring in public method | |
| D103 | PublicFunction | Missing docstring in public function | |
| D104 | PublicPackage | Missing docstring in public package | |
| D105 | MagicMethod | Missing docstring in magic method | |
| D106 | PublicNestedClass | Missing docstring in public nested class | |
| D107 | PublicInit | Missing docstring in `__init__` | |
| D200 | FitsOnOneLine | One-line docstring should fit on one line | |
| D201 | NoBlankLineBeforeFunction | No blank lines allowed before function docstring (found 1) | 🛠 |
| D202 | NoBlankLineAfterFunction | No blank lines allowed after function docstring (found 1) | 🛠 |
| D203 | OneBlankLineBeforeClass | 1 blank line required before class docstring | 🛠 |
| D204 | OneBlankLineAfterClass | 1 blank line required after class docstring | 🛠 |
| D205 | BlankLineAfterSummary | 1 blank line required between summary line and description | 🛠 |
| D206 | IndentWithSpaces | Docstring should be indented with spaces, not tabs | |
| D207 | NoUnderIndentation | Docstring is under-indented | 🛠 |
| D208 | NoOverIndentation | Docstring is over-indented | 🛠 |
| D209 | NewLineAfterLastParagraph | Multi-line docstring closing quotes should be on a separate line | 🛠 |
| D210 | NoSurroundingWhitespace | No whitespaces allowed surrounding docstring text | 🛠 |
| D211 | NoBlankLineBeforeClass | No blank lines allowed before class docstring | 🛠 |
| D212 | MultiLineSummaryFirstLine | Multi-line docstring summary should start at the first line | |
| D213 | MultiLineSummarySecondLine | Multi-line docstring summary should start at the second line | |
| D214 | SectionNotOverIndented | Section is over-indented ("Returns") | 🛠 |
| D215 | SectionUnderlineNotOverIndented | Section underline is over-indented ("Returns") | 🛠 |
| D300 | UsesTripleQuotes | Use """triple double quotes""" | |
| D400 | EndsInPeriod | First line should end with a period | |
| D402 | NoSignature | First line should not be the function's signature | |
| D403 | FirstLineCapitalized | First word of the first line should be properly capitalized | |
| D404 | NoThisPrefix | First word of the docstring should not be 'This' | |
| D405 | CapitalizeSectionName | Section name should be properly capitalized ("returns") | 🛠 |
| D406 | NewLineAfterSectionName | Section name should end with a newline ("Returns") | 🛠 |
| D407 | DashedUnderlineAfterSection | Missing dashed underline after section ("Returns") | 🛠 |
| D408 | SectionUnderlineAfterName | Section underline should be in the line following the section's name ("Returns") | 🛠 |
| D409 | SectionUnderlineMatchesSectionLength | Section underline should match the length of its name ("Returns") | 🛠 |
| D410 | BlankLineAfterSection | Missing blank line after section ("Returns") | 🛠 |
| D411 | BlankLineBeforeSection | Missing blank line before section ("Returns") | 🛠 |
| D412 | NoBlankLinesBetweenHeaderAndContent | No blank lines allowed between a section header and its content ("Returns") | 🛠 |
| D413 | BlankLineAfterLastSection | Missing blank line after last section ("Returns") | 🛠 |
| D414 | NonEmptySection | Section has no content ("Returns") | |
| D415 | EndsInPunctuation | First line should end with a period, question mark, or exclamation point | |
| D416 | SectionNameEndsInColon | Section name should end with a colon ("Returns") | 🛠 |
| D417 | DocumentAllArguments | Missing argument descriptions in the docstring: `x`, `y` | |
| D418 | SkipDocstring | Function decorated with `@overload` shouldn't contain a docstring | |
| D419 | NonEmpty | Docstring is empty | |
### pyupgrade
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| U001 | UselessMetaclassType | `__metaclass__ = type` is implied | 🛠 |
| U002 | UnnecessaryAbspath | `abspath(__file__)` is unnecessary in Python 3.9 and later | 🛠 |
| U003 | TypeOfPrimitive | Use `str` instead of `type(...)` | 🛠 |
| U004 | UselessObjectInheritance | Class `...` inherits from object | 🛠 |
| U005 | DeprecatedUnittestAlias | `assertEquals` is deprecated, use `assertEqual` instead | 🛠 |
| U006 | UsePEP585Annotation | Use `list` instead of `List` for type annotations | 🛠 |
| U007 | UsePEP604Annotation | Use `X \| Y` for type annotations | 🛠 |
| U008 | SuperCallWithParameters | Use `super()` instead of `super(__class__, self)` | 🛠 |
### pep8-naming
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| N801 | InvalidClassName | Class name `...` should use CapWords convention | |
| N802 | InvalidFunctionName | Function name `...` should be lowercase | |
| N803 | InvalidArgumentName | Argument name `...` should be lowercase | |
| N804 | InvalidFirstArgumentNameForClassMethod | First argument of a class method should be named `cls` | |
| N805 | InvalidFirstArgumentNameForMethod | First argument of a method should be named `self` | |
| N806 | NonLowercaseVariableInFunction | Variable `...` in function should be lowercase | |
| N807 | DunderFunctionName | Function name should not start and end with `__` | |
| N811 | ConstantImportedAsNonConstant | Constant `...` imported as non-constant `...` | |
| N812 | LowercaseImportedAsNonLowercase | Lowercase `...` imported as non-lowercase `...` | |
| N813 | CamelcaseImportedAsLowercase | Camelcase `...` imported as lowercase `...` | |
| N814 | CamelcaseImportedAsConstant | Camelcase `...` imported as constant `...` | |
| N815 | MixedCaseVariableInClassScope | Variable `mixedCase` in class scope should not be mixedCase | |
| N816 | MixedCaseVariableInGlobalScope | Variable `mixedCase` in global scope should not be mixedCase | |
| N817 | CamelcaseImportedAsAcronym | Camelcase `...` imported as acronym `...` | |
| N818 | ErrorSuffixOnExceptionName | Exception name `...` should be named with an Error suffix | |
### flake8-comprehensions
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| C400 | UnnecessaryGeneratorList | Unnecessary generator (rewrite as a `list` comprehension) | 🛠 |
| C401 | UnnecessaryGeneratorSet | Unnecessary generator (rewrite as a `set` comprehension) | 🛠 |
| C402 | UnnecessaryGeneratorDict | Unnecessary generator (rewrite as a `dict` comprehension) | 🛠 |
| C403 | UnnecessaryListComprehensionSet | Unnecessary `list` comprehension (rewrite as a `set` comprehension) | 🛠 |
| C404 | UnnecessaryListComprehensionDict | Unnecessary `list` comprehension (rewrite as a `dict` comprehension) | |
| C405 | UnnecessaryLiteralSet | Unnecessary `(list\|tuple)` literal (rewrite as a `set` literal) | 🛠 |
| C406 | UnnecessaryLiteralDict | Unnecessary `(list\|tuple)` literal (rewrite as a `dict` literal) | |
| C408 | UnnecessaryCollectionCall | Unnecessary `(dict\|list\|tuple)` call (rewrite as a literal) | 🛠 |
| C409 | UnnecessaryLiteralWithinTupleCall | Unnecessary `(list\|tuple)` literal passed to `tuple()` (remove the outer call to `tuple()`) | 🛠 |
| C410 | UnnecessaryLiteralWithinListCall | Unnecessary `(list\|tuple)` literal passed to `list()` (rewrite as a `list` literal) | 🛠 |
| C411 | UnnecessaryListCall | Unnecessary `list` call (remove the outer call to `list()`) | 🛠 |
| C413 | UnnecessaryCallAroundSorted | Unnecessary `(list\|reversed)` call around `sorted()` | |
| C414 | UnnecessaryDoubleCastOrProcess | Unnecessary `(list\|reversed\|set\|sorted\|tuple)` call within `(list\|set\|sorted\|tuple)()` | |
| C415 | UnnecessarySubscriptReversal | Unnecessary subscript reversal of iterable within `(reversed\|set\|sorted)()` | |
| C416 | UnnecessaryComprehension | Unnecessary `(list\|set)` comprehension (rewrite using `(list\|set)()`) | |
| C417 | UnnecessaryMap | Unnecessary `map` usage (rewrite using a `(list\|set\|dict)` comprehension) | |
### flake8-bugbear
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| B002 | UnaryPrefixIncrement | Python does not support the unary prefix increment. | |
| B006 | MutableArgumentDefault | Do not use mutable data structures for argument defaults. | |
| B007 | UnusedLoopControlVariable | Loop control variable `i` not used within the loop body. | 🛠 |
| B011 | DoNotAssertFalse | Do not `assert False` (`python -O` removes these calls), raise `AssertionError()` | 🛠 |
| B013 | RedundantTupleInExceptionHandler | A length-one tuple literal is redundant. Write `except ValueError:` instead of `except (ValueError,):`. | |
| B014 | DuplicateHandlerException | Exception handler with duplicate exception: `ValueError` | 🛠 |
| B017 | NoAssertRaisesException | `assertRaises(Exception):` should be considered evil. | |
| B025 | DuplicateTryBlockException | try-except block with duplicate exception `Exception` | |
### flake8-builtins
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| A001 | BuiltinVariableShadowing | Variable `...` is shadowing a python builtin | |
| A002 | BuiltinArgumentShadowing | Argument `...` is shadowing a python builtin | |
| A003 | BuiltinAttributeShadowing | Class attribute `...` is shadowing a python builtin | |
### flake8-print
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| T201 | PrintFound | `print` found | 🛠 |
| T203 | PPrintFound | `pprint` found | 🛠 |
### flake8-quotes
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| Q000 | BadQuotesInlineString | Single quotes found but double quotes preferred | |
| Q001 | BadQuotesMultilineString | Single quote multiline found but double quotes preferred | |
| Q002 | BadQuotesDocstring | Single quote docstring found but double quotes preferred | |
| Q003 | AvoidQuoteEscape | Change outer quotes to avoid escaping inner quotes | |
### Meta rules
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| M001 | UnusedNOQA | Unused `noqa` directive | 🛠 |
## Editor Integrations
### PyCharm
Ruff can be installed as an [External Tool](https://www.jetbrains.com/help/pycharm/configuring-third-party-tools.html)
in PyCharm. Open the Preferences pane, then navigate to "Tools", then "External Tools". From there,
add a new tool with the following configuration:
![Install Ruff as an External Tool](https://user-images.githubusercontent.com/1309177/193155720-336e43f0-1a8d-46b4-bc12-e60f9ae01f7e.png)
Ruff should then appear as a runnable action:
![Ruff as a runnable action](https://user-images.githubusercontent.com/1309177/193156026-732b0aaf-3dd9-4549-9b4d-2de6d2168a33.png)
### GitHub Actions
GitHub Actions has everything you need to run Ruff out-of-the-box:
```yaml
name: CI
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Python
uses: actions/setup-python@v4
with:
python-version: "3.10"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install ruff
- name: Run Ruff
run: ruff .
```
## FAQ
### Is Ruff compatible with Black?
Yes. Ruff is compatible with [Black](https://github.com/psf/black) out-of-the-box, as long as
the `line-length` setting is consistent between the two.
As a project, ruff is designed to be used alongside Black and, as such, will defer implementing
As a project, Ruff is designed to be used alongside Black and, as such, will defer implementing
stylistic lint rules that are obviated by autoformatting.
### Parity with Flake8
### How does Ruff compare to Flake8?
ruff's goal is to achieve feature-parity with Flake8 when used (1) without plugins, (2) alongside
Black, and (3) on Python 3 code.
Ruff can be used as a (near) drop-in replacement for Flake8 when used (1) without or with a small
number of plugins, (2) alongside Black, and (3) on Python 3 code.
**Under those conditions, ruff implements 44 out of 60 rules.** (ruff is missing: 14 rules related
to string `.format` calls, 1 rule related to docstring parsing, and 1 rule related to redefined
variables.)
Under those conditions Ruff is missing 14 rules related to string `.format` calls, 1 rule related
to docstring parsing, and 1 rule related to redefined variables.
Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis Flake8:
Ruff re-implements some of the most popular Flake8 plugins and related code quality tools natively,
including:
1. Flake8 has a plugin architecture and supports writing custom lint rules.
2. Flake8 supports a wider range of `noqa` patterns, such as per-file ignores defined in `.flake8`.
3. ruff does not yet support parenthesized context managers.
- [`pydocstyle`](https://pypi.org/project/pydocstyle/)
- [`pep8-naming`](https://pypi.org/project/pep8-naming/)
- [`yesqa`](https://github.com/asottile/yesqa)
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/)
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (10/32)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (10/34)
- [`autoflake`](https://pypi.org/project/autoflake/) (1/7)
## Rules
Beyond rule-set parity, Ruff suffers from the following limitations vis-à-vis Flake8:
| Code | Name | Message |
| ---- | ----- | ------- |
| E402 | ModuleImportNotAtTopOfFile | Module level import not at top of file |
| E501 | LineTooLong | Line too long (89 > 88 characters) |
| E711 | NoneComparison | Comparison to `None` should be `cond is None` |
| E712 | TrueFalseComparison | Comparison to `True` should be `cond is True` |
| E713 | NotInTest | Test for membership should be `not in` |
| E714 | NotIsTest | Test for object identity should be `is not` |
| E721 | TypeComparison | do not compare types, use `isinstance()` |
| E722 | DoNotUseBareExcept | Do not use bare `except` |
| E731 | DoNotAssignLambda | Do not assign a lambda expression, use a def |
| E741 | AmbiguousVariableName | ambiguous variable name '...' |
| E742 | AmbiguousClassName | ambiguous class name '...' |
| E743 | AmbiguousFunctionName | ambiguous function name '...' |
| E902 | IOError | ... |
| E999 | SyntaxError | SyntaxError: ... |
| F401 | UnusedImport | `...` imported but unused |
| F402 | ImportShadowedByLoopVar | import '...' from line 1 shadowed by loop variable |
| F403 | ImportStarUsed | `from ... import *` used; unable to detect undefined names |
| F404 | LateFutureImport | from __future__ imports must occur at the beginning of the file |
| F405 | ImportStarUsage | '...' may be undefined, or defined from star imports: ... |
| F406 | ImportStarNotPermitted | `from ... import *` only allowed at module level |
| F407 | FutureFeatureNotDefined | future feature '...' is not defined |
| F541 | FStringMissingPlaceholders | f-string without any placeholders |
| F601 | MultiValueRepeatedKeyLiteral | Dictionary key literal repeated |
| F602 | MultiValueRepeatedKeyVariable | Dictionary key `...` repeated |
| F621 | TooManyExpressionsInStarredAssignment | too many expressions in star-unpacking assignment |
| F622 | TwoStarredExpressions | two starred expressions in assignment |
| F631 | AssertTuple | Assert test is a non-empty tuple, which is always `True` |
| F632 | IsLiteral | use ==/!= to compare constant literals |
| F633 | InvalidPrintSyntax | use of >> is invalid with print function |
| F634 | IfTuple | If test is a tuple, which is always `True` |
| F701 | BreakOutsideLoop | `break` outside loop |
| F702 | ContinueOutsideLoop | `continue` not properly in loop |
| F704 | YieldOutsideFunction | a `yield` or `yield from` statement outside of a function/method |
| F706 | ReturnOutsideFunction | a `return` statement outside of a function/method |
| F707 | DefaultExceptNotLast | an `except:` block as not the last exception handler |
| F722 | ForwardAnnotationSyntaxError | syntax error in forward annotation '...' |
| F821 | UndefinedName | Undefined name `...` |
| F822 | UndefinedExport | Undefined name `...` in `__all__` |
| F823 | UndefinedLocal | Local variable `...` referenced before assignment |
| F831 | DuplicateArgumentName | Duplicate argument name in function definition |
| F841 | UnusedVariable | Local variable `...` is assigned to but never used |
| F901 | RaiseNotImplemented | `raise NotImplemented` should be `raise NotImplementedError` |
| R001 | UselessObjectInheritance | Class `...` inherits from object |
| R002 | NoAssertEquals | `assertEquals` is deprecated, use `assertEqual` instead |
1. Ruff does not yet support a few Python 3.9 and 3.10 language features, including structural
pattern matching and parenthesized context managers.
2. Flake8 has a plugin architecture and supports writing custom lint rules. (To date, popular Flake8
plugins have been re-implemented within Ruff directly.)
### Which tools does Ruff replace?
Today, Ruff can be used to replace Flake8 when used with any of the following plugins:
- [`pep8-naming`](https://pypi.org/project/pep8-naming/)
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/)
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (10/32)
Ruff also implements the functionality that you get from [`yesqa`](https://github.com/asottile/yesqa),
and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (10/34).
If you're looking to use Ruff, but rely on an unsupported Flake8 plugin, free to file an Issue.
### Do I need to install Rust to use Ruff?
Nope! Ruff is available as [`ruff`](https://pypi.org/project/ruff/) on PyPI:
```shell
pip install ruff
```
Ruff ships with wheels for all major platforms, which enables `pip` to install Ruff without relying
on Rust at all.
### Can I write my own plugins for Ruff?
Ruff does not yet support third-party plugins, though a plugin system is within-scope for the
project. See [#283](https://github.com/charliermarsh/ruff/issues/283) for more.
### Does Ruff support NumPy- or Google-style docstrings?
Yes! To enable a specific docstring convention, start by enabling all `pydocstyle` error codes, and
then selectively disabling based on your [preferred convention](https://www.pydocstyle.org/en/latest/error_codes.html#default-conventions).
For example, if you're coming from `flake8-docstrings`, the following configuration is equivalent to
`--docstring-convention=numpy`:
```toml
[tool.ruff]
extend-select = ["D"]
extend-ignore = [
"D107",
"D203",
"D212",
"D213",
"D402",
"D413",
"D415",
"D416",
"D417",
]
```
Similarly, the following is equivalent to `--docstring-convention=google`:
```toml
[tool.ruff]
extend-select = ["D"]
extend-ignore = [
"D203",
"D204",
"D213",
"D215",
"D400",
"D404",
"D406",
"D407",
"D408",
"D409",
"D413",
]
```
Similarly, the following is equivalent to `--docstring-convention=pep8`:
```toml
[tool.ruff]
extend-select = ["D"]
extend-ignore = [
"D203",
"D212",
"D213",
"D214",
"D215",
"D404",
"D405",
"D406",
"D407",
"D408",
"D409",
"D410",
"D411",
"D413",
"D415",
"D416",
"D417",
]
```
## Development
ruff is written in Rust (1.63.0). You'll need to install the [Rust toolchain](https://www.rust-lang.org/tools/install)
Ruff is written in Rust (1.64.0). You'll need to install the [Rust toolchain](https://www.rust-lang.org/tools/install)
for development.
Assuming you have `cargo` installed, you can run:
```shell
cargo run resources/test/fixtures
cargo fmt
cargo clippy
cargo test
```
## Deployment
For development, we use [nightly Rust](https://rust-lang.github.io/rustup/concepts/channels.html#working-with-nightly-rust):
ruff is distributed on [PyPI](https://pypi.org/project/ruff/), and published via [`maturin`](https://github.com/PyO3/maturin).
```shell
cargo +nightly fmt
cargo +nightly clippy
cargo +nightly test
```
## Releases
Ruff is distributed on [PyPI](https://pypi.org/project/ruff/), and published via [`maturin`](https://github.com/PyO3/maturin).
See: `.github/workflows/release.yaml`.
## Benchmarking
## Benchmarks
First, clone [CPython](https://github.com/python/cpython). It's a large and diverse Python codebase,
which makes it a good target for benchmarking.
@@ -307,9 +737,9 @@ hyperfine --ignore-failure --warmup 5 \
In order, these evaluate:
- ruff
- Ruff
- Pylint
- PyFlakes
- Pyflakes
- autoflake
- pycodestyle
- Flake8
@@ -369,3 +799,8 @@ Summary
## License
MIT
## Contributing
Contributions are welcome and hugely appreciated. To get started, check out the
[contributing guidelines](https://github.com/charliermarsh/ruff/blob/main/CONTRIBUTING.md).

View File

@@ -0,0 +1,15 @@
use std::path::Path;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use ruff::fs;
use ruff::source_code_locator::compute_offsets;
fn criterion_benchmark(c: &mut Criterion) {
let contents = fs::read_file(Path::new("resources/test/fixtures/D.py")).unwrap();
c.bench_function("compute_offsets", |b| {
b.iter(|| compute_offsets(black_box(&contents)))
});
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

2964
crates/flake8_to_ruff/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,22 @@
[package]
name = "flake8-to-ruff"
version = "0.0.96-dev.0"
edition = "2021"
[lib]
name = "flake8_to_ruff"
[dependencies]
anyhow = { version = "1.0.60" }
clap = { version = "4.0.1", features = ["derive"] }
configparser = { version = "3.0.2" }
once_cell = { version = "1.13.1" }
regex = { version = "1.6.0" }
ruff = { path = "../..", default-features = false }
serde = { version = "1.0.143", features = ["derive"] }
serde_json = { version = "1.0.83" }
toml = { version = "0.5.9" }
[dev-dependencies]
[features]

View File

@@ -0,0 +1,47 @@
# flake8-to-ruff
Convert existing Flake8 configuration files (`setup.cfg`, `tox.ini`, or `.flake8`) for use with
[Ruff](https://github.com/charliermarsh/ruff).
Generates a Ruff-compatible `pyproject.toml` section.
## Installation and Usage
### Installation
Available as [`flake8-to-ruff`](https://pypi.org/project/flake8-to-ruff/) on PyPI:
```shell
pip install flake8-to-ruff
```
### Usage
To run Ruff, try any of the following:
```shell
flake8-to-ruff path/to/setup.cfg
flake8-to-ruff path/to/tox.ini
flake8-to-ruff path/to/.flake8
```
## Limitations
1. Ruff only supports a subset of the Flake configuration options. `flake8-to-ruff` will warn on and
ignore unsupported options in the `.flake8` file (or equivalent). (Similarly, Ruff has a few
configuration options that don't exist in Flake8.)
2. Ruff will omit any error codes that are unimplemented or unsupported by Ruff, including error
codes from unsupported plugins. (See the [Ruff README](https://github.com/charliermarsh/ruff#user-content-how-does-ruff-compare-to-flake8)
for the complete list of supported plugins.)
3. `flake8-to-ruff` does not auto-detect your Flake8 plugins, so any reliance on Flake8 plugins that
implicitly enable third-party checks will be ignored. Instead, add those error codes to your
`select` or `extend-select` fields, so that `flake8-to-ruff` can pick them up.
## License
MIT
## Contributing
Contributions are welcome and hugely appreciated. To get started, check out the
[contributing guidelines](https://github.com/charliermarsh/ruff/blob/main/CONTRIBUTING.md).

View File

@@ -0,0 +1,33 @@
[project]
name = "flake8-to-ruff"
keywords = ["automation", "flake8", "pycodestyle", "pyflakes", "pylint", "clippy"]
classifiers = [
"Development Status :: 3 - Alpha",
"Environment :: Console",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3 :: Only",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Software Development :: Quality Assurance",
]
author = "Charlie Marsh"
author_email = "charlie.r.marsh@gmail.com"
description = "Convert existing Flake8 configuration to Ruff."
requires-python = ">=3.7"
[project.urls]
repository = "https://github.com/charliermarsh/ruff#subdirectory=crates/flake8_to_ruff"
[build-system]
requires = ["maturin>=0.13,<0.14"]
build-backend = "maturin"
[tool.maturin]
bindings = "bin"
strip = true

View File

@@ -0,0 +1,243 @@
use std::collections::HashMap;
use anyhow::Result;
use ruff::flake8_quotes::settings::Quote;
use ruff::settings::options::Options;
use ruff::settings::pyproject::Pyproject;
use ruff::{flake8_quotes, pep8_naming};
use crate::parser;
pub fn convert(config: HashMap<String, HashMap<String, Option<String>>>) -> Result<Pyproject> {
// Extract the Flake8 section.
let flake8 = config
.get("flake8")
.expect("Unable to find flake8 section in INI file.");
// Parse each supported option.
let mut options: Options = Default::default();
let mut flake8_quotes: flake8_quotes::settings::Options = Default::default();
let mut pep8_naming: pep8_naming::settings::Options = Default::default();
for (key, value) in flake8 {
if let Some(value) = value {
match key.as_str() {
// flake8
"max-line-length" | "max_line_length" => match value.clone().parse::<usize>() {
Ok(line_length) => options.line_length = Some(line_length),
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
},
"select" => {
options.select = Some(parser::parse_prefix_codes(value.as_ref()));
}
"extend-select" | "extend_select" => {
options.extend_select = Some(parser::parse_prefix_codes(value.as_ref()));
}
"ignore" => {
options.ignore = Some(parser::parse_prefix_codes(value.as_ref()));
}
"extend-ignore" | "extend_ignore" => {
options.extend_ignore = Some(parser::parse_prefix_codes(value.as_ref()));
}
"exclude" => {
options.exclude = Some(parser::parse_strings(value.as_ref()));
}
"extend-exclude" | "extend_exclude" => {
options.extend_exclude = Some(parser::parse_strings(value.as_ref()));
}
"per-file-ignores" | "per_file_ignores" => {
match parser::parse_files_to_codes_mapping(value.as_ref()) {
Ok(per_file_ignores) => {
options.per_file_ignores =
Some(parser::collect_per_file_ignores(per_file_ignores))
}
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
}
}
// flake8-quotes
"quotes" | "inline-quotes" | "inline_quotes" => match value.trim() {
"'" | "single" => flake8_quotes.inline_quotes = Some(Quote::Single),
"\"" | "double" => flake8_quotes.inline_quotes = Some(Quote::Single),
_ => eprintln!("Unexpected '{key}' value: {value}"),
},
"multiline-quotes" | "multiline_quotes" => match value.trim() {
"'" | "single" => flake8_quotes.multiline_quotes = Some(Quote::Single),
"\"" | "double" => flake8_quotes.multiline_quotes = Some(Quote::Single),
_ => eprintln!("Unexpected '{key}' value: {value}"),
},
"docstring-quotes" | "docstring_quotes" => match value.trim() {
"'" | "single" => flake8_quotes.docstring_quotes = Some(Quote::Single),
"\"" | "double" => flake8_quotes.docstring_quotes = Some(Quote::Single),
_ => eprintln!("Unexpected '{key}' value: {value}"),
},
"avoid-escape" | "avoid_escape" => match value.trim() {
"true" => flake8_quotes.avoid_escape = Some(true),
"false" => flake8_quotes.avoid_escape = Some(false),
_ => eprintln!("Unexpected '{key}' value: {value}"),
},
// pep8-naming
"ignore-names" | "ignore_names" => {
pep8_naming.ignore_names = Some(parser::parse_strings(value.as_ref()));
}
"classmethod-decorators" | "classmethod_decorators" => {
pep8_naming.classmethod_decorators =
Some(parser::parse_strings(value.as_ref()));
}
"staticmethod-decorators" | "staticmethod_decorators" => {
pep8_naming.staticmethod_decorators =
Some(parser::parse_strings(value.as_ref()));
}
// Unknown
_ => eprintln!("Skipping unsupported property: {key}"),
}
}
}
if flake8_quotes != Default::default() {
options.flake8_quotes = Some(flake8_quotes);
}
if pep8_naming != Default::default() {
options.pep8_naming = Some(pep8_naming);
}
// Create the pyproject.toml.
Ok(Pyproject::new(options))
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use anyhow::Result;
use ruff::flake8_quotes;
use ruff::settings::options::Options;
use ruff::settings::pyproject::Pyproject;
use crate::converter::convert;
#[test]
fn it_converts_empty() -> Result<()> {
let actual = convert(HashMap::from([("flake8".to_string(), HashMap::from([]))]))?;
let expected = Pyproject::new(Options {
line_length: None,
exclude: None,
extend_exclude: None,
select: None,
extend_select: None,
ignore: None,
extend_ignore: None,
per_file_ignores: None,
dummy_variable_rgx: None,
target_version: None,
flake8_quotes: None,
pep8_naming: None,
});
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn it_converts_dashes() -> Result<()> {
let actual = convert(HashMap::from([(
"flake8".to_string(),
HashMap::from([("max-line-length".to_string(), Some("100".to_string()))]),
)]))?;
let expected = Pyproject::new(Options {
line_length: Some(100),
exclude: None,
extend_exclude: None,
select: None,
extend_select: None,
ignore: None,
extend_ignore: None,
per_file_ignores: None,
dummy_variable_rgx: None,
target_version: None,
flake8_quotes: None,
pep8_naming: None,
});
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn it_converts_underscores() -> Result<()> {
let actual = convert(HashMap::from([(
"flake8".to_string(),
HashMap::from([("max_line_length".to_string(), Some("100".to_string()))]),
)]))?;
let expected = Pyproject::new(Options {
line_length: Some(100),
exclude: None,
extend_exclude: None,
select: None,
extend_select: None,
ignore: None,
extend_ignore: None,
per_file_ignores: None,
dummy_variable_rgx: None,
target_version: None,
flake8_quotes: None,
pep8_naming: None,
});
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn it_ignores_parse_errors() -> Result<()> {
let actual = convert(HashMap::from([(
"flake8".to_string(),
HashMap::from([("max_line_length".to_string(), Some("abc".to_string()))]),
)]))?;
let expected = Pyproject::new(Options {
line_length: None,
exclude: None,
extend_exclude: None,
select: None,
extend_select: None,
ignore: None,
extend_ignore: None,
per_file_ignores: None,
dummy_variable_rgx: None,
target_version: None,
flake8_quotes: None,
pep8_naming: None,
});
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn it_converts_extensions() -> Result<()> {
let actual = convert(HashMap::from([(
"flake8".to_string(),
HashMap::from([("inline-quotes".to_string(), Some("single".to_string()))]),
)]))?;
let expected = Pyproject::new(Options {
line_length: None,
exclude: None,
extend_exclude: None,
select: None,
extend_select: None,
ignore: None,
extend_ignore: None,
per_file_ignores: None,
dummy_variable_rgx: None,
target_version: None,
flake8_quotes: Some(flake8_quotes::settings::Options {
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
multiline_quotes: None,
docstring_quotes: None,
avoid_escape: None,
}),
pep8_naming: None,
});
assert_eq!(actual, expected);
Ok(())
}
}

View File

@@ -0,0 +1,4 @@
#![allow(clippy::collapsible_if, clippy::collapsible_else_if)]
pub mod converter;
mod parser;

View File

@@ -0,0 +1,35 @@
//! Utility to generate Ruff's pyproject.toml section from a Flake8 INI file.
use std::path::PathBuf;
use anyhow::Result;
use clap::Parser;
use configparser::ini::Ini;
use flake8_to_ruff::converter;
#[derive(Parser)]
#[command(
about = "Convert existing Flake8 configuration to Ruff.",
long_about = None
)]
struct Cli {
/// Path to the Flake8 configuration file (e.g., 'setup.cfg', 'tox.ini', or
/// '.flake8').
#[arg(required = true)]
file: PathBuf,
}
fn main() -> Result<()> {
let cli = Cli::parse();
// Read the INI file.
let mut ini = Ini::new_cs();
ini.set_multiline(true);
let config = ini.load(cli.file).map_err(|msg| anyhow::anyhow!(msg))?;
// Create the pyproject.toml.
let pyproject = converter::convert(config)?;
println!("{}", toml::to_string_pretty(&pyproject)?);
Ok(())
}

View File

@@ -0,0 +1,373 @@
use std::collections::BTreeMap;
use std::str::FromStr;
use anyhow::Result;
use once_cell::sync::Lazy;
use regex::Regex;
use ruff::checks_gen::CheckCodePrefix;
use ruff::settings::types::PatternPrefixPair;
static COMMA_SEPARATED_LIST_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"[,\s]").unwrap());
/// Parse a comma-separated list of `CheckCodePrefix` values (e.g.,
/// "F401,E501").
pub fn parse_prefix_codes(value: &str) -> Vec<CheckCodePrefix> {
let mut codes: Vec<CheckCodePrefix> = vec![];
for code in COMMA_SEPARATED_LIST_RE.split(value) {
let code = code.trim();
if code.is_empty() {
continue;
}
if let Ok(code) = CheckCodePrefix::from_str(code) {
codes.push(code);
} else {
eprintln!("Unsupported prefix code: {code}");
}
}
codes
}
/// Parse a comma-separated list of strings (e.g., "__init__.py,__main__.py").
pub fn parse_strings(value: &str) -> Vec<String> {
COMMA_SEPARATED_LIST_RE
.split(value)
.map(|part| part.trim())
.filter(|part| !part.is_empty())
.map(String::from)
.collect()
}
#[derive(Debug)]
struct Token {
token_name: TokenType,
src: String,
}
#[derive(Debug)]
enum TokenType {
Code,
File,
Colon,
Comma,
Ws,
Eof,
}
struct State {
seen_sep: bool,
seen_colon: bool,
filenames: Vec<String>,
codes: Vec<String>,
}
impl State {
fn new() -> Self {
Self {
seen_sep: true,
seen_colon: false,
filenames: vec![],
codes: vec![],
}
}
/// Generate the list of `StrCheckCodePair` pairs for the current state.
fn parse(&self) -> Vec<PatternPrefixPair> {
let mut codes: Vec<PatternPrefixPair> = vec![];
for code in &self.codes {
match CheckCodePrefix::from_str(code) {
Ok(code) => {
for filename in &self.filenames {
codes.push(PatternPrefixPair {
pattern: filename.clone(),
prefix: code.clone(),
});
}
}
Err(_) => eprintln!("Skipping unrecognized prefix: {}", code),
}
}
codes
}
}
/// Tokenize the raw 'files-to-codes' mapping.
fn tokenize_files_to_codes_mapping(value: &str) -> Vec<Token> {
let mut tokens = vec![];
let mut i = 0;
while i < value.len() {
for (token_re, token_name) in [
(
Regex::new(r"([A-Z]+[0-9]*)(?:$|\s|,)").unwrap(),
TokenType::Code,
),
(Regex::new(r"([^\s:,]+)").unwrap(), TokenType::File),
(Regex::new(r"(\s*:\s*)").unwrap(), TokenType::Colon),
(Regex::new(r"(\s*,\s*)").unwrap(), TokenType::Comma),
(Regex::new(r"(\s+)").unwrap(), TokenType::Ws),
] {
if let Some(cap) = token_re.captures(&value[i..]) {
let mat = cap.get(1).unwrap();
if mat.start() == 0 {
tokens.push(Token {
token_name,
src: mat.as_str().to_string().trim().to_string(),
});
i += mat.end();
break;
}
}
}
}
tokens.push(Token {
token_name: TokenType::Eof,
src: "".to_string(),
});
tokens
}
/// Parse a 'files-to-codes' mapping, mimicking Flake8's internal logic.
///
/// See: https://github.com/PyCQA/flake8/blob/7dfe99616fc2f07c0017df2ba5fa884158f3ea8a/src/flake8/utils.py#L45
pub fn parse_files_to_codes_mapping(value: &str) -> Result<Vec<PatternPrefixPair>> {
if value.trim().is_empty() {
return Ok(vec![]);
}
let mut codes: Vec<PatternPrefixPair> = vec![];
let mut state = State::new();
for token in tokenize_files_to_codes_mapping(value) {
if matches!(token.token_name, TokenType::Comma | TokenType::Ws) {
state.seen_sep = true;
} else if !state.seen_colon {
if matches!(token.token_name, TokenType::Colon) {
state.seen_colon = true;
state.seen_sep = true;
} else if state.seen_sep && matches!(token.token_name, TokenType::File) {
state.filenames.push(token.src);
state.seen_sep = false;
} else {
return Err(anyhow::anyhow!("Unexpected token: {:?}", token.token_name));
}
} else {
if matches!(token.token_name, TokenType::Eof) {
codes.extend(state.parse());
state = State::new();
} else if state.seen_sep && matches!(token.token_name, TokenType::Code) {
state.codes.push(token.src);
state.seen_sep = false;
} else if state.seen_sep && matches!(token.token_name, TokenType::File) {
codes.extend(state.parse());
state = State::new();
state.filenames.push(token.src);
state.seen_sep = false;
} else {
return Err(anyhow::anyhow!("Unexpected token: {:?}", token.token_name));
}
}
}
Ok(codes)
}
/// Collect a list of `PatternPrefixPair` structs as a `BTreeMap`.
pub fn collect_per_file_ignores(
pairs: Vec<PatternPrefixPair>,
) -> BTreeMap<String, Vec<CheckCodePrefix>> {
let mut per_file_ignores: BTreeMap<String, Vec<CheckCodePrefix>> = BTreeMap::new();
for pair in pairs {
per_file_ignores
.entry(pair.pattern)
.or_insert_with(Vec::new)
.push(pair.prefix);
}
per_file_ignores
}
#[cfg(test)]
mod tests {
use anyhow::Result;
use ruff::checks_gen::CheckCodePrefix;
use ruff::settings::types::PatternPrefixPair;
use crate::parser::{parse_files_to_codes_mapping, parse_prefix_codes, parse_strings};
#[test]
fn it_parses_prefix_codes() {
let actual = parse_prefix_codes("");
let expected: Vec<CheckCodePrefix> = vec![];
assert_eq!(actual, expected);
let actual = parse_prefix_codes(" ");
let expected: Vec<CheckCodePrefix> = vec![];
assert_eq!(actual, expected);
let actual = parse_prefix_codes("F401");
let expected = vec![CheckCodePrefix::F401];
assert_eq!(actual, expected);
let actual = parse_prefix_codes("F401,");
let expected = vec![CheckCodePrefix::F401];
assert_eq!(actual, expected);
let actual = parse_prefix_codes("F401,E501");
let expected = vec![CheckCodePrefix::F401, CheckCodePrefix::E501];
assert_eq!(actual, expected);
let actual = parse_prefix_codes("F401, E501");
let expected = vec![CheckCodePrefix::F401, CheckCodePrefix::E501];
assert_eq!(actual, expected);
}
#[test]
fn it_parses_strings() {
let actual = parse_strings("");
let expected: Vec<String> = vec![];
assert_eq!(actual, expected);
let actual = parse_strings(" ");
let expected: Vec<String> = vec![];
assert_eq!(actual, expected);
let actual = parse_strings("__init__.py");
let expected = vec!["__init__.py".to_string()];
assert_eq!(actual, expected);
let actual = parse_strings("__init__.py,");
let expected = vec!["__init__.py".to_string()];
assert_eq!(actual, expected);
let actual = parse_strings("__init__.py,__main__.py");
let expected = vec!["__init__.py".to_string(), "__main__.py".to_string()];
assert_eq!(actual, expected);
let actual = parse_strings("__init__.py, __main__.py");
let expected = vec!["__init__.py".to_string(), "__main__.py".to_string()];
assert_eq!(actual, expected);
}
#[test]
fn it_parse_files_to_codes_mapping() -> Result<()> {
let actual = parse_files_to_codes_mapping("")?;
let expected: Vec<PatternPrefixPair> = vec![];
assert_eq!(actual, expected);
let actual = parse_files_to_codes_mapping(" ")?;
let expected: Vec<PatternPrefixPair> = vec![];
assert_eq!(actual, expected);
// Ex) locust
let actual = parse_files_to_codes_mapping(
"per-file-ignores =
locust/test/*: F841
examples/*: F841
*.pyi: E302,E704"
.strip_prefix("per-file-ignores =")
.unwrap(),
)?;
let expected: Vec<PatternPrefixPair> = vec![
PatternPrefixPair {
pattern: "locust/test/*".to_string(),
prefix: CheckCodePrefix::F841,
},
PatternPrefixPair {
pattern: "examples/*".to_string(),
prefix: CheckCodePrefix::F841,
},
];
assert_eq!(actual, expected);
// Ex) celery
let actual = parse_files_to_codes_mapping(
"per-file-ignores =
t/*,setup.py,examples/*,docs/*,extra/*:
D,"
.strip_prefix("per-file-ignores =")
.unwrap(),
)?;
let expected: Vec<PatternPrefixPair> = vec![
PatternPrefixPair {
pattern: "t/*".to_string(),
prefix: CheckCodePrefix::D,
},
PatternPrefixPair {
pattern: "setup.py".to_string(),
prefix: CheckCodePrefix::D,
},
PatternPrefixPair {
pattern: "examples/*".to_string(),
prefix: CheckCodePrefix::D,
},
PatternPrefixPair {
pattern: "docs/*".to_string(),
prefix: CheckCodePrefix::D,
},
PatternPrefixPair {
pattern: "extra/*".to_string(),
prefix: CheckCodePrefix::D,
},
];
assert_eq!(actual, expected);
// Ex) scrapy
let actual = parse_files_to_codes_mapping(
"per-file-ignores =
scrapy/__init__.py:E402
scrapy/core/downloader/handlers/http.py:F401
scrapy/http/__init__.py:F401
scrapy/linkextractors/__init__.py:E402,F401
scrapy/selector/__init__.py:F401
scrapy/spiders/__init__.py:E402,F401
scrapy/utils/url.py:F403,F405
tests/test_loader.py:E741"
.strip_prefix("per-file-ignores =")
.unwrap(),
)?;
let expected: Vec<PatternPrefixPair> = vec![
PatternPrefixPair {
pattern: "scrapy/__init__.py".to_string(),
prefix: CheckCodePrefix::E402,
},
PatternPrefixPair {
pattern: "scrapy/core/downloader/handlers/http.py".to_string(),
prefix: CheckCodePrefix::F401,
},
PatternPrefixPair {
pattern: "scrapy/http/__init__.py".to_string(),
prefix: CheckCodePrefix::F401,
},
PatternPrefixPair {
pattern: "scrapy/linkextractors/__init__.py".to_string(),
prefix: CheckCodePrefix::E402,
},
PatternPrefixPair {
pattern: "scrapy/linkextractors/__init__.py".to_string(),
prefix: CheckCodePrefix::F401,
},
PatternPrefixPair {
pattern: "scrapy/selector/__init__.py".to_string(),
prefix: CheckCodePrefix::F401,
},
PatternPrefixPair {
pattern: "scrapy/spiders/__init__.py".to_string(),
prefix: CheckCodePrefix::E402,
},
PatternPrefixPair {
pattern: "scrapy/spiders/__init__.py".to_string(),
prefix: CheckCodePrefix::F401,
},
PatternPrefixPair {
pattern: "scrapy/utils/url.py".to_string(),
prefix: CheckCodePrefix::F403,
},
PatternPrefixPair {
pattern: "scrapy/utils/url.py".to_string(),
prefix: CheckCodePrefix::F405,
},
PatternPrefixPair {
pattern: "tests/test_loader.py".to_string(),
prefix: CheckCodePrefix::E741,
},
];
assert_eq!(actual, expected);
Ok(())
}
}

View File

@@ -0,0 +1,104 @@
//! Generate the CheckCodePrefix enum.
use std::collections::{BTreeMap, BTreeSet};
use codegen::{Scope, Type, Variant};
use itertools::Itertools;
use ruff::checks::CheckCode;
use strum::IntoEnumIterator;
fn main() {
// Build up a map from prefix to matching CheckCodes.
let mut prefix_to_codes: BTreeMap<String, BTreeSet<CheckCode>> = Default::default();
for check_code in CheckCode::iter() {
let as_ref = check_code.as_ref().to_string();
for i in 1..=as_ref.len() {
let prefix = as_ref[..i].to_string();
let entry = prefix_to_codes
.entry(prefix)
.or_insert_with(|| Default::default());
entry.insert(check_code.clone());
}
}
let mut scope = Scope::new();
// Create the `CheckCodePrefix` definition.
let mut gen = scope
.new_enum("CheckCodePrefix")
.vis("pub")
.derive("EnumString")
.derive("Debug")
.derive("PartialEq")
.derive("Eq")
.derive("Clone")
.derive("Serialize")
.derive("Deserialize");
for (prefix, _) in &prefix_to_codes {
gen = gen.push_variant(Variant::new(prefix.to_string()));
}
// Create the `PrefixSpecificity` definition.
scope
.new_enum("PrefixSpecificity")
.vis("pub")
.derive("PartialEq")
.derive("Eq")
.derive("PartialOrd")
.derive("Ord")
.push_variant(Variant::new("Category"))
.push_variant(Variant::new("Hundreds"))
.push_variant(Variant::new("Tens"))
.push_variant(Variant::new("Explicit"));
// Create the `match` statement, to map from definition to relevant codes.
let mut gen = scope
.new_impl("CheckCodePrefix")
.new_fn("codes")
.arg_ref_self()
.ret(Type::new("Vec<CheckCode>"))
.vis("pub")
.line("match self {");
for (prefix, codes) in &prefix_to_codes {
gen = gen.line(format!(
"CheckCodePrefix::{prefix} => vec![{}],",
codes
.iter()
.map(|code| format!("CheckCode::{}", code.as_ref()))
.join(", ")
));
}
gen.line("}");
// Create the `match` statement, to map from definition to specificity.
let mut gen = scope
.new_impl("CheckCodePrefix")
.new_fn("specificity")
.arg_ref_self()
.ret(Type::new("PrefixSpecificity"))
.vis("pub")
.line("match self {");
for (prefix, _) in &prefix_to_codes {
let specificity = match prefix.len() {
4 => "Explicit",
3 => "Tens",
2 => "Hundreds",
1 => "Category",
_ => panic!("Invalid prefix: {}", prefix),
};
gen = gen.line(format!(
"CheckCodePrefix::{prefix} => PrefixSpecificity::{},",
specificity
));
}
gen.line("}");
println!("//! File automatically generated by examples/generate_check_code_prefix.rs.");
println!();
println!("use serde::{{Serialize, Deserialize}};");
println!("use strum_macros::EnumString;");
println!();
println!("use crate::checks::CheckCode;");
println!();
println!("{}", scope.to_string());
}

View File

@@ -1,19 +1,28 @@
/// Generate a Markdown-compatible table of supported lint rules.
use ruff::checks::{CheckCode, ALL_CHECK_CODES};
//! Generate a Markdown-compatible table of supported lint rules.
use ruff::checks::{CheckCategory, CheckCode};
use strum::IntoEnumIterator;
fn main() {
let mut check_codes: Vec<CheckCode> = ALL_CHECK_CODES.to_vec();
check_codes.sort();
for check_category in CheckCategory::iter() {
println!("### {}", check_category.title());
println!();
println!("| Code | Name | Message |");
println!("| ---- | ----- | ------- |");
for check_code in check_codes {
let check_kind = check_code.kind();
println!(
"| {} | {} | {} |",
check_kind.code().as_str(),
check_kind.name(),
check_kind.body()
);
println!("| Code | Name | Message | Fix |");
println!("| ---- | ---- | ------- | --- |");
for check_code in CheckCode::iter() {
if check_code.category() == check_category {
let check_kind = check_code.kind();
let fix_token = if check_kind.fixable() { "🛠" } else { "" };
println!(
"| {} | {} | {} | {} |",
check_kind.code().as_ref(),
check_kind.as_ref(),
check_kind.summary().replace("|", r"\|"),
fix_token
);
}
}
println!();
}
}

View File

@@ -0,0 +1,25 @@
use std::path::PathBuf;
use anyhow::Result;
use clap::Parser;
use ruff::code_gen::SourceGenerator;
use ruff::fs;
use rustpython_parser::parser;
#[derive(Debug, Parser)]
struct Cli {
#[arg(required = true)]
file: PathBuf,
}
fn main() -> Result<()> {
let cli = Cli::parse();
let contents = fs::read_file(&cli.file)?;
let python_ast = parser::parse_program(&contents, &cli.file.to_string_lossy())?;
let mut generator = SourceGenerator::new();
generator.unparse_suite(&python_ast)?;
println!("{}", generator.generate()?);
Ok(())
}

View File

@@ -1,15 +1,15 @@
/// Print the AST for a given Python file.
//! Print the AST for a given Python file.
use std::path::PathBuf;
use anyhow::Result;
use clap::{Parser, ValueHint};
use rustpython_parser::parser;
use clap::Parser;
use ruff::fs;
use rustpython_parser::parser;
#[derive(Debug, Parser)]
struct Cli {
#[clap(parse(from_os_str), value_hint = ValueHint::FilePath, required = true)]
#[arg(required = true)]
file: PathBuf,
}

View File

@@ -1,15 +1,15 @@
/// Print the token stream for a given Python file.
//! Print the token stream for a given Python file.
use std::path::PathBuf;
use anyhow::Result;
use clap::{Parser, ValueHint};
use rustpython_parser::lexer;
use clap::Parser;
use ruff::fs;
use rustpython_parser::lexer;
#[derive(Debug, Parser)]
struct Cli {
#[clap(parse(from_os_str), value_hint = ValueHint::FilePath, required = true)]
#[arg(required = true)]
file: PathBuf,
}

View File

@@ -8,7 +8,6 @@ classifiers = [
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",

27
resources/test/fixtures/A001.py vendored Normal file
View File

@@ -0,0 +1,27 @@
import some as sum
from some import other as int
print = 1
copyright: 'annotation' = 2
(complex := 3)
float = object = 4
min, max = 5, 6
def bytes():
pass
class slice:
pass
try:
...
except ImportError as ValueError:
...
for memoryview, *bytearray in []:
pass
with open('file') as str, open('file2') as (all, any):
pass
[0 for sum in ()]

9
resources/test/fixtures/A002.py vendored Normal file
View File

@@ -0,0 +1,9 @@
def func1(str, /, type, *complex, Exception, **getattr):
pass
async def func2(bytes):
pass
map([], lambda float: ...)

8
resources/test/fixtures/A003.py vendored Normal file
View File

@@ -0,0 +1,8 @@
class MyClass:
ImportError = 4
def __init__(self):
self.float = 5 # is fine
def str(self):
pass

20
resources/test/fixtures/B002.py vendored Normal file
View File

@@ -0,0 +1,20 @@
"""
Should emit:
B002 - on lines 15 and 20
"""
def this_is_all_fine(n):
x = n + 1
y = 1 + n
z = +x + y
return +z
def this_is_buggy(n):
x = ++n
return x
def this_is_buggy_too(n):
return ++n

187
resources/test/fixtures/B006_B008.py vendored Normal file
View File

@@ -0,0 +1,187 @@
import collections
import datetime as dt
import logging
import operator
import random
import re
import time
import types
from operator import attrgetter, itemgetter, methodcaller
from types import MappingProxyType
# B006
# Allow immutable literals/calls/comprehensions
def this_is_okay(value=(1, 2, 3)):
...
async def and_this_also(value=tuple()):
pass
def frozenset_also_okay(value=frozenset()):
pass
def mappingproxytype_okay(
value=MappingProxyType({}), value2=types.MappingProxyType({})
):
pass
def re_compile_ok(value=re.compile("foo")):
pass
def operators_ok(
v=operator.attrgetter("foo"),
v2=operator.itemgetter("foo"),
v3=operator.methodcaller("foo"),
):
pass
def operators_ok_unqualified(
v=attrgetter("foo"),
v2=itemgetter("foo"),
v3=methodcaller("foo"),
):
pass
def kwonlyargs_immutable(*, value=()):
...
# Flag mutable literals/comprehensions
def this_is_wrong(value=[1, 2, 3]):
...
def this_is_also_wrong(value={}):
...
def and_this(value=set()):
...
def this_too(value=collections.OrderedDict()):
...
async def async_this_too(value=collections.defaultdict()):
...
def dont_forget_me(value=collections.deque()):
...
# N.B. we're also flagging the function call in the comprehension
def list_comprehension_also_not_okay(default=[i**2 for i in range(3)]):
pass
def dict_comprehension_also_not_okay(default={i: i**2 for i in range(3)}):
pass
def set_comprehension_also_not_okay(default={i**2 for i in range(3)}):
pass
def kwonlyargs_mutable(*, value=[]):
...
# Recommended approach for mutable defaults
def do_this_instead(value=None):
if value is None:
value = set()
# B008
# Flag function calls as default args (including if they are part of a sub-expression)
def in_fact_all_calls_are_wrong(value=time.time()):
...
def f(when=dt.datetime.now() + dt.timedelta(days=7)):
pass
def can_even_catch_lambdas(a=(lambda x: x)()):
...
# Recommended approach for function calls as default args
LOGGER = logging.getLogger(__name__)
def do_this_instead_of_calls_in_defaults(logger=LOGGER):
# That makes it more obvious that this one value is reused.
...
# Handle inf/infinity/nan special case
def float_inf_okay(value=float("inf")):
pass
def float_infinity_okay(value=float("infinity")):
pass
def float_plus_infinity_okay(value=float("+infinity")):
pass
def float_minus_inf_okay(value=float("-inf")):
pass
def float_nan_okay(value=float("nan")):
pass
def float_minus_NaN_okay(value=float("-NaN")):
pass
def float_infinity_literal(value=float("1e999")):
pass
# But don't allow standard floats
def float_int_is_wrong(value=float(3)):
pass
def float_str_not_inf_or_nan_is_wrong(value=float("3.14")):
pass
# B006 and B008
# We should handle arbitrary nesting of these B008.
def nested_combo(a=[float(3), dt.datetime.now()]):
pass
# Don't flag nested B006 since we can't guarantee that
# it isn't made mutable by the outer operation.
def no_nested_b006(a=map(lambda s: s.upper(), ["a", "b", "c"])):
pass
# B008-ception.
def nested_b008(a=random.randint(0, dt.datetime.now().year)):
pass
# Ignore lambda contents since they are evaluated at call time.
def foo(f=lambda x: print(x)):
f(1)

31
resources/test/fixtures/B007.py vendored Normal file
View File

@@ -0,0 +1,31 @@
for i in range(10):
print(i)
print(i) # name no longer defined on Python 3; no warning yet
for i in range(10): # name not used within the loop; B007
print(10)
print(i) # name no longer defined on Python 3; no warning yet
for _ in range(10): # _ is okay for a throw-away variable
print(10)
for i in range(10):
for j in range(10):
for k in range(10): # k not used, i and j used transitively
print(i + j)
def strange_generator():
for i in range(10):
for j in range(10):
for k in range(10):
for l in range(10):
yield i, (j, (k, l))
for i, (j, (k, l)) in strange_generator(): # i, k not used
print(j, l)

10
resources/test/fixtures/B011.py vendored Normal file
View File

@@ -0,0 +1,10 @@
"""
Should emit:
B011 - on line 8
B011 - on line 10
"""
assert 1 != 2
assert False
assert 1 != 2, "message"
assert False, "message"

8
resources/test/fixtures/B013.py vendored Normal file
View File

@@ -0,0 +1,8 @@
try:
pass
except (ValueError,):
pass
except AttributeError:
pass
except (ImportError, TypeError):
pass

76
resources/test/fixtures/B014.py vendored Normal file
View File

@@ -0,0 +1,76 @@
"""
Should emit:
B014 - on lines 11, 17, 28, 42, 49, 56, and 74.
"""
import binascii
import re
try:
pass
except (Exception, TypeError):
# TypeError is a subclass of Exception, so it doesn't add anything
pass
try:
pass
except (OSError, OSError) as err:
# Duplicate exception types are useless
pass
class MyError(Exception):
pass
try:
pass
except (MyError, MyError):
# Detect duplicate non-builtin errors
pass
try:
pass
except (MyError, Exception) as e:
# Don't assume that we're all subclasses of Exception
pass
try:
pass
except (MyError, BaseException) as e:
# But we *can* assume that everything is a subclass of BaseException
pass
try:
pass
except (re.error, re.error):
# Duplicate exception types as attributes
pass
try:
pass
except (IOError, EnvironmentError, OSError):
# Detect if a primary exception and any its aliases are present.
#
# Since Python 3.3, IOError, EnvironmentError, WindowsError, mmap.error,
# socket.error and select.error are aliases of OSError. See PEP 3151 for
# more info.
pass
try:
pass
except (MyException, NotImplemented):
# NotImplemented is not an exception, let's not crash on it.
pass
try:
pass
except (ValueError, binascii.Error):
# binascii.Error is a subclass of ValueError.
pass

36
resources/test/fixtures/B017.py vendored Normal file
View File

@@ -0,0 +1,36 @@
"""
Should emit:
B017 - on lines 20
"""
import asyncio
import unittest
CONSTANT = True
def something_else() -> None:
for i in (1, 2, 3):
print(i)
class Foo:
pass
class Foobar(unittest.TestCase):
def evil_raises(self) -> None:
with self.assertRaises(Exception):
raise Exception("Evil I say!")
def context_manager_raises(self) -> None:
with self.assertRaises(Exception) as ex:
raise Exception("Context manager is good")
self.assertEqual("Context manager is good", str(ex.exception))
def regex_raises(self) -> None:
with self.assertRaisesRegex(Exception, "Regex is good"):
raise Exception("Regex is good")
def raises_with_absolute_reference(self):
with self.assertRaises(asyncio.CancelledError):
Foo()

38
resources/test/fixtures/B025.py vendored Normal file
View File

@@ -0,0 +1,38 @@
"""
Should emit:
B025 - on lines 15, 22, 31
"""
import pickle
try:
a = 1
except ValueError:
a = 2
finally:
a = 3
try:
a = 1
except ValueError:
a = 2
except ValueError:
a = 2
try:
a = 1
except pickle.PickleError:
a = 2
except ValueError:
a = 2
except pickle.PickleError:
a = 2
try:
a = 1
except (ValueError, TypeError):
a = 2
except ValueError:
a = 2
except (OSError, TypeError):
a = 2

4
resources/test/fixtures/C400.py vendored Normal file
View File

@@ -0,0 +1,4 @@
x = list(x for x in range(3))
x = list(
x for x in range(3)
)

4
resources/test/fixtures/C401.py vendored Normal file
View File

@@ -0,0 +1,4 @@
x = set(x for x in range(3))
x = set(
x for x in range(3)
)

5
resources/test/fixtures/C402.py vendored Normal file
View File

@@ -0,0 +1,5 @@
dict((x, x) for x in range(3))
dict(
(x, x) for x in range(3)
)
dict(((x, x) for x in range(3)), z=3)

4
resources/test/fixtures/C403.py vendored Normal file
View File

@@ -0,0 +1,4 @@
s = set([x for x in range(3)])
s = set(
[x for x in range(3)]
)

2
resources/test/fixtures/C404.py vendored Normal file
View File

@@ -0,0 +1,2 @@
dict([(i, i) for i in range(3)])
dict([(i, i) for i in range(3)], z=4)

5
resources/test/fixtures/C405.py vendored Normal file
View File

@@ -0,0 +1,5 @@
s1 = set([1, 2])
s2 = set((1, 2))
s3 = set([])
s4 = set(())
s5 = set()

5
resources/test/fixtures/C406.py vendored Normal file
View File

@@ -0,0 +1,5 @@
d1 = dict([(1, 2)])
d2 = dict(((1, 2),))
d3 = dict([])
d4 = dict(())
d5 = dict()

5
resources/test/fixtures/C408.py vendored Normal file
View File

@@ -0,0 +1,5 @@
t = tuple()
l = list()
d1 = dict()
d2 = dict(a=1)
d3 = dict(**d2)

10
resources/test/fixtures/C409.py vendored Normal file
View File

@@ -0,0 +1,10 @@
t1 = tuple([])
t2 = tuple([1, 2])
t3 = tuple((1, 2))
t4 = tuple([
1,
2
])
t5 = tuple(
(1, 2)
)

4
resources/test/fixtures/C410.py vendored Normal file
View File

@@ -0,0 +1,4 @@
l1 = list([1, 2])
l2 = list((1, 2))
l3 = list([])
l4 = list(())

2
resources/test/fixtures/C411.py vendored Normal file
View File

@@ -0,0 +1,2 @@
x = [1, 2, 3]
list([i for i in x])

4
resources/test/fixtures/C413.py vendored Normal file
View File

@@ -0,0 +1,4 @@
x = [2, 3, 1]
list(sorted(x))
reversed(sorted(x))
reversed(sorted(x, reverse=True))

14
resources/test/fixtures/C414.py vendored Normal file
View File

@@ -0,0 +1,14 @@
x = [1, 2, 3]
list(list(x))
list(tuple(x))
tuple(list(x))
tuple(tuple(x))
set(set(x))
set(list(x))
set(tuple(x))
set(sorted(x))
set(reversed(x))
sorted(list(x))
sorted(tuple(x))
sorted(sorted(x))
sorted(reversed(x))

9
resources/test/fixtures/C415.py vendored Normal file
View File

@@ -0,0 +1,9 @@
lst = [2, 1, 3]
a = set(lst[::-1])
b = reversed(lst[::-1])
c = sorted(lst[::-1])
d = sorted(lst[::-1], reverse=True)
e = set(lst[2:-1])
f = set(lst[:1:-1])
g = set(lst[::1])
h = set(lst[::-2])

6
resources/test/fixtures/C416.py vendored Normal file
View File

@@ -0,0 +1,6 @@
x = [1, 2, 3]
[i for i in x]
{i for i in x}
[i for i in x if i > 1]
[i for i in x for j in x]

6
resources/test/fixtures/C417.py vendored Normal file
View File

@@ -0,0 +1,6 @@
nums = [1, 2, 3]
map(lambda x: x + 1, nums)
map(lambda x: str(x), nums)
list(map(lambda x: x * 2, nums))
set(map(lambda x: x % 2 == 0, nums))
dict(map(lambda v: (v, v**2), nums))

534
resources/test/fixtures/D.py vendored Normal file
View File

@@ -0,0 +1,534 @@
# No docstring, so we can test D100
from functools import wraps
import os
from .expected import Expectation
from typing import overload
expectation = Expectation()
expect = expectation.expect
expect('class_', 'D101: Missing docstring in public class')
class class_:
expect('meta', 'D419: Docstring is empty')
class meta:
""""""
@expect('D102: Missing docstring in public method')
def method(self=None):
pass
def _ok_since_private(self=None):
pass
@overload
def overloaded_method(self, a: int) -> str:
...
@overload
def overloaded_method(self, a: str) -> str:
"""Foo bar documentation."""
...
def overloaded_method(a):
"""Foo bar documentation."""
return str(a)
expect('overloaded_method',
"D418: Function/ Method decorated with @overload"
" shouldn't contain a docstring")
@property
def foo(self):
"""The foo of the thing, which isn't in imperitive mood."""
return "hello"
@expect('D102: Missing docstring in public method')
def __new__(self=None):
pass
@expect('D107: Missing docstring in __init__')
def __init__(self=None):
pass
@expect('D105: Missing docstring in magic method')
def __str__(self=None):
pass
@expect('D102: Missing docstring in public method')
def __call__(self=None, x=None, y=None, z=None):
pass
@expect('D419: Docstring is empty')
def function():
""" """
def ok_since_nested():
pass
@expect('D419: Docstring is empty')
def nested():
''
def function_with_nesting():
"""Foo bar documentation."""
@overload
def nested_overloaded_func(a: int) -> str:
...
@overload
def nested_overloaded_func(a: str) -> str:
"""Foo bar documentation."""
...
def nested_overloaded_func(a):
"""Foo bar documentation."""
return str(a)
expect('nested_overloaded_func',
"D418: Function/ Method decorated with @overload"
" shouldn't contain a docstring")
@overload
def overloaded_func(a: int) -> str:
...
@overload
def overloaded_func(a: str) -> str:
"""Foo bar documentation."""
...
def overloaded_func(a):
"""Foo bar documentation."""
return str(a)
expect('overloaded_func',
"D418: Function/ Method decorated with @overload"
" shouldn't contain a docstring")
@expect('D200: One-line docstring should fit on one line with quotes '
'(found 3)')
@expect('D212: Multi-line docstring summary should start at the first line')
def asdlkfasd():
"""
Wrong.
"""
@expect('D201: No blank lines allowed before function docstring (found 1)')
def leading_space():
"""Leading space."""
@expect('D202: No blank lines allowed after function docstring (found 1)')
def trailing_space():
"""Leading space."""
pass
@expect('D201: No blank lines allowed before function docstring (found 1)')
@expect('D202: No blank lines allowed after function docstring (found 1)')
def trailing_and_leading_space():
"""Trailing and leading space."""
pass
expect('LeadingSpaceMissing',
'D203: 1 blank line required before class docstring (found 0)')
class LeadingSpaceMissing:
"""Leading space missing."""
expect('WithLeadingSpace',
'D211: No blank lines allowed before class docstring (found 1)')
class WithLeadingSpace:
"""With leading space."""
expect('TrailingSpace',
'D204: 1 blank line required after class docstring (found 0)')
expect('TrailingSpace',
'D211: No blank lines allowed before class docstring (found 1)')
class TrailingSpace:
"""TrailingSpace."""
pass
expect('LeadingAndTrailingSpaceMissing',
'D203: 1 blank line required before class docstring (found 0)')
expect('LeadingAndTrailingSpaceMissing',
'D204: 1 blank line required after class docstring (found 0)')
class LeadingAndTrailingSpaceMissing:
"""Leading and trailing space missing."""
pass
@expect('D205: 1 blank line required between summary line and description '
'(found 0)')
@expect('D213: Multi-line docstring summary should start at the second line')
def multi_line_zero_separating_blanks():
"""Summary.
Description.
"""
@expect('D205: 1 blank line required between summary line and description '
'(found 2)')
@expect('D213: Multi-line docstring summary should start at the second line')
def multi_line_two_separating_blanks():
"""Summary.
Description.
"""
@expect('D213: Multi-line docstring summary should start at the second line')
def multi_line_one_separating_blanks():
"""Summary.
Description.
"""
@expect('D207: Docstring is under-indented')
@expect('D213: Multi-line docstring summary should start at the second line')
def asdfsdf():
"""Summary.
Description.
"""
@expect('D207: Docstring is under-indented')
@expect('D213: Multi-line docstring summary should start at the second line')
def asdsdfsdffsdf():
"""Summary.
Description.
"""
@expect('D208: Docstring is over-indented')
@expect('D213: Multi-line docstring summary should start at the second line')
def asdfsdsdf24():
"""Summary.
Description.
"""
@expect('D208: Docstring is over-indented')
@expect('D213: Multi-line docstring summary should start at the second line')
def asdfsdsdfsdf24():
"""Summary.
Description.
"""
@expect('D208: Docstring is over-indented')
@expect('D213: Multi-line docstring summary should start at the second line')
def asdfsdfsdsdsdfsdf24():
"""Summary.
Description.
"""
@expect('D209: Multi-line docstring closing quotes should be on a separate '
'line')
@expect('D213: Multi-line docstring summary should start at the second line')
def asdfljdf24():
"""Summary.
Description."""
@expect('D210: No whitespaces allowed surrounding docstring text')
def endswith():
"""Whitespace at the end. """
@expect('D210: No whitespaces allowed surrounding docstring text')
def around():
""" Whitespace at everywhere. """
@expect('D210: No whitespaces allowed surrounding docstring text')
@expect('D213: Multi-line docstring summary should start at the second line')
def multiline():
""" Whitespace at the beginning.
This is the end.
"""
@expect('D300: Use """triple double quotes""" (found \'\'\'-quotes)')
def triple_single_quotes_raw():
r'''Summary.'''
@expect('D300: Use """triple double quotes""" (found \'\'\'-quotes)')
def triple_single_quotes_raw_uppercase():
R'''Summary.'''
@expect('D300: Use """triple double quotes""" (found \'-quotes)')
def single_quotes_raw():
r'Summary.'
@expect('D300: Use """triple double quotes""" (found \'-quotes)')
def single_quotes_raw_uppercase():
R'Summary.'
@expect('D300: Use """triple double quotes""" (found \'-quotes)')
@expect('D301: Use r""" if any backslashes in a docstring')
def single_quotes_raw_uppercase_backslash():
R'Sum\mary.'
@expect('D301: Use r""" if any backslashes in a docstring')
def double_quotes_backslash():
"""Sum\\mary."""
@expect('D301: Use r""" if any backslashes in a docstring')
def double_quotes_backslash_uppercase():
R"""Sum\\mary."""
@expect('D213: Multi-line docstring summary should start at the second line')
def exceptions_of_D301():
"""Exclude some backslashes from D301.
In particular, line continuations \
and unicode literals \u0394 and \N{GREEK CAPITAL LETTER DELTA}.
They are considered to be intentionally unescaped.
"""
@expect("D400: First line should end with a period (not 'y')")
@expect("D415: First line should end with a period, question mark, "
"or exclamation point (not 'y')")
def lwnlkjl():
"""Summary"""
@expect("D401: First line should be in imperative mood "
"(perhaps 'Return', not 'Returns')")
def liouiwnlkjl():
"""Returns foo."""
@expect("D401: First line should be in imperative mood; try rephrasing "
"(found 'Constructor')")
def sdgfsdg23245():
"""Constructor for a foo."""
@expect("D401: First line should be in imperative mood; try rephrasing "
"(found 'Constructor')")
def sdgfsdg23245777():
"""Constructor."""
@expect('D402: First line should not be the function\'s "signature"')
def foobar():
"""Signature: foobar()."""
@expect('D213: Multi-line docstring summary should start at the second line')
def new_209():
"""First line.
More lines.
"""
pass
@expect('D213: Multi-line docstring summary should start at the second line')
def old_209():
"""One liner.
Multi-line comments. OK to have extra blank line
"""
@expect("D103: Missing docstring in public function")
def oneliner_d102(): return
@expect("D400: First line should end with a period (not 'r')")
@expect("D415: First line should end with a period, question mark,"
" or exclamation point (not 'r')")
def oneliner_withdoc(): """One liner"""
def ignored_decorator(func): # noqa: D400,D401,D415
"""Runs something"""
func()
pass
def decorator_for_test(func): # noqa: D400,D401,D415
"""Runs something"""
func()
pass
@ignored_decorator
def oneliner_ignored_decorator(): """One liner"""
@decorator_for_test
@expect("D400: First line should end with a period (not 'r')")
@expect("D415: First line should end with a period, question mark,"
" or exclamation point (not 'r')")
def oneliner_with_decorator_expecting_errors(): """One liner"""
@decorator_for_test
def valid_oneliner_with_decorator(): """One liner."""
@expect("D207: Docstring is under-indented")
@expect('D213: Multi-line docstring summary should start at the second line')
def docstring_start_in_same_line(): """First Line.
Second Line
"""
def function_with_lambda_arg(x=lambda y: y):
"""Wrap the given lambda."""
@expect('D213: Multi-line docstring summary should start at the second line')
def a_following_valid_function(x=None):
"""Check for a bug where the previous function caused an assertion.
The assertion was caused in the next function, so this one is necessary.
"""
def outer_function():
"""Do something."""
def inner_function():
"""Do inner something."""
return 0
@expect("D400: First line should end with a period (not 'g')")
@expect("D401: First line should be in imperative mood "
"(perhaps 'Run', not 'Runs')")
@expect("D415: First line should end with a period, question mark, "
"or exclamation point (not 'g')")
def docstring_bad():
"""Runs something"""
pass
def docstring_bad_ignore_all(): # noqa
"""Runs something"""
pass
def docstring_bad_ignore_one(): # noqa: D400,D401,D415
"""Runs something"""
pass
@expect("D401: First line should be in imperative mood "
"(perhaps 'Run', not 'Runs')")
def docstring_ignore_some_violations_but_catch_D401(): # noqa: E501,D400,D415
"""Runs something"""
pass
@expect(
"D401: First line should be in imperative mood "
"(perhaps 'Initiate', not 'Initiates')"
)
def docstring_initiates():
"""Initiates the process."""
@expect(
"D401: First line should be in imperative mood "
"(perhaps 'Initialize', not 'Initializes')"
)
def docstring_initializes():
"""Initializes the process."""
@wraps(docstring_bad_ignore_one)
def bad_decorated_function():
"""Bad (E501) but decorated"""
pass
def valid_google_string(): # noqa: D400
"""Test a valid something!"""
@expect("D415: First line should end with a period, question mark, "
"or exclamation point (not 'g')")
def bad_google_string(): # noqa: D400
"""Test a valid something"""
# This is reproducing a bug where AttributeError is raised when parsing class
# parameters as functions for Google / Numpy conventions.
class Blah: # noqa: D203,D213
"""A Blah.
Parameters
----------
x : int
"""
def __init__(self, x):
pass
expect(os.path.normcase(__file__ if __file__[-1] != 'c' else __file__[:-1]),
'D100: Missing docstring in public module')

View File

@@ -1,4 +1,8 @@
"""Top-level docstring."""
__all__ = ["y"]
__version__: str = "0.1.0"
import a
try:

View File

@@ -5,5 +5,12 @@
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
"""
_ = """Lorem ipsum dolor sit amet.
https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
""" # noqa: E501
_ = "---------------------------------------------------------------------------AAAAAAA"
_ = "---------------------------------------------------------------------------亜亜亜亜亜亜亜"

View File

@@ -1,41 +0,0 @@
from __future__ import all_feature_names
import os
import functools
from datetime import datetime
from collections import (
Counter,
OrderedDict,
namedtuple,
)
import multiprocessing.pool
import multiprocessing.process
import logging.config
import logging.handlers
from typing import TYPING_CHECK, NamedTuple, Dict, Type, TypeVar, List, Set, Union, cast
from blah import ClassA, ClassB, ClassC
if TYPING_CHECK:
from models import Fruit, Nut, Vegetable
class X:
datetime: datetime
foo: Type["NamedTuple"]
def a(self) -> "namedtuple":
x = os.environ["1"]
y = Counter()
z = multiprocessing.pool.ThreadPool()
__all__ = ["ClassA"] + ["ClassB"]
__all__ += ["ClassC"]
X = TypeVar("X")
Y = TypeVar("Y", bound="Dict")
Z = TypeVar("Z", "List", "Set")
a = list["Fruit"]
b = Union["Nut", None]
c = cast("Vegetable", b)

88
resources/test/fixtures/F401_0.py vendored Normal file
View File

@@ -0,0 +1,88 @@
from __future__ import all_feature_names
import functools, os
from datetime import datetime
from collections import (
Counter,
OrderedDict,
namedtuple,
)
import multiprocessing.pool
import multiprocessing.process
import logging.config
import logging.handlers
from typing import (
TYPE_CHECKING,
NamedTuple,
Dict,
Type,
TypeVar,
List,
Set,
Union,
cast,
)
from dataclasses import MISSING, field
from blah import ClassA, ClassB, ClassC
if TYPE_CHECKING:
from models import Fruit, Nut, Vegetable
if TYPE_CHECKING:
import shelve
import importlib
if TYPE_CHECKING:
"""Hello, world!"""
import pathlib
z = 1
class X:
datetime: datetime
foo: Type["NamedTuple"]
def a(self) -> "namedtuple":
x = os.environ["1"]
y = Counter()
z = multiprocessing.pool.ThreadPool()
def b(self) -> None:
import pickle
__all__ = ["ClassA"] + ["ClassB"]
__all__ += ["ClassC"]
X = TypeVar("X")
Y = TypeVar("Y", bound="Dict")
Z = TypeVar("Z", "List", "Set")
a = list["Fruit"]
b = Union["""Nut""", None]
c = cast("Vegetable", b)
Field = lambda default=MISSING: field(default=default)
# Test: access a sub-importation via an alias.
import pyarrow as pa
import pyarrow.csv
print(pa.csv.read_csv("test.csv"))
# Test: referencing an import via TypeAlias.
import sys
import numpy as np
if sys.version_info >= (3, 10):
from typing import TypeAlias
else:
from typing_extensions import TypeAlias
CustomInt: TypeAlias = "np.int8 | np.int16"

5
resources/test/fixtures/F401_1.py vendored Normal file
View File

@@ -0,0 +1,5 @@
"""Access a sub-importation via an alias."""
import pyarrow as pa
import pyarrow.csv
print(pa.csv.read_csv("test.csv"))

12
resources/test/fixtures/F401_2.py vendored Normal file
View File

@@ -0,0 +1,12 @@
"""Test: referencing an import via TypeAlias."""
import sys
import numpy as np
if sys.version_info >= (3, 10):
from typing import TypeAlias
else:
from typing_extensions import TypeAlias
CustomInt: TypeAlias = "np.int8 | np.int16"

14
resources/test/fixtures/F401_3.py vendored Normal file
View File

@@ -0,0 +1,14 @@
"""Test: referencing an import via TypeAlias (with future annotations)."""
from __future__ import annotations
import sys
import numpy as np
if sys.version_info >= (3, 10):
from typing import TypeAlias
else:
from typing_extensions import TypeAlias
CustomInt: TypeAlias = np.int8 | np.int16

14
resources/test/fixtures/F401_4.py vendored Normal file
View File

@@ -0,0 +1,14 @@
"""Test: referencing an import via TypeAlias (with future annotations and quotes)."""
from __future__ import annotations
import sys
import numpy as np
if sys.version_info >= (3, 10):
from typing import TypeAlias
else:
from typing_extensions import TypeAlias
CustomInt: TypeAlias = "np.int8 | np.int16"

5
resources/test/fixtures/F401_5.py vendored Normal file
View File

@@ -0,0 +1,5 @@
"""Test: removal of multi-segment and aliases imports."""
from a.b import c
from d.e import f as g
import h.i
import j.k as l

View File

@@ -1,4 +1,3 @@
from __future__ import print_function
"""Docstring"""
from __future__ import absolute_import

View File

@@ -89,3 +89,45 @@ A = (
f'B'
f'{B}'
)
from typing import Annotated, Literal
def arbitrary_callable() -> None:
...
class PEP593Test:
field: Annotated[
int,
"base64",
arbitrary_callable(),
123,
(1, 2, 3),
]
field_with_stringified_type: Annotated[
"PEP593Test",
123,
]
field_with_undefined_stringified_type: Annotated[
"PEP593Test123",
123,
]
field_with_nested_subscript: Annotated[
dict[Literal["foo"], str],
123,
]
field_with_undefined_nested_subscript: Annotated[
dict["foo", "bar"], # Expected to fail as undefined.
123,
]
def in_ipython_notebook() -> bool:
try:
# autoimported by notebooks
get_ipython() # type: ignore[name-defined]
except NameError:
return False # not in notebook
return True

30
resources/test/fixtures/F821_1.py vendored Normal file
View File

@@ -0,0 +1,30 @@
"""Test: typing module imports."""
from foo import cast
# OK
x = cast("Model")
from typing import cast
# F821 Undefined name `Model`
x = cast("Model", x)
import typing
# F821 Undefined name `Model`
x = typing.cast("Model", x)
from typing import Pattern
# F821 Undefined name `Model`
x = Pattern["Model"]
from typing.re import Match
# F821 Undefined name `Model`
x = Match["Model"]

18
resources/test/fixtures/F821_2.py vendored Normal file
View File

@@ -0,0 +1,18 @@
"""Test: typing_extensions module imports."""
from foo import Literal
# F821 Undefined name `Model`
x: Literal["Model"]
from typing_extensions import Literal
# OK
x: Literal["Model"]
import typing_extensions
# OK
x: typing_extensions.Literal["Model"]

View File

@@ -10,13 +10,13 @@ except ValueError as e:
print(e)
def f():
def f1():
x = 1
y = 2
z = x + y
def g():
def f2():
foo = (1, 2)
(a, b) = (1, 2)
@@ -26,6 +26,12 @@ def g():
(x, y) = baz = bar
def h():
def f3():
locals()
x = 1
def f4():
_ = 1
__ = 1
_discarded = 1

60
resources/test/fixtures/M001.py vendored Normal file
View File

@@ -0,0 +1,60 @@
def f() -> None:
# Valid
a = 1 # noqa
# Valid
b = 2 # noqa: F841
# Invalid
c = 1 # noqa
print(c)
# Invalid
d = 1 # noqa: E501
# Invalid
d = 1 # noqa: F841, E501
# Invalid (and unimplemented)
d = 1 # noqa: F841, W191
# Valid
_ = """Lorem ipsum dolor sit amet.
https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
""" # noqa: E501
# Valid
_ = """Lorem ipsum dolor sit amet.
https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
""" # noqa
# Invalid
_ = """Lorem ipsum dolor sit amet.
https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
""" # noqa: E501, F841
# Invalid
_ = """Lorem ipsum dolor sit amet.
https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.
""" # noqa: E501
# Invalid
_ = """Lorem ipsum dolor sit amet.
https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.
""" # noqa

34
resources/test/fixtures/N801.py vendored Normal file
View File

@@ -0,0 +1,34 @@
class bad:
pass
class _bad:
pass
class bad_class:
pass
class Bad_Class:
pass
class BAD_CLASS:
pass
class Good:
pass
class _Good:
pass
class GoodClass:
pass
class GOOD:
pass

41
resources/test/fixtures/N802.py vendored Normal file
View File

@@ -0,0 +1,41 @@
import unittest
def Bad():
pass
def _Bad():
pass
def BAD():
pass
def BAD_FUNC():
pass
def good():
pass
def _good():
pass
def good_func():
pass
def tearDownModule():
pass
class Test(unittest.TestCase):
def tearDown(self):
return super().tearDown()
def testTest(self):
assert True

7
resources/test/fixtures/N803.py vendored Normal file
View File

@@ -0,0 +1,7 @@
def func(a, A):
return a, A
class Class:
def method(self, a, A):
return a, A

19
resources/test/fixtures/N804.py vendored Normal file
View File

@@ -0,0 +1,19 @@
class Class:
@classmethod
def bad_class_method(this):
pass
@classmethod
def good_class_method(cls):
pass
def method(self):
pass
@staticmethod
def static_method(x):
return x
def func(x):
return x

26
resources/test/fixtures/N805.py vendored Normal file
View File

@@ -0,0 +1,26 @@
import random
class Class:
def bad_method(this):
pass
if random.random(0, 2) == 0:
def extra_bad_method(this):
pass
def good_method(self):
pass
@classmethod
def class_method(cls):
pass
@staticmethod
def static_method(x):
return x
def func(x):
return x

4
resources/test/fixtures/N806.py vendored Normal file
View File

@@ -0,0 +1,4 @@
def f():
lower = 0
Camel = 0
CONSTANT = 0

15
resources/test/fixtures/N807.py vendored Normal file
View File

@@ -0,0 +1,15 @@
def __bad__():
pass
def __good():
pass
def good__():
pass
class Class:
def __good__(self):
pass

3
resources/test/fixtures/N811.py vendored Normal file
View File

@@ -0,0 +1,3 @@
import mod.CONST as const
from mod import CONSTANT as constant
from mod import ANOTHER_CONSTANT as another_constant

3
resources/test/fixtures/N812.py vendored Normal file
View File

@@ -0,0 +1,3 @@
import modl.lowercase as Lower
from mod import lowercase as Lowercase
from mod import another_lowercase as AnotherLowercase

3
resources/test/fixtures/N813.py vendored Normal file
View File

@@ -0,0 +1,3 @@
import mod.Camel as camel
from mod import CamelCase as camelcase
from mod import AnotherCamelCase as another_camelcase

3
resources/test/fixtures/N814.py vendored Normal file
View File

@@ -0,0 +1,3 @@
import mod.Camel as CAMEL
from mod import CamelCase as CAMELCASE
from mod import AnotherCamelCase as ANOTHER_CAMELCASE

6
resources/test/fixtures/N815.py vendored Normal file
View File

@@ -0,0 +1,6 @@
class C:
lower = 0
CONSTANT = 0
mixedCase = 0
_mixedCase = 0
mixed_Case = 0

5
resources/test/fixtures/N816.py vendored Normal file
View File

@@ -0,0 +1,5 @@
lower = 0
CONSTANT = 0
mixedCase = 0
_mixedCase = 0
mixed_Case = 0

2
resources/test/fixtures/N817.py vendored Normal file
View File

@@ -0,0 +1,2 @@
import mod.CaMel as CM
from mod import CamelCase as CC

10
resources/test/fixtures/N818.py vendored Normal file
View File

@@ -0,0 +1,10 @@
class Error(Exception):
pass
class AnotherError(Exception):
pass
class C(Exception):
pass

View File

@@ -1,3 +0,0 @@
self.assertEquals (1, 2)
self.assertEquals(1, 2)
self.assertEqual(3, 4)

1
resources/test/fixtures/T201.py vendored Normal file
View File

@@ -0,0 +1 @@
print("Hello, world!") # T201

10
resources/test/fixtures/T203.py vendored Normal file
View File

@@ -0,0 +1,10 @@
from pprint import pprint
pprint("Hello, world!") # T203
import pprint
pprint.pprint("Hello, world!") # T203
pprint.pformat("Hello, world!")

13
resources/test/fixtures/U001.py vendored Normal file
View File

@@ -0,0 +1,13 @@
class A:
__metaclass__ = type
class B:
__metaclass__ = type
def __init__(self) -> None:
pass
class C(metaclass=type):
pass

15
resources/test/fixtures/U002.py vendored Normal file
View File

@@ -0,0 +1,15 @@
from os.path import abspath
x = abspath(__file__)
import os
y = os.path.abspath(__file__)
from os import path
z = path.abspath(__file__)

5
resources/test/fixtures/U003.py vendored Normal file
View File

@@ -0,0 +1,5 @@
type('')
type(b'')
type(0)
type(0.)
type(0j)

10
resources/test/fixtures/U005.py vendored Normal file
View File

@@ -0,0 +1,10 @@
import unittest
class Suite(unittest.TestCase):
def test(self) -> None:
self.assertEquals (1, 2)
self.assertEquals(1, 2)
self.assertEqual(3, 4)
self.failUnlessAlmostEqual(1, 1.1)
self.assertNotRegexpMatches("a", "b")

12
resources/test/fixtures/U006.py vendored Normal file
View File

@@ -0,0 +1,12 @@
from typing import List
def f(x: List[str]) -> None:
...
import typing
def f(x: typing.List[str]) -> None:
...

40
resources/test/fixtures/U007.py vendored Normal file
View File

@@ -0,0 +1,40 @@
from typing import Optional
def f(x: Optional[str]) -> None:
...
import typing
def f(x: typing.Optional[str]) -> None:
...
from typing import Union
def f(x: Union[str, int, Union[float, bytes]]) -> None:
...
import typing
def f(x: typing.Union[str, int]) -> None:
...
from typing import Union
def f(x: "Union[str, int, Union[float, bytes]]") -> None:
...
import typing
def f(x: "typing.Union[str, int]") -> None:
...

65
resources/test/fixtures/U008.py vendored Normal file
View File

@@ -0,0 +1,65 @@
class Parent:
def method(self):
pass
def wrong(self):
pass
class Child(Parent):
def method(self):
parent = super() # ok
super().method() # ok
Parent.method(self) # ok
Parent.super(1, 2) # ok
def wrong(self):
parent = super(Child, self) # wrong
super(Child, self).method # wrong
super(
Child,
self,
).method() # wrong
class BaseClass:
def f(self):
print("f")
def defined_outside(self):
super(MyClass, self).f() # CANNOT use super()
class MyClass(BaseClass):
def normal(self):
super(MyClass, self).f() # can use super()
super().f()
def different_argument(self, other):
super(MyClass, other).f() # CANNOT use super()
def comprehension_scope(self):
[super(MyClass, self).f() for x in [1]] # CANNOT use super()
def inner_functions(self):
def outer_argument():
super(MyClass, self).f() # CANNOT use super()
def inner_argument(self):
super(MyClass, self).f() # can use super()
super().f()
outer_argument()
inner_argument(self)
def inner_class(self):
class InnerClass:
super(MyClass, self).f() # CANNOT use super()
def method(inner_self):
super(MyClass, self).f() # CANNOT use super()
InnerClass().method()
defined_outside = defined_outside

2
resources/test/fixtures/W292_0.py vendored Normal file
View File

@@ -0,0 +1,2 @@
def fn() -> None:
pass

2
resources/test/fixtures/W292_1.py vendored Normal file
View File

@@ -0,0 +1,2 @@
def fn() -> None:
pass # noqa: W292

2
resources/test/fixtures/W292_2.py vendored Normal file
View File

@@ -0,0 +1,2 @@
def fn() -> None:
pass

35
resources/test/fixtures/W605.py vendored Normal file
View File

@@ -0,0 +1,35 @@
#: W605:1:10
regex = '\.png$'
#: W605:2:1
regex = '''
\.png$
'''
#: W605:2:6
f(
'\_'
)
#: W605:4:6
"""
multi-line
literal
with \_ somewhere
in the middle
"""
#: Okay
regex = r'\.png$'
regex = '\\.png$'
regex = r'''
\.png$
'''
regex = r'''
\\.png$
'''
s = '\\'
regex = '\w' # noqa
regex = '''
\w
''' # noqa

View File

@@ -1,3 +1,5 @@
import os
print(__path__)
__all__ = ["a", "b", "c"]

View File

@@ -0,0 +1,108 @@
"""A one line summary of the module or program, terminated by a period.
Leave one blank line. The rest of this docstring should contain an
overall description of the module or program. Optionally, it may also
contain a brief description of exported classes and functions and/or usage
examples.
Typical usage example:
foo = ClassFoo()
bar = foo.FunctionBar()
"""
# above: "2.8.2 Modules" section example
# https://google.github.io/styleguide/pyguide.html#382-modules
# Examples from the official "Google Python Style Guide" documentation:
# * As HTML: https://google.github.io/styleguide/pyguide.html
# * Source Markdown:
# https://github.com/google/styleguide/blob/gh-pages/pyguide.md
import os
from .expected import Expectation
expectation = Expectation()
expect = expectation.expect
# module docstring expected violations:
expectation.expected.add((
os.path.normcase(__file__),
"D213: Multi-line docstring summary should start at the second line"))
# "3.8.3 Functions and Methods" section example
# https://google.github.io/styleguide/pyguide.html#383-functions-and-methods
@expect("D213: Multi-line docstring summary should start at the second line",
arg_count=3)
@expect("D401: First line should be in imperative mood "
"(perhaps 'Fetch', not 'Fetches')", arg_count=3)
@expect("D406: Section name should end with a newline "
"('Raises', not 'Raises:')", arg_count=3)
@expect("D406: Section name should end with a newline "
"('Returns', not 'Returns:')", arg_count=3)
@expect("D407: Missing dashed underline after section ('Raises')", arg_count=3)
@expect("D407: Missing dashed underline after section ('Returns')",
arg_count=3)
@expect("D413: Missing blank line after last section ('Raises')", arg_count=3)
def fetch_bigtable_rows(big_table, keys, other_silly_variable=None):
"""Fetches rows from a Bigtable.
Retrieves rows pertaining to the given keys from the Table instance
represented by big_table. Silly things may happen if
other_silly_variable is not None.
Args:
big_table: An open Bigtable Table instance.
keys: A sequence of strings representing the key of each table row
to fetch.
other_silly_variable: Another optional variable, that has a much
longer name than the other args, and which does nothing.
Returns:
A dict mapping keys to the corresponding table row data
fetched. Each row is represented as a tuple of strings. For
example:
{'Serak': ('Rigel VII', 'Preparer'),
'Zim': ('Irk', 'Invader'),
'Lrrr': ('Omicron Persei 8', 'Emperor')}
If a key from the keys argument is missing from the dictionary,
then that row was not found in the table.
Raises:
IOError: An error occurred accessing the bigtable.Table object.
"""
# "3.8.4 Classes" section example
# https://google.github.io/styleguide/pyguide.html#384-classes
@expect("D203: 1 blank line required before class docstring (found 0)")
@expect("D213: Multi-line docstring summary should start at the second line")
@expect("D406: Section name should end with a newline "
"('Attributes', not 'Attributes:')")
@expect("D407: Missing dashed underline after section ('Attributes')")
@expect("D413: Missing blank line after last section ('Attributes')")
class SampleClass:
"""Summary of class here.
Longer class information....
Longer class information....
Attributes:
likes_spam: A boolean indicating if we like SPAM or not.
eggs: An integer count of the eggs we have laid.
"""
@expect("D401: First line should be in imperative mood "
"(perhaps 'Init', not 'Inits')", arg_count=2)
def __init__(self, likes_spam=False):
"""Inits SampleClass with blah."""
if self: # added to avoid NameError when run via @expect decorator
self.likes_spam = likes_spam
self.eggs = 0
@expect("D401: First line should be in imperative mood "
"(perhaps 'Perform', not 'Performs')", arg_count=1)
def public_method(self):
"""Performs operation blah."""

Some files were not shown because too many files have changed in this diff Show More