Repository: astral-sh/ruff
File: crates/ruff_linter/src/rules/refurb/rules/read_whole_file.rs (Rust)

Code before:

impl<'a> Visitor<'a> for ReadMatcher<'a, '_> {
    fn visit_expr(&mut self, expr: &'a Expr) {
        if let Some(read_from) = match_read_call(expr) {
            if let Some(open) = self
                .candidates
                .iter()
                .position(|open| open.is_ref(read_from))
            {
                let open = self.candidates.remove(open);
                let suggestion = make_suggestion(&open, self.checker.generator());
                let mut diagnostic = self.checker.report_diagnostic(
                    ReadWholeFile {
                        filename: SourceCodeSnippet::from_str(
                            &self.checker.generator().expr(open.filename),
                        ),
                        suggestion: SourceCodeSnippet::from_str(&suggestion),
                    },
                    open.item.range(),
                );

                if !crate::preview::is_fix_read_whole_file_enabled(self.checker.settings()) {
                    return;
                }

                let (target, annotation) = match self.with_stmt.body.first() {
                    Some(Stmt::Assign(assign))
                        if assign.value.range().contains_range(expr.range()) =>
                    {
                        match assign.targets.first() {
                            Some(Expr::Name(name)) => (Some(name.id.as_str()), None),
                            _ => (None, None),
                        }
                    }
                    Some(Stmt::AnnAssign(ann_assign))
                        if ann_assign
                            .value
                            .as_ref()
                            .is_some_and(|v| v.range().contains_range(expr.range())) =>
                    {
                        match ann_assign.target.as_ref() {
                            Expr::Name(name) => {
                                let annotation_code = self
                                    .checker
                                    .generator()
                                    .expr(ann_assign.annotation.as_ref());
                                (Some(name.id.as_str()), Some(annotation_code))
                            }
                            _ => (None, None),
                        }
                    }

Review comment (suggestion):
I should have noticed this in the earlier PR, but this actually looks quite repetitive with the check inside of `generate_fix`. I think we can combine this check and this check:

```rust
matches!(with_stmt.body.as_slice(), [Stmt::Assign(_)]))
```

`generate_fix` seems like the right place to do that to me, which should allow us to decrease the number of arguments since we're already passing the `with_stmt` itself.

Code after:

impl<'a> Visitor<'a> for ReadMatcher<'a, '_> {
    fn visit_expr(&mut self, expr: &'a Expr) {
        if let Some(read_from) = match_read_call(expr) {
            if let Some(open) = self
                .candidates
                .iter()
                .position(|open| open.is_ref(read_from))
            {
                let open = self.candidates.remove(open);
                let suggestion = make_suggestion(&open, self.checker.generator());
                let mut diagnostic = self.checker.report_diagnostic(
                    ReadWholeFile {
                        filename: SourceCodeSnippet::from_str(
                            &self.checker.generator().expr(open.filename),
                        ),
                        suggestion: SourceCodeSnippet::from_str(&suggestion),
                    },
                    open.item.range(),
                );

                if let Some(fix) =
                    generate_fix(self.checker, &open, expr, self.with_stmt, &suggestion)
                {
                    diagnostic.set_fix(fix);
                }
            }
            return;
        }
        visitor::walk_expr(self, expr);
    }
}

/// Match `x.read()` expression and return expression `x` on success.
fn match_read_call(expr: &Expr) -> Option<&Expr> {
    let call = expr.as_call_expr()?;
    let attr = call.func.as_attribute_expr()?;
    let method_name = &attr.attr;

    if method_name != "read"
        || !attr.value.is_name_expr()
        || !call.arguments.args.is_empty()
        || !call.arguments.keywords.is_empty()
    {
        return None;
    }

    Some(&*attr.value)
}

fn make_suggestion(open: &FileOpen<'_>, generator: Generator) -> String {