ruff RUF201
labelled unknown
Analysis Nemotron 3 Super
The rule flags TOML configuration entries where Ruff lint selectors use numeric rule codes instead of human-readable rule names.
Fix: Replace each numeric rule code in select, ignore, extend-select, or extend-ignore lists with its corresponding human-readable rule name (e.g., 'F401' becomes 'unused-import').
12 hazards
- The rewrite assumes all numeric codes have valid, stable name mappings; if a code is deprecated or misspelled, the fix may insert an invalid name causing configuration errors.
- If the configuration uses variable references or expressions (e.g., select = [code]) instead of literals, the rule does not fire but the fix would incorrectly attempt to rewrite non-literal values.
- Comments adjacent to or within the selector list (e.g., select = ["F401" # unused import]) may be lost or misplaced during the rewrite if the formatter does not preserve them.
- The fix does not account for selective application per file or directory; replacing a global ignore like "C901" with its name may unintentionally disable the check in contexts where it was conditionally enabled via per-section overrides.
- If the TOML file is parsed by a tool that expects numeric codes (e.g., a legacy script), replacing them with names could break external tooling that relies on the original format.
- The rewrite changes the lexical form but not the semantic meaning only if the mapping is bijective and stable; however, Ruff may update name mappings in future versions, making today’s fix obsolete or incorrect.
- In extend-select or extend-ignore, the fix may introduce duplicate names if the base configuration already includes the same rule via another path, potentially altering the effective lint set.
- If the selector list contains a mix of codes and names (e.g., ["F401", "unused-import"]), the fix will rewrite only the codes, potentially creating redundancy or confusion.
- The fix does not preserve whitespace formatting inside the array (e.g., newlines, indentation) beyond what the TOML formatter enforces, which may alter readability.
- If the rule code is part of a larger string or interpolated value (not applicable in standard TOML but possible via preprocessor), the fix would not apply and could lead to inconsistent state.
- Changing from codes to names may affect version control diffs in ways that obscure the intent of the change, especially if reviewers are unfamiliar with the naming convention.
- The fix assumes the target Rust-based Ruff binary used for linting recognizes the rule names; if an older version is used, the names may be unrecognized, causing linting failures.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | silent prediction off | Comments Adjacent/Within Selector List Lost | Comments adjacent to or within the selector list may be lost or misplaced during rewrite. |
| r1-p002 | silent prediction off | External Tool Expecting Numeric Codes Broken | If external tool expects numeric codes, replacing with names breaks compatibility. |
| r1-p003 | silent prediction off | Runtime Type Annotations Evaluated | Type annotations evaluated at runtime may reference numeric codes now replaced. |
| r1-p004 | silent prediction off | Duplicate Names in Extend-Select/Ignore | Fix may introduce duplicate names in extend-* if base already has rule via another path. |
| r1-p005 | silent prediction off | Whitespace Formatting Inside Array Not Preserved | Fix does not preserve whitespace formatting (newlines, indentation) inside array. |
| r1-p006 | silent prediction off | Version Control Diffs Obscure Intent | Changing codes to names affects VC diffs, obscuring intent if reviewers unfamiliar with names. |
| r1-p007 | silent prediction off | Mix of Codes and Names Creates Redundancy | Mix of codes and names: fix rewrites only codes, creating redundancy or confusion. |
| r2-p001 | silent prediction off | Context manager simulation using object without close | context managers that are not files, objects with write() but no close() |
| r2-p002 | silent prediction off | String formatting with mixed quote styles and nesting | string formatting edge cases: %, format, f-strings, repr vs str, nested quotes |
| r2-p003 | silent prediction off | Walrus and comprehension scoping affecting rule usage | walrus, comprehensions scoping, late binding in closures and lambdas |
| r2-p004 | silent prediction off | Bijective mapping assumption failure over time | The rewrite changes the lexical form but not the semantic meaning only if the mapping is bijective and stable; however, Ruff may update name mappings in future versions, making today’s fix obsolete or |
| r2-p005 | silent prediction off | Global ignore replacement breaking per-section overrides | The fix does not account for selective application per file or directory; replacing a global ignore like "C901" with its name may unintentionally disable the check in contexts where it was conditional |
| r2-p006 | silent prediction off | Evaluation order and side effects in merged selector lists | evaluation order and side effects when expressions are merged, duplicated or deleted |
| r2-p007 | silent prediction off | Rule code embedded in generated string via string concat | If the rule code is part of a larger string or interpolated value (not applicable in standard TOML but possible via preprocessor), the fix would not apply and could lead to inconsistent state. |
| r2-p008 | silent prediction off | Keyword-only args with default depending on rule code | keyword-only, positional-only, *args/**kwargs forwarded, defaults evaluated once |
Sources
r1-p001 · Comments Adjacent/Within Selector List Lost
1import tomllib
2with open('pyproject.toml', 'w') as f:
3 f.write('[tool.ruff]\nselect = ["F401" # check unused imports\n]\n')
4toml = tomllib.load(open('pyproject.toml', 'rb'))
5print(len(toml['tool']['ruff']['select']))
1
r1-p002 · External Tool Expecting Numeric Codes Broken
1import tomllib, json
2with open('.ruff.toml', 'w') as f:
3 f.write('[tool.ruff]\nignore = ["W291"]\n')
4data = tomllib.load(open('.ruff.toml', 'rb'))
5codes = data['tool']['ruff']['ignore']
6if all(c.isdigit() for c in ''.join(codes)):
7 print('legacy_ok')
8else:
9 print('legacy_broken')
legacy_broken
r1-p003 · Runtime Type Annotations Evaluated
1from typing import get_type_hints
2import tomllib
3with open('ruff.toml', 'w') as f:
4 f.write('[tool.ruff]\nselect = ["F841"]\n')
5toml = tomllib.load(open('ruff.toml', 'rb'))
6code = toml['tool']['ruff']['select'][0]
7def func(x: code) -> None: # type annotation uses code string
8 pass
9try:
10 hints = get_type_hints(func)
11 print(hints.get('x', 'missing'))
12except Exception as e:
13 print(f'{type(e).__name__}: {e}')
NameError: name 'F841' is not defined
r1-p004 · Duplicate Names in Extend-Select/Ignore
1import tomllib
2with open('pyproject.toml', 'w') as f:
3 f.write('[tool.ruff]\nselect = ["F401"]\nextend-select = ["F401"]\n')
4toml = tomllib.load(open('pyproject.toml', 'rb'))
5base = set(toml['tool']['ruff']['select'])
6extra = set(toml['tool']['ruff']['extend-select'])
7combined = list(base | extra)
8print(sorted(combined))
['F401']
r1-p005 · Whitespace Formatting Inside Array Not Preserved
1import tomllib
2with open('temp.toml', 'w') as f:
3 f.write('[tool.ruff]\nignore = [\n "W291",\n "E111"\n]\n')
4toml = tomllib.load(open('temp.toml', 'rb'))
5arr = toml['tool']['ruff']['ignore']
6print(len(arr))
7for item in arr:
8 print(repr(item))
2 'W291' 'E111'
r1-p006 · Version Control Diffs Obscure Intent
1import tomllib
2with open('config.toml', 'w') as f:
3 f.write('[tool.ruff]\nselect = ["F401", "E302"]\n')
4toml = tomllib.load(open('config.toml', 'rb'))
5names = toml['tool']['ruff']['select']
6print(' & '.join(sorted(names)))
E302 & F401
r1-p007 · Mix of Codes and Names Creates Redundancy
1import tomllib
2with open('ruff.toml', 'w') as f:
3 f.write('[tool.ruff]\nselect = ["F401", "unused-import"]\n')
4toml = tomllib.load(open('ruff.toml', 'rb'))
5select = toml['tool']['ruff']['select']
6print(len(select))
7print('dup' if len(select) != len(set(select)) else 'unique')
2 unique
r2-p001 · Context manager simulation using object without close
1# Simulates writing via buffer-like object affected by F401 rename
2class Buffer:
3 def __init__(self):
4 self.data = ''
5 def write(self, s):
6 self.data += s
7 # Missing close() - relies on GC
8buf = Buffer()
9def foo(x):
10 return x # unused-import equivalent -> F401 triggers
11print(buf.write(str(foo(42))))
None
r2-p002 · String formatting with mixed quote styles and nesting
1msg = 'Error: %s'
2code = "W291" # trailing-whitespace -> ignore list entry
3formatted = msg % code
4print(formatted)
5# Also testing f-string
6print(f'Ignore: {code}')
7# Nested quotes
8s = "He said '%s'" % code
9print(s)
Error: W291 Ignore: W291 He said 'W291'
r2-p003 · Walrus and comprehension scoping affecting rule usage
1rules = ["C901"] # max-complexity -> ignore
2funcs = []
3for i in range(2):
4 if (n := len(rules)) > 0:
5 def make_func(idx=i):
6 return lambda: print(f'Complexity limit: {rules[idx]}')
7 funcs.append(make_func())
8for f in funcs:
9 f()
10# Later reassignment
11rules = ["unused-variable"]
12print(f'After: {rules[0] if rules else "none"}')
Complexity limit: C901
r2-p004 · Bijective mapping assumption failure over time
1TOML_SELECT = ['F401'] # unused-import
2TOML_EXTEND_SELECT = ['W291'] # trailing-whitespace
3# Assume we map back for logging
4def to_code(name):
5 rev = {'unused-import': 'F401', 'trailing-whitespace': 'W291'}
6 return rev.get(name, name)
7codes = [to_code(n) for n in TOML_SELECT + TOML_EXTEND_SELECT]
8print('Mapped codes:', ','.join(codes))
9# Simulate future Ruff renaming F401 -> something-else
10# Today’s fix would be wrong if mapping changes
Mapped codes: F401,W291
r2-p005 · Global ignore replacement breaking per-section overrides
1IGNORE_GLOBAL = ['C901'] # max-complexity
2# Simulate per-file override: enable C901 here
3IGNORE_LOCAL = [x for x in IGNORE_GLOBAL if x != 'C901'] # should be empty
4print('Local ignore after filter:', IGNORE_LOCAL)
5# But if renamed globally to 'max-complexity', comparison fails
6IGNORE_GLOBAL_NAMED = ['max-complexity']
7IGNORE_LOCAL_NAMED = [x for x in IGNORE_GLOBAL_NAMED if x != 'C901'] # no match!
8print('Local ignore (named):', IGNORE_LOCAL_NAMED)
9print('Unintended retention of check due to failed override')
Local ignore after filter: [] Local ignore (named): ['max-complexity'] Unintended retention of check due to failed override
r2-p006 · Evaluation order and side effects in merged selector lists
1def get_rules():
2 print('Fetching rules')
3 return ['E302'] # blank line
4SELECT = get_rules() + ['F401'] # unused-import
5print('Select:', SELECT)
6# Later re-evaluate
7SELECT2 = get_rules() + ['W291']
8print('Select2:', SELECT2)
9# Side effect: two prints
10# If fix alters timing or duplication, behavior may differ
Fetching rules Select: ['E302', 'F401'] Fetching rules Select2: ['E302', 'W291']
r2-p007 · Rule code embedded in generated string via string concat
1PREFIX = 'rule-'
2CODE = 'D400' # docstring-consecutive-blank-lines
3FULL = PREFIX + CODE
4print('Generated:', FULL)
5# Simulate config generation
6config_line = f'extend-ignore = ["{FULL}"]'
7print('Config line:', config_line)
8# Fix would not touch FULL because it's not a raw list item
9# So D400 stays as code, causing mismatch if linter expects names
Generated: rule-D400 Config line: extend-ignore = ["rule-D400"]
r2-p008 · Keyword-only args with default depending on rule code
1def process(*, ignore=('W291',), action='log'): # W291 = trailing-whitespace
2 print(f'Ignoring: {ignore[0]}')
3 if action == 'log':
4 return f'logged:{ignore[0]}'
5 return ignore[0]
6# Default captured at definition
7out = process()
8print(out)
9# Later redefine? Can't, but show reliance
10# If fix changes 'W291' to name, default still holds old string
11# Unless function is redefined, but we don't — so behavior static
Ignoring: W291 logged:W291