mirror of https://github.com/pypa/hatch.git
328 lines
6.7 KiB
Python
328 lines
6.7 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
import re
|
|
from collections import defaultdict
|
|
from functools import cache
|
|
from typing import Any
|
|
|
|
from markdown.preprocessors import Preprocessor
|
|
|
|
MARKER_VERSION = '<HATCH_RUFF_VERSION>'
|
|
MARKER_SELECTED_RULES = '<HATCH_RUFF_SELECTED_RULES>'
|
|
MARKER_UNSELECTED_RULES = '<HATCH_RUFF_UNSELECTED_RULES>'
|
|
MARKER_STABLE_RULES_COUNT = '<HATCH_RUFF_STABLE_RULES_COUNT>'
|
|
MARKER_PREVIEW_RULES_COUNT = '<HATCH_RUFF_PREVIEW_RULES_COUNT>'
|
|
MARKER_UNSELECTED_RULES_COUNT = '<HATCH_RUFF_UNSELECTED_RULES_COUNT>'
|
|
MARKER_PER_FILE_IGNORED_RULES = '<HATCH_RUFF_PER_FILE_IGNORED_RULES>'
|
|
RULE_URLS = {'S': 'https://docs.astral.sh/ruff/rules/#flake8-bandit-s'}
|
|
|
|
|
|
def read_constants(path: str, start: str) -> dict[str, Any]:
|
|
with open(path, encoding='utf-8') as f:
|
|
lines = f.read().splitlines()
|
|
|
|
for i, line in enumerate(lines):
|
|
if line.startswith(start):
|
|
block_start = i
|
|
break
|
|
else:
|
|
message = f'Could not find {start} in {path}'
|
|
raise RuntimeError(message)
|
|
|
|
data = {}
|
|
exec('\n'.join(lines[block_start:]), None, data) # noqa: S102
|
|
return data
|
|
|
|
|
|
def parse_rules(rules: tuple[str, ...]) -> defaultdict[str, list[str]]:
|
|
selected_rules: defaultdict[str, list[str]] = defaultdict(list)
|
|
separator = re.compile(r'^(\D+)(\d+)$')
|
|
|
|
for rule in rules:
|
|
match = separator.search(rule)
|
|
if match is None:
|
|
message = f'Could not parse rule {rule}'
|
|
raise RuntimeError(message)
|
|
|
|
group, number = match.groups()
|
|
selected_rules[group].append(number)
|
|
|
|
return selected_rules
|
|
|
|
|
|
def construct_collapsed_markdown_rule_list(text: str, rules: defaultdict[str, list[str]]) -> str:
|
|
preview_rule_set = set(ruff_data()['PREVIEW_RULES'])
|
|
|
|
lines = [f'??? "{text}"']
|
|
for group, numbers in sorted(rules.items()):
|
|
numbers.sort(key=lambda x: int(x[0]))
|
|
|
|
parts = []
|
|
for number in numbers:
|
|
rule = f'{group}{number}'
|
|
part = f'[{rule}](https://docs.astral.sh/ruff/rules/{rule})'
|
|
if f'{group}{number}' in preview_rule_set:
|
|
part += '^P^'
|
|
parts.append(part)
|
|
|
|
lines.append(f' - {", ".join(parts)}')
|
|
|
|
return '\n'.join(lines)
|
|
|
|
|
|
@cache
|
|
def ruff_data():
|
|
root = os.getcwd()
|
|
data = {}
|
|
for path, start in (
|
|
(os.path.join(root, 'src', 'hatch', 'cli', 'fmt', 'core.py'), 'STABLE_RULES'),
|
|
(os.path.join(root, 'src', 'hatch', 'env', 'internal', 'static_analysis.py'), 'RUFF_DEFAULT_VERSION'),
|
|
):
|
|
data.update(read_constants(path, start))
|
|
|
|
return data
|
|
|
|
|
|
@cache
|
|
def get_ruff_version():
|
|
return ruff_data()['RUFF_DEFAULT_VERSION']
|
|
|
|
|
|
@cache
|
|
def get_stable_rules_count():
|
|
return str(len(ruff_data()['STABLE_RULES']))
|
|
|
|
|
|
@cache
|
|
def get_preview_rules_count():
|
|
return str(len(ruff_data()['PREVIEW_RULES']))
|
|
|
|
|
|
@cache
|
|
def get_unselected_rules_count():
|
|
return str(len(UNSELECTED_RULES))
|
|
|
|
|
|
@cache
|
|
def get_selected_rules():
|
|
data = ruff_data()
|
|
rules = parse_rules(data['STABLE_RULES'])
|
|
for group, numbers in parse_rules(data['PREVIEW_RULES']).items():
|
|
rules[group].extend(numbers)
|
|
|
|
return construct_collapsed_markdown_rule_list('Selected rules', rules)
|
|
|
|
|
|
@cache
|
|
def get_unselected_rules():
|
|
return construct_collapsed_markdown_rule_list('Unselected rules', parse_rules(UNSELECTED_RULES))
|
|
|
|
|
|
@cache
|
|
def get_per_file_ignored_rules():
|
|
lines = []
|
|
for glob, rules in sorted(ruff_data()['PER_FILE_IGNORED_RULES'].items()):
|
|
parts = []
|
|
for rule in rules:
|
|
url = RULE_URLS.get(rule) or f'https://docs.astral.sh/ruff/rules/{rule}'
|
|
parts.append(f'[{rule}]({url})')
|
|
|
|
lines.append(f'- `{glob}`: {", ".join(parts)}')
|
|
|
|
return '\n'.join(lines)
|
|
|
|
|
|
class RuffDefaultsPreprocessor(Preprocessor):
|
|
def run(self, lines): # noqa: PLR6301
|
|
return (
|
|
'\n'.join(lines)
|
|
.replace(MARKER_VERSION, get_ruff_version())
|
|
.replace(MARKER_STABLE_RULES_COUNT, get_stable_rules_count())
|
|
.replace(MARKER_PREVIEW_RULES_COUNT, get_preview_rules_count())
|
|
.replace(MARKER_UNSELECTED_RULES_COUNT, get_unselected_rules_count())
|
|
.replace(MARKER_SELECTED_RULES, get_selected_rules())
|
|
.replace(MARKER_UNSELECTED_RULES, get_unselected_rules())
|
|
.replace(MARKER_PER_FILE_IGNORED_RULES, get_per_file_ignored_rules())
|
|
.splitlines()
|
|
)
|
|
|
|
|
|
UNSELECTED_RULES: tuple[str, ...] = (
|
|
'AIR001',
|
|
'ANN001',
|
|
'ANN002',
|
|
'ANN003',
|
|
'ANN101',
|
|
'ANN102',
|
|
'ANN201',
|
|
'ANN202',
|
|
'ANN204',
|
|
'ANN205',
|
|
'ANN206',
|
|
'ANN401',
|
|
'B027',
|
|
'C901',
|
|
'COM812',
|
|
'COM819',
|
|
'CPY001',
|
|
'D100',
|
|
'D101',
|
|
'D102',
|
|
'D103',
|
|
'D104',
|
|
'D105',
|
|
'D106',
|
|
'D107',
|
|
'D200',
|
|
'D201',
|
|
'D202',
|
|
'D203',
|
|
'D204',
|
|
'D205',
|
|
'D206',
|
|
'D207',
|
|
'D208',
|
|
'D209',
|
|
'D210',
|
|
'D211',
|
|
'D212',
|
|
'D213',
|
|
'D214',
|
|
'D215',
|
|
'D300',
|
|
'D301',
|
|
'D400',
|
|
'D401',
|
|
'D402',
|
|
'D403',
|
|
'D404',
|
|
'D405',
|
|
'D406',
|
|
'D407',
|
|
'D408',
|
|
'D409',
|
|
'D410',
|
|
'D411',
|
|
'D412',
|
|
'D413',
|
|
'D414',
|
|
'D415',
|
|
'D416',
|
|
'D417',
|
|
'D418',
|
|
'D419',
|
|
'DJ001',
|
|
'DJ003',
|
|
'DJ006',
|
|
'DJ007',
|
|
'DJ008',
|
|
'DJ012',
|
|
'DJ013',
|
|
'E111',
|
|
'E114',
|
|
'E117',
|
|
'E301',
|
|
'E302',
|
|
'E303',
|
|
'E304',
|
|
'E305',
|
|
'E306',
|
|
'E501',
|
|
'ERA001',
|
|
'FBT003',
|
|
'FIX001',
|
|
'FIX002',
|
|
'FIX003',
|
|
'FIX004',
|
|
'FURB101',
|
|
'FURB103',
|
|
'FURB140',
|
|
'ISC001',
|
|
'ISC002',
|
|
'NPY001',
|
|
'NPY002',
|
|
'NPY003',
|
|
'NPY201',
|
|
'PD002',
|
|
'PD003',
|
|
'PD004',
|
|
'PD007',
|
|
'PD008',
|
|
'PD009',
|
|
'PD010',
|
|
'PD011',
|
|
'PD012',
|
|
'PD013',
|
|
'PD015',
|
|
'PD101',
|
|
'PD901',
|
|
'PERF203',
|
|
'PGH001',
|
|
'PGH002',
|
|
'PGH003',
|
|
'PGH004',
|
|
'PLR0904',
|
|
'PLR0911',
|
|
'PLR0912',
|
|
'PLR0913',
|
|
'PLR0914',
|
|
'PLR0915',
|
|
'PLR0916',
|
|
'PLR0917',
|
|
'PLR1702',
|
|
'PLR1706',
|
|
'PT004',
|
|
'PT005',
|
|
'PTH100',
|
|
'PTH101',
|
|
'PTH102',
|
|
'PTH103',
|
|
'PTH104',
|
|
'PTH105',
|
|
'PTH106',
|
|
'PTH107',
|
|
'PTH108',
|
|
'PTH109',
|
|
'PTH110',
|
|
'PTH111',
|
|
'PTH112',
|
|
'PTH113',
|
|
'PTH114',
|
|
'PTH115',
|
|
'PTH116',
|
|
'PTH117',
|
|
'PTH118',
|
|
'PTH119',
|
|
'PTH120',
|
|
'PTH121',
|
|
'PTH122',
|
|
'PTH123',
|
|
'PTH124',
|
|
'PTH201',
|
|
'PTH202',
|
|
'PTH203',
|
|
'PTH204',
|
|
'PTH205',
|
|
'PTH206',
|
|
'PTH207',
|
|
'Q000',
|
|
'Q001',
|
|
'Q002',
|
|
'Q003',
|
|
'Q004',
|
|
'RET501',
|
|
'RET502',
|
|
'RUF011',
|
|
'RUF200',
|
|
'S404',
|
|
'S410',
|
|
'S603',
|
|
'SIM401',
|
|
'TD001',
|
|
'TD002',
|
|
'TD003',
|
|
'TRY200',
|
|
'W191',
|
|
)
|