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

Code before:
        let filename = self.filename.truncated_display();
        let suggestion = self.suggestion.truncated_display();

        match self.argument {
            OpenArgument::Pathlib { .. } => Some(format!("Replace with `{filename}.{suggestion}`")),
            OpenArgument::Builtin { .. } => {
                Some(format!("Replace with `Path({filename}).{suggestion}`"))
            }
        }
    }
}

/// FURB103
pub(crate) fn write_whole_file(checker: &Checker, with: &ast::StmtWith) {
    // `async` check here is more of a precaution.
    if with.is_async {
        return;
    }

    // First we go through all the items in the statement and find all `open` operations.
    let following_statements = following_statements_after_with(
        with,
        checker.semantic().current_statement_parent(),
        checker.python_ast(),
    );
    let candidates = find_file_opens(
        with,
        checker.semantic(),
        false,
        checker.target_version(),
        following_statements,
    );
    if candidates.is_empty() {
        return;
    }

    // Then we need to match each `open` operation with exactly one `write` call.
    let mut matcher = WriteMatcher::new(checker, candidates, with);
    visitor::walk_body(&mut matcher, &with.body);
}

/// AST visitor that matches `open` operations with the corresponding `write` calls.
struct WriteMatcher<'a, 'b> {
    checker: &'a Checker<'b>,
    candidates: Vec<FileOpen<'a>>,
    loop_counter: u32,
    with_stmt: &'a ast::StmtWith,
}

impl<'a, 'b> WriteMatcher<'a, 'b> {
    fn new(

Review comment (suggestion):
Rather than doing up-front work to find all the statements for every `with` block, even if it has nothing to do with opening a file, this should first see if there are any candidates, and only then fetch the following statements and filter the list of candidates afterwards.

Or at the very least, the call to get following statements should happen further inside of say, `resolve_file_open` where the data will actually be used, at the point that it will actually be needed and no sooner.

Code after:

        match self.argument {
            OpenArgument::Pathlib { .. } => Some(format!("Replace with `{filename}.{suggestion}`")),
            OpenArgument::Builtin { .. } => {
                Some(format!("Replace with `Path({filename}).{suggestion}`"))
            }
        }
    }
}

/// FURB103
pub(crate) fn write_whole_file(checker: &Checker, with: &ast::StmtWith) {
    // `async` check here is more of a precaution.
    if with.is_async {
        return;
    }

    // First we go through all the items in the statement and find all `open` operations.
    let candidates = find_file_opens(with, checker.semantic(), false, checker.target_version());
    if candidates.is_empty() {
        return;
    }

    // Then we need to match each `open` operation with exactly one `write` call.
    let mut matcher = WriteMatcher::new(checker, candidates, with);
    visitor::walk_body(&mut matcher, &with.body);
}

/// AST visitor that matches `open` operations with the corresponding `write` calls.
struct WriteMatcher<'a, 'b> {
    checker: &'a Checker<'b>,
    candidates: Vec<FileOpen<'a>>,
    loop_counter: u32,
    with_stmt: &'a ast::StmtWith,
}

impl<'a, 'b> WriteMatcher<'a, 'b> {
    fn new(
        checker: &'a Checker<'b>,
        candidates: Vec<FileOpen<'a>>,
        with_stmt: &'a ast::StmtWith,
    ) -> Self {
        Self {
            checker,
            candidates,
            loop_counter: 0,
            with_stmt,
        }
    }
}