diff --git a/.config/ruff.toml b/.config/ruff.toml index c74669ae..61d486d4 100644 --- a/.config/ruff.toml +++ b/.config/ruff.toml @@ -62,8 +62,11 @@ ignore = [ "N999", # Ignoring this since we are using jinja templates in the directory path which will eventually turn into a valid module name. # TODO: make a task in downstream templates to remove this "S101", # We do not use the -o optimize flag, so it's fine to use `assert` in the main code. It's especially helpful for static typing "SIM102", # Nested if statements can be better analyzed by coverage detectors than compound statements + "SIM103", # Explicit if/else branches let the coverage checker track both paths, unlike evaluating an expression "SIM108", # Explicit if/else branches let the coverage checker track both paths, unlike a ternary + "SIM110", # Explicit if statements and loops let the coverage checker track both paths, unlike using `any` or `all` with a generator expression "SIM114", # Using `or` statements to simplify this would confuse the coverage checker + "SIM401", # using dictionary getters to simplify this would inhibit the coverage checker from tracking both paths "TD002", # Adding author names to TODOs erodes shared ownership of codebase. Git history provides information about who originally created the TODO if that information is vitally needed "TD003", # Up to the author's judgement whether a TODO requires a link to an issue or not "TID252", # Sometimes it makes sense to use relative imports, that's a judgement call for us, not ruff diff --git a/AGENTS.md b/AGENTS.md index d7489b02..bbcd203c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,12 +9,16 @@ This project is a Copier template used to generate other copier templates. It is - Comments should be used very rarely. Code should generally express its intent. - Never write a one-line docstring — either the name is sufficient or the behavior warrants a full explanation. - Don't sort or remove imports manually — pre-commit handles it. -- Always include type hints in Python -- Respect the pyrefly unused-call-result check; assign unneeded return values to `_` - Prefer keyword-only parameters (unless a very clear single-argument function): use `*` in Python signatures and destructured options objects in TypeScript. - When disabling a linting rule with an inline directive, provide a comment at the end of the line (or on the line above for tools that don't allow extra text after an inline directive) describing the reasoning for disabling the rule. - Avoid telling the type checker what a type is rather than letting it prove it. This includes type assertions (`as SomeType` in TypeScript, `cast()` in Python) and variable annotations that override inference. Prefer approaches that let the type checker verify the type itself: `isinstance`/`instanceof` narrowing, restructuring code so the correct type flows naturally, or using discriminated unions. When there is genuinely no alternative, add a comment explaining why the workaround is necessary and why it is safe. - Avoid `||` (TypeScript) or `or` (Python) in `if`/`elif` conditions, and avoid `x in ['a', 'b']`-style membership tests in implementation code — coverage tools treat these as a single branch, silently masking untested paths and producing false 100% branch coverage. Use separate `if`/`elif` branches instead so each condition is independently covered. + +### Python + +- Always include type hints. +- Respect the pyrefly unused-call-result check; assign unneeded return values to `_` +- Prefer explicit `if`/`else` (or a `for` loop with `if`/`return`) over one-line forms that collapse branches: a ternary, `d.get(key, default)`, returning a boolean expression `return b > 5`/`return bool(b)`, or `any()`/`all()` over a generator in place of a loop. `coverage.py` tracks branches as line-to-line arcs, so a single-line expression hides the untaken path. - When filtering logic combines multiple `and`-joined guards (e.g. a null check alongside a value check), prefer a loop with explicit `if`/`continue` branches over a single-line comprehension. A compound boolean filter on one line hides individual branches from line coverage — each guard condition should be its own statement so missing test cases are surfaced. ## Testing