cairo-language-server 2.20.0

The Cairo Language Server
Documentation
use std::collections::HashMap;

use cairo_lang_semantic::items::function_with_body::{
    FunctionWithBodySemantic, SemanticExprLookup,
};
use cairo_lang_semantic::lookup_item::LookupItemEx;
use cairo_lang_syntax::node::{TypedSyntaxNode, ast};
use cairo_language_common::CommonGroup;
use lsp_types::{CodeAction, CodeActionKind, TextEdit, Url, WorkspaceEdit};

use crate::lang::analysis_context::AnalysisContext;
use crate::lang::db::{AnalysisDatabase, LsSemanticGroup};
use crate::lang::lsp::ToLsp;
use crate::lang::members::find_members_for_type;
use crate::lang::text_matching::text_matches;

pub fn suggest_similar_member<'db>(
    db: &'db AnalysisDatabase,
    ctx: &AnalysisContext<'db>,
    uri: Url,
) -> Option<Vec<CodeAction>> {
    db.get_node_resultants(ctx.node)?.iter().find_map(|resultant_node| {
        let expr_binary = resultant_node.ancestor_of_type::<ast::ExprBinary>(db)?;
        let lhs_stable_ptr = expr_binary.lhs(db).stable_ptr(db);
        // Get its semantic model.
        let lookup_item = db.find_lookup_item(*resultant_node)?;
        let resultants_function_with_body = lookup_item.function_with_body()?;

        let expr_id = db.lookup_expr_by_ptr(resultants_function_with_body, lhs_stable_ptr).ok()?;

        let ty = db.expr_semantic(resultants_function_with_body, expr_id).ty();
        if ty.is_missing(db) {
            return None;
        }

        let member_candidates = find_members_for_type(db, resultants_function_with_body, ty)?;

        let typed_member_name = ctx.node.get_text_without_trivia(db);

        // Filter member candidates by similarity to the typed member name
        let suggestions: Vec<String> = member_candidates
            .into_iter()
            .map(|name| name.to_string(db))
            .filter(|name| text_matches(name, typed_member_name.to_string(db)))
            .collect();

        if suggestions.is_empty() {
            return None;
        }

        let bad_member_span = ctx
            .node
            .span_without_trivia(db)
            .position_in_file(db, ctx.node.stable_ptr(db).file_id(db))?;

        let code_actions = suggestions
            .into_iter()
            .map(|member_name| {
                let edits = vec![TextEdit {
                    range: bad_member_span.to_lsp(),
                    new_text: member_name.clone(),
                }];

                CodeAction {
                    title: format!("Use {member_name} instead"),
                    kind: Some(CodeActionKind::QUICKFIX),
                    edit: Some(WorkspaceEdit {
                        changes: Some(HashMap::from([(uri.clone(), edits)])),
                        document_changes: None,
                        change_annotations: None,
                    }),
                    ..Default::default()
                }
            })
            .collect();

        Some(code_actions)
    })
}