svelte-compiler 0.1.0

Core compiler API for the Rust Svelte toolchain
Documentation
use std::sync::Arc;

use crate::api::modern::{
    RawField, estree_node_field_array, estree_node_field_object, estree_node_field_str,
    estree_node_type,
};
use crate::ast::modern::{EstreeNode, EstreeValue};

pub(crate) fn raw_identifier_name(node: &EstreeNode) -> Option<Arc<str>> {
    (estree_node_type(node) == Some("Identifier"))
        .then(|| estree_node_field_str(node, RawField::Name))
        .flatten()
        .map(Arc::from)
}

pub(crate) fn raw_callee_name(node: &EstreeNode) -> Option<Arc<str>> {
    match estree_node_type(node) {
        Some("Identifier") => estree_node_field_str(node, RawField::Name).map(Arc::from),
        Some("MemberExpression") => {
            let object = estree_node_field_object(node, RawField::Object)?;
            let property = estree_node_field_object(node, RawField::Property)?;
            let object_name = raw_callee_name(object)?;
            let property_name = raw_identifier_name(property)?;
            Some(format!("{object_name}.{property_name}").into())
        }
        _ => None,
    }
}

pub(crate) fn raw_base_identifier_name(node: &EstreeNode) -> Option<Arc<str>> {
    match estree_node_type(node) {
        Some("Identifier") => estree_node_field_str(node, RawField::Name).map(Arc::from),
        Some("MemberExpression") => {
            let object = estree_node_field_object(node, RawField::Object)?;
            raw_base_identifier_name(object)
        }
        _ => None,
    }
}

pub(crate) fn raw_member_property_name(node: &EstreeNode) -> Option<Arc<str>> {
    if estree_node_type(node) != Some("MemberExpression") {
        return None;
    }
    let property = estree_node_field_object(node, RawField::Property)?;
    raw_identifier_name(property)
}

pub(crate) fn raw_literal_string(node: &EstreeNode) -> Option<Arc<str>> {
    if estree_node_type(node) != Some("Literal") {
        return None;
    }
    match node.fields.get("value")? {
        EstreeValue::String(value) => Some(value.to_string().into()),
        _ => None,
    }
}

pub(crate) fn export_specifier_exported_name(specifier: &EstreeNode) -> Option<Arc<str>> {
    let value = specifier.fields.get("exported")?;
    let EstreeValue::Object(exported) = value else {
        return None;
    };

    match estree_node_type(exported) {
        Some("Identifier") => estree_node_field_str(exported, RawField::Name).map(Arc::from),
        Some("Literal") => raw_literal_string(exported),
        _ => None,
    }
}

pub(crate) fn unwrap_typescript_expression(mut node: &EstreeNode) -> &EstreeNode {
    loop {
        match estree_node_type(node) {
            Some(
                "ParenthesizedExpression"
                | "TSAsExpression"
                | "TSSatisfiesExpression"
                | "TSNonNullExpression"
                | "TSTypeAssertion",
            ) => {
                let Some(expression) = estree_node_field_object(node, RawField::Expression) else {
                    return node;
                };
                node = expression;
            }
            _ => return node,
        }
    }
}

pub(crate) fn is_identifier_or_member_expression(node: &EstreeNode) -> bool {
    matches!(
        estree_node_type(unwrap_typescript_expression(node)),
        Some("Identifier" | "MemberExpression")
    )
}

