cairo-language-server 2.20.0

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

use cairo_lang_semantic::lsp_helpers::LspHelpers;
use cairo_lang_syntax::node::ast::ExprPath;
use cairo_lang_syntax::node::helpers::GetIdentifier;
use cairo_language_common::CommonGroup;
use lsp_types::{CodeAction, CodeActionKind, Url, WorkspaceEdit};

use crate::lang::analysis_context::AnalysisContext;
use crate::lang::db::AnalysisDatabase;
use crate::lang::importer::new_import_edit;

pub fn missing_import<'db>(
    db: &'db AnalysisDatabase,
    ctx: &AnalysisContext<'db>,
    uri: Url,
) -> Option<Vec<CodeAction>> {
    let resultants = db.get_node_resultants(ctx.node)?;

    resultants.iter().chain(std::iter::once(&ctx.node)).find_map(|node| {
        let typed_path_generic = node.ancestor_of_type::<ExprPath>(db)?;

        // Remove generic args.
        let typed_path_segments: Vec<_> = typed_path_generic
            .segments(db)
            .elements(db)
            .map(|e| e.identifier(db).to_string(db))
            .rev()
            .collect();

        let items = db.visible_importables_from_module(ctx.module_id)?;

        let items: Vec<_> = items
            .iter()
            .filter_map(|(_item, proposed_path)| {
                let mut proposed_path_segments: Vec<_> = proposed_path.split("::").collect();

                // We exclude items that are already in scope (commonly prelude).
                // These items can NOT generate E0006.
                // This prevents cases like derive with same name as trait, that is broken and generates code with E0006 error inside.
                if proposed_path_segments.len() == 1 {
                    return None;
                }

                let mut last_path_segment = None;

                for typed_path_segment in &typed_path_segments {
                    last_path_segment = proposed_path_segments.pop();

                    if typed_path_segment != last_path_segment? {
                        return None;
                    }
                }

                proposed_path_segments.extend(last_path_segment);

                Some(proposed_path_segments.join("::"))
            })
            .collect();

        let is_preferred = is_preferred(&items);

        Some(
            items
                .into_iter()
                .map(|path| CodeAction {
                    title: format!("Import `{path}`"),
                    kind: Some(CodeActionKind::QUICKFIX),
                    is_preferred,
                    edit: Some(WorkspaceEdit {
                        changes: new_import_edit(db, ctx, path)
                            .map(|edit| HashMap::from_iter([(uri.clone(), vec![edit])])),
                        ..Default::default()
                    }),
                    ..Default::default()
                })
                .collect(),
        )
    })
}

pub fn is_preferred<T>(items: &[T]) -> Option<bool> {
    let is_unambiguous = match items.len() {
        0 => return None,
        1 => true,
        _ => false,
    };

    // We can propose this for autofix if there is exactly one possible option.
    is_unambiguous.then_some(true)
}