Repository: astral-sh/ruff
File: crates/ty_ide/src/completion.rs (Rust)

Code before:
        let start = offset - typed.text_len();
        TextRange::new(start, offset)
    } else {
        TextRange::empty(offset)
    };

    let covering = covering_node(parsed.syntax().into(), range);
    covering.ancestors().any(|node| match node {
        ast::AnyNodeRef::Parameter(param) => param.name.range.contains_range(range),
        ast::AnyNodeRef::TypeParamTypeVar(type_param) => {
            type_param.name.range.contains_range(range)
        }
        ast::AnyNodeRef::StmtFor(stmt_for) => stmt_for.target.range().contains_range(range),
        _ => false,
    })
}

/// Returns true when the cursor is after a `raise` keyword.
fn is_raising_exception(tokens: &[Token]) -> bool {
    /// The maximum number of tokens we're willing to
    /// look-behind to find a `raise` keyword.
    const LIMIT: usize = 10;

    // This only looks for things like `raise foo.bar.baz.qu<CURSOR>`.
    // Technically, any kind of expression is allowed after `raise`.
    // But we may not always wanted to treat it specially. So we're
    // rather conservative about what we consider "raising an
    // exception" to be for the purposes of completions. The failure
    // mode here is that we may wind up suggesting things that
    // shouldn't be raised. The benefit is that when this heuristic
    // does work, we won't suggest things that shouldn't be raised.
    for token in tokens.iter().rev().take(LIMIT) {
        match token.kind() {
            TokenKind::Name | TokenKind::Dot => continue,
            TokenKind::Raise => return true,
            _ => return false,
        }
    }
    false
}

/// Order completions according to the following rules:
///
/// 1) Names with no underscore prefix
/// 2) Names starting with `_` but not dunders
/// 3) `__dunder__` names
///
/// Among each category, type-check-only items are sorted last,
/// and otherwise completions are sorted lexicographically.
///
/// This has the effect of putting all dunder attributes after "normal"

Review comment (suggestion):
```suggestion
    // But we may not always want to treat it specially. So we're
```

Code after:
    parsed: &ParsedModuleRef,
    offset: TextSize,
    tokens: &[Token],
    typed: Option<&str>,
) -> bool {
    fn is_definition_token(token: &Token) -> bool {
        matches!(
            token.kind(),
            TokenKind::Def | TokenKind::Class | TokenKind::Type | TokenKind::As | TokenKind::For
        )
    }

    let is_definition_keyword = |token: &Token| {
        if is_definition_token(token) {
            true
        } else if token.kind() == TokenKind::Name {
            let source = source_text(db, file);
            &source[token.range()] == "type"
        } else {
            false
        }
    };
    if match tokens {
        [.., penultimate, _] if typed.is_some() => is_definition_keyword(penultimate),
        [.., last] if typed.is_none() => is_definition_keyword(last),
        _ => false,
    } {
        return true;
    }
    // Analyze the AST if token matching is insufficient
    // to determine if we're inside a name definition.
    is_in_variable_binding(parsed, offset, typed)
}

/// Returns true when the cursor sits on a binding statement.
/// E.g. naming a parameter, type parameter, or `for` <name>).
fn is_in_variable_binding(parsed: &ParsedModuleRef, offset: TextSize, typed: Option<&str>) -> bool {
    let range = if let Some(typed) = typed {
        let start = offset.saturating_sub(typed.text_len());
        TextRange::new(start, offset)
    } else {
        TextRange::empty(offset)
    };

    let covering = covering_node(parsed.syntax().into(), range);
    covering.ancestors().any(|node| match node {
        ast::AnyNodeRef::Parameter(param) => param.name.range.contains_range(range),
        ast::AnyNodeRef::TypeParamTypeVar(type_param) => {
            type_param.name.range.contains_range(range)
        }
        ast::AnyNodeRef::StmtFor(stmt_for) => stmt_for.target.range().contains_range(range),