pub(crate) fn collect_assignment_target_identifiers(
    target: &EstreeNode,
    out: &mut impl Extend<Arc<str>>,
) {
    match estree_node_type(target) {
        Some("Identifier") => {
            if let Some(name) = raw_identifier_name(target) {
                out.extend([name]);
            }
        }
        Some("AssignmentPattern") => {
            if let Some(left) = estree_node_field_object(target, RawField::Left) {
                collect_assignment_target_identifiers(left, out);
            }
        }
        Some("RestElement") => {
            if let Some(argument) = estree_node_field_object(target, RawField::Argument) {
                collect_assignment_target_identifiers(argument, out);
            }
        }
        Some("ArrayPattern") => {
            if let Some(elements) = estree_node_field_array(target, RawField::Elements) {
                for element in elements {
                    let EstreeValue::Object(element) = element else {
                        continue;
                    };
                    collect_assignment_target_identifiers(element, out);
                }
            }
        }
        Some("ObjectPattern") => {
            if let Some(properties) = estree_node_field_array(target, RawField::Properties) {
                for property in properties {
                    let EstreeValue::Object(property) = property else {
                        continue;
                    };
                    match estree_node_type(property) {
                        Some("Property") => {
                            if let Some(value) = estree_node_field_object(property, RawField::Value)
                            {
                                collect_assignment_target_identifiers(value, out);
                            }
                        }
                        Some("RestElement") => {
                            if let Some(argument) =
                                estree_node_field_object(property, RawField::Argument)
                            {
                                collect_assignment_target_identifiers(argument, out);
                            }
                        }
                        _ => {}
                    }
                }
            }
        }
        _ => {}
    }
}

pub(crate) fn collect_pattern_binding_names<E>(pattern: &EstreeNode, out: &mut E)
where
    E: Extend<Arc<str>>,
{
    match estree_node_type(pattern) {
        Some("Identifier") => {
            if let Some(name) = estree_node_field_str(pattern, RawField::Name) {
                out.extend([Arc::from(name)]);
            }
        }
        Some("RestElement") => {
            if let Some(argument) = estree_node_field_object(pattern, RawField::Argument) {
                collect_pattern_binding_names(argument, out);
            }
        }
        Some("AssignmentPattern") => {
            if let Some(left) = estree_node_field_object(pattern, RawField::Left) {
                collect_pattern_binding_names(left, out);
            }
        }
        Some("ArrayPattern") => {
            if let Some(elements) = estree_node_field_array(pattern, RawField::Elements) {
                for element in elements {
                    let EstreeValue::Object(element) = element else {
                        continue;
                    };
                    collect_pattern_binding_names(element, out);
                }
            }
        }
        Some("ObjectPattern") => {
            if let Some(properties) = estree_node_field_array(pattern, RawField::Properties) {
                for property in properties {
                    let EstreeValue::Object(property) = property else {
                        continue;
                    };
                    match estree_node_type(property) {
                        Some("Property") => {
                            if let Some(value) = estree_node_field_object(property, RawField::Value)
                            {
                                collect_pattern_binding_names(value, out);
                            }
                        }
                        Some("RestElement") => {
                            if let Some(argument) =
                                estree_node_field_object(property, RawField::Argument)
                            {
                                collect_pattern_binding_names(argument, out);
                            }
                        }
                        _ => {}
                    }
                }
            }
        }
        _ => {}
    }
}

pub(crate) fn class_key_name(node: &EstreeNode) -> Option<Arc<str>> {
    match estree_node_type(node) {
        Some("Identifier") => estree_node_field_str(node, RawField::Name).map(Arc::from),
        Some("PrivateIdentifier") => {
            estree_node_field_str(node, RawField::Name).map(|name| Arc::from(format!("#{name}")))
        }
        Some("Literal") => match node.fields.get("value") {
            Some(EstreeValue::String(value)) => Some(value.clone()),
            Some(EstreeValue::Int(value)) => Some(Arc::from(value.to_string())),
            Some(EstreeValue::UInt(value)) => Some(Arc::from(value.to_string())),
            _ => None,
        },
        _ => None,
    }
}

pub(crate) fn this_member_name(node: &EstreeNode) -> Option<Arc<str>> {
    if estree_node_type(node) != Some("MemberExpression") {
        return None;
    }
    let object = estree_node_field_object(node, RawField::Object)?;
    if estree_node_type(object) != Some("ThisExpression") {
        return None;
    }
    let property = estree_node_field_object(node, RawField::Property)?;
    if estree_node_field_bool_named(node, "computed").unwrap_or(false)
        && estree_node_type(property) != Some("Literal")
    {
        return None;
    }
    class_key_name(property)
}

