{"record":{"id":"6dbdfe24c5d1b812","repo":"astral-sh/ruff","slug":"missing-argument-first","errorCode":null,"errorMessage":"Missing argument `first`","messagePattern":"Missing argument `first`","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/ruff_linter/src/rules/flake8_pytest_style/rules/unittest_assert.rs","lineNumber":321,"sourceCode":"                        assert(expr, msg)\n                    },\n                )\n            }\n            UnittestAssert::Equal\n            | UnittestAssert::Equals\n            | UnittestAssert::FailUnlessEqual\n            | UnittestAssert::NotEqual\n            | UnittestAssert::NotEquals\n            | UnittestAssert::FailIfEqual\n            | UnittestAssert::Greater\n            | UnittestAssert::GreaterEqual\n            | UnittestAssert::Less\n            | UnittestAssert::LessEqual\n            | UnittestAssert::Is\n            | UnittestAssert::IsNot => {\n                let first = args\n                    .get(\"first\")\n                    .ok_or_else(|| anyhow!(\"Missing argument `first`\"))?;\n                let second = args\n                    .get(\"second\")\n                    .ok_or_else(|| anyhow!(\"Missing argument `second`\"))?;\n                let msg = args.get(\"msg\").copied();\n                let cmp_op = match self {\n                    UnittestAssert::Equal\n                    | UnittestAssert::Equals\n                    | UnittestAssert::FailUnlessEqual => CmpOp::Eq,\n                    UnittestAssert::NotEqual\n                    | UnittestAssert::NotEquals\n                    | UnittestAssert::FailIfEqual => CmpOp::NotEq,\n                    UnittestAssert::Greater => CmpOp::Gt,\n                    UnittestAssert::GreaterEqual => CmpOp::GtE,\n                    UnittestAssert::Less => CmpOp::Lt,\n                    UnittestAssert::LessEqual => CmpOp::LtE,\n                    UnittestAssert::Is => CmpOp::Is,\n                    UnittestAssert::IsNot => CmpOp::IsNot,\n                    _ => unreachable!(),","sourceCodeStart":303,"sourceCodeEnd":339,"githubUrl":"https://github.com/astral-sh/ruff/blob/15f3fe6b15a5f00172f34b0f542f8ea277f5a586/crates/ruff_linter/src/rules/flake8_pytest_style/rules/unittest_assert.rs#L303-L339","documentation":"Ruff's flake8-pytest-style rewrite of unittest asserts (PT009, `generate_assert`) converts `self.assertLess`/`assertLessEqual`/`assertIs`/`assertIsNot` calls into plain `assert first <op> second` expressions. It first builds a map from argument names to expressions (`args_map`), then looks up `first`. If the call provides no first argument (positionally or by keyword), the rewrite cannot build the replacement and aborts with this error.","triggerScenarios":"`UnittestAssert::Less/LessEqual/Is/IsNot.generate_assert(args, keywords)` where the mapped args contain no `\"first\"` entry — e.g. a source call `self.assertLess()` with zero args, or fewer positional args than the arg_spec (`first`, `second`, `msg`).","commonSituations":"Running `ruff --fix` on test code calling `assertLess`/`assertIs` etc. with an incomplete argument list; dynamically bound methods matching the assert name but not unittest's signature; in-progress edits to test files.","solutions":["Fix the flagged call so it passes two arguments: `self.assertLess(a, b)`.","Confirm the matched method is genuinely a unittest assert; suppress PT009 for files with custom same-named helpers.","If editing Ruff itself, check that `args_map` zipped `arg_spec` names with positional args correctly.","Re-run `ruff check --fix` after correcting the test call."],"exampleFix":"// before\ndef test(self):\n    self.assertLess()\n// after\ndef test(self):\n    self.assertLess(actual, expected)","handlingStrategy":"validation","validationCode":"import inspect\n\ndef has_two_operands(obj, name):\n    method = getattr(obj, name, None)\n    if not callable(method):\n        return False\n    required = [p for p in inspect.signature(method).parameters.values() if p.default is inspect.Parameter.empty]\n    return len(required) >= 2\n\nassert has_two_operands(self, 'assertLess'), 'assertLess needs (first, second)'","typeGuard":"def has_first_operand(args: tuple, kwargs: dict) -> bool:\n    return len(args) >= 1 or 'first' in kwargs","tryCatchPattern":null,"preventionTips":["Always pass both comparands to assertLess/assertLessEqual/assertIs/assertIsNot.","Run `ruff check` (no --fix) in CI before autofix so incomplete calls surface first.","Keep test methods on real `unittest.TestCase` classes so signatures match PT009 expectations.","Avoid dynamically rebinding self.assertX to custom callables with different arities."],"tags":["linter","rust","pytest-style","argument-missing"],"backgroundTag":"missing-required-argument","analyzedSha":"15f3fe6b15a5f00172f34b0f542f8ea277f5a586","analyzedAt":"2026-09-05T10:32:37.492Z","contentChangedAt":"2026-09-05T10:32:37.492Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}