Skip to content

linting

buff lints and formats .by source. it is ruff, so it reads ruff's configuration out of pyproject.toml and every rule ruff ships is available:

[tool.ruff.lint]
select = ["E", "F", "BY"]

BY is basedpython's own rule prefix. most of those rules look for a python spelling of something basedpython has syntax for:

code name what it finds
BY001 manual-none-coalesce a conditional expression that is a ??
BY002 manual-optional-chain a conditional expression that is a ?.
BY003 manual-isinstance an isinstance call that is an is
BY004 manual-super-call super() where super will do
BY007 manual-any-annotation Any where dynamic will do
BY009 manual-unpack-annotation Unpack[…] where * will do
BY010 manual-typeof-annotation TypeOf[…] where typeof will do
BY011 manual-re-export import name as name, which is export
BY012 redundant-typing-import an import of a member already implicit
BY017 unnecessary-stub-body a : ... body an empty declaration needs no more
BY019 manual-sentinel a Sentinel(…) assignment, which is sentinel
BY020 manual-cast-call a typing.cast call, which is the cast keyword
BY101 redundant-none-coalesce a ?? whose fallback cannot change the result

every one of them is fixable. BY020's fix is the only one that is always unsafe — basedpython's cast is checked where typing.cast is a no-op, so the rewrite adds a way for the program to fail.

they also compose with the upstream rules that produce their input. SIM108 turns an if / else block into a conditional expression, and BY001 takes it the rest of the way, so one --fix run rewrites

if a is None:
    result = b
else:
    result = a

into

result = a ?? b

what the linter does not check

the linter reads one file at a time and has no types. anything that takes a type to decide is by check's job instead, and reporting it in both places would mean two answers that can disagree. that split is why a once callback the callee never calls is once-not-called in the checker and has no BY rule here, and why t[0] is not reported as a tuple member access — whether t is a tuple is a type question

upstream rules on .by source

ruff's own rules are written for python source, and two things follow from that.

a rule that resolves a name through its import does not see a name basedpython resolves for you. UP045 rewrites Optional[int] to int | None when Optional was imported, and says nothing when it was left to implicit typing — the same annotation, reported or not depending on a line that basedpython does not need

a rule that suggests a replacement suggests the python one. SIM108 above is the case where that composes; where it does not, the suggestion is still valid .by, just not the shortest way to write it

nothing in ruff's rule set is known to report a construct that is correct basedpython. F821 used to: an unqualified builder inside a trailing-lambda block resolves against the block's implicit receiver, and the linter cannot see receiver types, so it now defers every unresolved name inside a block to by check. the same deferral covers self and an enum variant written bare