#[derive(Clone, Copy)]
pub(crate) struct PathStep<'a> {
    pub parent: &'a EstreeNode,
    pub via_key: &'a str,
}

pub(crate) fn walk_estree_node_with_path<'a>(
    node: &'a EstreeNode,
    path: &mut Vec<PathStep<'a>>,
    visitor: &mut impl FnMut(&'a EstreeNode, &[PathStep<'a>]),
) {
    visitor(node, path);
    for (key, value) in &node.fields {
        walk_estree_value_with_path(value, node, key.as_str(), path, visitor);
    }
}

pub(crate) fn walk_reference_identifiers_with_path<'a>(
    node: &'a EstreeNode,
    path: &mut Vec<PathStep<'a>>,
    visitor: &mut impl FnMut(&'a EstreeNode, &'a str, &[PathStep<'a>]),
) {
    walk_estree_node_with_path(node, path, &mut |current, current_path| {
        if estree_node_type(current) != Some("Identifier")
            || is_ignored_identifier_context(current_path)
            || is_type_identifier_context(current_path)
        {
            return;
        }
        let Some(name) = estree_node_field_str(current, RawField::Name) else {
            return;
        };
        visitor(current, name, current_path);
    });
}

pub(crate) fn path_has_function_scope(path: &[PathStep<'_>]) -> bool {
    path.iter().any(|step| {
        matches!(
            estree_node_type(step.parent),
            Some("FunctionDeclaration" | "FunctionExpression" | "ArrowFunctionExpression")
        )
    })
}

pub(crate) fn is_ignored_identifier_context(path: &[PathStep<'_>]) -> bool {
    let Some(step) = path.last() else {
        return false;
    };
    let parent_type = estree_node_type(step.parent);
    if matches!(
        parent_type,
        Some(
            "VariableDeclarator"
                | "FunctionDeclaration"
                | "FunctionExpression"
                | "ArrowFunctionExpression"
                | "ClassDeclaration"
                | "ImportSpecifier"
                | "ImportDefaultSpecifier"
                | "ImportNamespaceSpecifier"
                | "CatchClause"
                | "LabeledStatement"
                | "ExportSpecifier"
        )
    ) && matches!(
        step.via_key,
        "id" | "params" | "local" | "exported" | "param" | "label"
    ) {
        return true;
    }
    if parent_type == Some("MemberExpression") && step.via_key == "property" {
        return true;
    }
    if parent_type == Some("Property") && step.via_key == "key" {
        return true;
    }
    false
}

pub(crate) fn is_type_identifier_context(path: &[PathStep<'_>]) -> bool {
    path.iter().any(|step| {
        estree_node_type(step.parent)
            .is_some_and(|kind| kind.starts_with("TS") || kind == "TSTypeAnnotation")
    })
}

fn walk_estree_value_with_path<'a>(
    value: &'a EstreeValue,
    parent: &'a EstreeNode,
    via_key: &'a str,
    path: &mut Vec<PathStep<'a>>,
    visitor: &mut impl FnMut(&'a EstreeNode, &[PathStep<'a>]),
) {
    match value {
        EstreeValue::Object(node) => {
            path.push(PathStep { parent, via_key });
            walk_estree_node_with_path(node, path, visitor);
            path.pop();
        }
        EstreeValue::Array(values) => {
            for item in values {
                walk_estree_value_with_path(item, parent, via_key, path, visitor);
            }
        }
        EstreeValue::String(_)
        | EstreeValue::Int(_)
        | EstreeValue::UInt(_)
        | EstreeValue::Number(_)
        | EstreeValue::Bool(_)
        | EstreeValue::Null => {}
    }
}

fn estree_node_field_bool_named(node: &EstreeNode, key: &str) -> Option<bool> {
    match node.fields.get(key) {
        Some(EstreeValue::Bool(value)) => Some(*value),
        _ => None,
    }
}