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

Code before:
/// 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"
/// attributes, and all single-underscore attributes after dunder attributes.
fn compare_suggestions(c1: &Completion, c2: &Completion) -> Ordering {
    fn key<'a>(completion: &'a Completion) -> (bool, bool, bool, NameKind, bool, &'a Name) {
        (
            completion.module_name.is_some(),
            // At time of writing (2025-11-11), keyword completions
            // are classified as builtins, which makes them sort after
            // everything else. But we probably want keyword completions
            // to sort *before* anything else since they are so common.
            // Moreover, it seems VS Code forcefully does this sorting.
            // By doing it ourselves, we make our natural sorting match
            // VS Code's, and thus our completion evaluation framework
            // should be more representative of real world conditions.
            completion.kind != Some(CompletionKind::Keyword),
            completion.builtin,
            NameKind::classify(&completion.name),
            completion.is_type_check_only,
            &completion.name,
        )
    }

    key(c1).cmp(&key(c2))
}

#[cfg(test)]
mod tests {
    use insta::assert_snapshot;
    use ruff_python_parser::{Mode, ParseOptions, TokenKind, Tokens};
    use ty_python_semantic::ModuleName;

    use crate::completion::{Completion, completion};
    use crate::tests::{CursorTest, CursorTestBuilder};

    use super::{CompletionKind, CompletionSettings, token_suffix_by_kinds};

    #[test]
    fn token_suffixes_match() {
        insta::assert_debug_snapshot!(
            token_suffix_by_kinds(&tokenize("foo.x"), [TokenKind::Newline]),
            @r"
        Some(
            [
                Newline 5..5,
            ],

Review comment (suggestion):
I think you want to, at least, skip all trivia tokens and parentheses (`(`, `)`)) to properly support

```py
raise (
	a  # comment
	.Error
)
```

Code after:
    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 want 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"
/// attributes, and all single-underscore attributes after dunder attributes.
fn compare_suggestions(c1: &Completion, c2: &Completion) -> Ordering {
    fn key<'a>(completion: &'a Completion) -> (bool, bool, bool, bool, NameKind, bool, &'a Name) {
        (
            // This is only true when we are both in a `raise` context
            // *and* we know this suggestion is definitively usable