vize_croquis 0.76.0

Croquis - Semantic analysis layer for Vize. Quick sketches of meaning from Vue templates.
Documentation
//! Statement and variable processing for Vue scripts.
//!
//! Handles processing of:
//! - Variable declarations (const, let, var)
//! - Function and class declarations
//! - Import and export statements
//! - Type declarations
//!
//! This module is split into:
//! - `macros`: Variable declarator processing (macros, reactivity, inject)
//! - `bindings`: Binding pattern helpers and expression classification

mod bindings;
mod macros;

use oxc_ast::ast::{Declaration, Expression, Statement};
use oxc_span::GetSpan;

use crate::analysis::{
    ImportStatementInfo, InvalidExport, InvalidExportKind, ReExportInfo, TypeExport, TypeExportKind,
};
use crate::scope::{BlockKind, BlockScopeData, ClosureScopeData, ExternalModuleScopeData};
use crate::ScopeBinding;
use vize_carton::CompactString;
use vize_relief::BindingType;

use super::extract::{
    detect_setup_context_violation, process_call_expression, process_invalid_export,
    process_type_export,
};
use super::walk::{extract_function_params, walk_expression, walk_statement};
use super::ScriptParseResult;

/// Process a single statement
pub fn process_statement(result: &mut ScriptParseResult, stmt: &Statement<'_>, source: &str) {
    match stmt {
        // Variable declarations: const, let, var
        Statement::VariableDeclaration(decl) => {
            for declarator in decl.declarations.iter() {
                macros::process_variable_declarator(result, declarator, decl.kind, source);
            }
        }

        // Function declarations
        Statement::FunctionDeclaration(func) => {
            if let Some(id) = &func.id {
                let name = id.name.as_str();
                result.bindings.add(name, BindingType::SetupConst);
                result
                    .binding_spans
                    .insert(CompactString::new(name), (id.span.start, id.span.end));
            }

            // Create closure scope and walk body
            let params = extract_function_params(&func.params);
            let name = func
                .id
                .as_ref()
                .map(|id| CompactString::new(id.name.as_str()));

            result.scopes.enter_closure_scope(
                ClosureScopeData {
                    name,
                    param_names: params,
                    is_arrow: false,
                    is_async: func.r#async,
                    is_generator: func.generator,
                },
                func.span.start,
                func.span.end,
            );

            if let Some(body) = &func.body {
                for stmt in body.statements.iter() {
                    walk_statement(result, stmt, source);
                }
            }

            result.scopes.exit_scope();
        }

        // Class declarations
        Statement::ClassDeclaration(class) => {
            if let Some(id) = &class.id {
                let name = id.name.as_str();
                result.bindings.add(name, BindingType::SetupConst);
                result
                    .binding_spans
                    .insert(CompactString::new(name), (id.span.start, id.span.end));
            }
        }

        // Expression statements (may contain macro calls and callback scopes)
        Statement::ExpressionStatement(expr_stmt) => {
            if let Expression::CallExpression(call) = &expr_stmt.expression {
                // Detect setup context violations (watch, onMounted, etc.)
                detect_setup_context_violation(result, call);
                process_call_expression(result, call, source);
            }
            // Walk the expression to find callback scopes
            walk_expression(result, &expr_stmt.expression, source);
        }

        // Module declarations (imports, exports)
        Statement::ImportDeclaration(import) => {
            result.import_statements.push(ImportStatementInfo {
                start: import.span.start,
                end: import.span.end,
            });

            let is_type_only = import.import_kind.is_type();

            // Create external module scope for this import
            let source_name = import.source.value.as_str();
            let span = import.span;

            result.scopes.enter_external_module_scope(
                ExternalModuleScopeData {
                    source: CompactString::new(source_name),
                    is_type_only,
                },
                span.start,
                span.end,
            );

            if let Some(specifiers) = &import.specifiers {
                for spec in specifiers.iter() {
                    let (name, is_type_spec, local_span) = match spec {
                        oxc_ast::ast::ImportDeclarationSpecifier::ImportSpecifier(s) => {
                            (s.local.name.as_str(), s.import_kind.is_type(), s.local.span)
                        }
                        oxc_ast::ast::ImportDeclarationSpecifier::ImportDefaultSpecifier(s) => {
                            (s.local.name.as_str(), false, s.local.span)
                        }
                        oxc_ast::ast::ImportDeclarationSpecifier::ImportNamespaceSpecifier(s) => {
                            (s.local.name.as_str(), false, s.local.span)
                        }
                    };

                    // Record definition span for Go-to-Definition
                    result
                        .binding_spans
                        .insert(CompactString::new(name), (local_span.start, local_span.end));

                    // Determine binding type based on specifier kind:
                    // - Named imports (ImportSpecifier) -> SetupMaybeRef (could be ref/reactive)
                    // - Default/Namespace imports -> SetupConst
                    let binding_type = if is_type_only || is_type_spec {
                        BindingType::ExternalModule
                    } else {
                        match spec {
                            oxc_ast::ast::ImportDeclarationSpecifier::ImportSpecifier(_) => {
                                BindingType::SetupMaybeRef
                            }
                            _ => BindingType::SetupConst, // default/namespace
                        }
                    };
                    result.scopes.add_binding(
                        CompactString::new(name),
                        ScopeBinding::new(binding_type, span.start),
                    );

                    // Only add to bindings if not type-only
                    if !is_type_only && !is_type_spec {
                        result.bindings.add(name, binding_type);
                    }
                }
            }

            result.scopes.exit_scope();
        }

        Statement::ExportNamedDeclaration(export) => {
            // Re-export: `export { ... } from "..."`
            if export.source.is_some() {
                result.re_exports.push(ReExportInfo {
                    start: export.span.start,
                    end: export.span.end,
                });
                return;
            }

            if let Some(decl) = &export.declaration {
                // Check if the declaration itself is a type declaration
                match decl {
                    Declaration::TSTypeAliasDeclaration(_)
                    | Declaration::TSInterfaceDeclaration(_) => {
                        // Type exports are valid in script setup
                        process_type_export(result, decl, stmt.span());
                    }
                    _ => {
                        // Check if it's a type-only export (export type { ... })
                        if export.export_kind.is_type() {
                            process_type_export(result, decl, stmt.span());
                        } else {
                            // Value exports are invalid in script setup
                            process_invalid_export(result, decl, stmt.span());
                        }
                    }
                }
            }
        }

        Statement::ExportDefaultDeclaration(export) => {
            // Default exports are invalid in script setup
            result.invalid_exports.push(InvalidExport {
                name: CompactString::new("default"),
                kind: InvalidExportKind::Default,
                start: export.span.start,
                end: export.span.end,
            });
        }

        // Type declarations at top level
        Statement::TSTypeAliasDeclaration(type_alias) => {
            // Type aliases are allowed (not bindings, but tracked)
            let name = type_alias.id.name.as_str();
            result.type_exports.push(TypeExport {
                name: CompactString::new(name),
                kind: TypeExportKind::Type,
                start: type_alias.span.start,
                end: type_alias.span.end,
                hoisted: true,
            });
        }

        Statement::TSInterfaceDeclaration(interface) => {
            // Interfaces are allowed (not bindings, but tracked)
            let name = interface.id.name.as_str();
            result.type_exports.push(TypeExport {
                name: CompactString::new(name),
                kind: TypeExportKind::Interface,
                start: interface.span.start,
                end: interface.span.end,
                hoisted: true,
            });
        }

        // Block statements at top level (scoped blocks)
        Statement::BlockStatement(block) => {
            result.scopes.enter_block_scope(
                BlockScopeData {
                    kind: BlockKind::Block,
                },
                block.span.start,
                block.span.end,
            );
            for stmt in block.body.iter() {
                walk_statement(result, stmt, source);
            }
            result.scopes.exit_scope();
        }

        _ => {}
    }
}