#![allow(dead_code)]
#![allow(unused_imports)]
#![allow(unused_variables)]
use oxc_ast::ast::*;
use oxc_span::GetSpan;
use super::macros::{
detect_macro_kind, MacroArrayArg, MacroDeclarator, MacroObjectArg, MacroProperty,
MacroTypeParams, ScriptMacro, VueMacroKind,
};
use super::resolve_type::{infer_runtime_type, resolve_type_elements, ResolvedElements};
use super::shared::ScriptParseContext;
use super::types::{
DeclarationKind, ScriptAsync, ScriptBinding, ScriptDeclaration, ScriptError, ScriptErrorKind,
ScriptItem, ScriptTypeDeclaration, TypeDeclarationKind,
};
use super::usage::{
detect_vue_api_call, CallSiteContext, EmitCallUsage, EmitEventName, InjectUsage, LifecycleHook,
LifecycleUsage, ProvideKey, ProvideKeyKind, ProvideUsage, ReactiveKind, ReactiveStateUsage,
SyncContextUsage, TemplateUtilUsage, UsageCollector, VueApiCategory, VueApiKind, WatcherUsage,
};
use crate::common::Span;
pub struct SetupContext {
pub is_async: bool,
function_depth: u32,
block_depth: u32,
}
impl Default for SetupContext {
fn default() -> Self {
Self::new()
}
}
impl SetupContext {
pub fn new() -> Self {
Self {
is_async: false,
function_depth: 0,
block_depth: 0,
}
}
fn should_track_declarations(&self) -> bool {
self.block_depth == 0
}
pub fn enter_function(&mut self) {
self.function_depth += 1;
}
pub fn leave_function(&mut self) {
self.function_depth = self.function_depth.saturating_sub(1);
}
pub fn enter_block(&mut self) {
self.block_depth += 1;
}
pub fn leave_block(&mut self) {
self.block_depth = self.block_depth.saturating_sub(1);
}
}
pub fn process_setup_statements<'a>(
statements: &[Statement<'a>],
ctx: &ScriptParseContext<'a>,
setup_ctx: &mut SetupContext,
items: &mut Vec<ScriptItem<'a>>,
errors: &mut Vec<ScriptError>,
) {
for stmt in statements {
process_setup_statement(stmt, ctx, setup_ctx, items, errors);
}
}
pub fn process_setup_statement<'a>(
stmt: &Statement<'a>,
ctx: &ScriptParseContext<'a>,
setup_ctx: &mut SetupContext,
items: &mut Vec<ScriptItem<'a>>,
errors: &mut Vec<ScriptError>,
) {
match stmt {
Statement::ImportDeclaration(_) => {}
Statement::VariableDeclaration(var_decl) => {
process_variable_declaration(var_decl, ctx, setup_ctx, items);
}
Statement::FunctionDeclaration(func) => {
process_function_declaration(func, ctx, setup_ctx, items);
}
Statement::ClassDeclaration(class) => {
if setup_ctx.should_track_declarations() {
if let Some(id) = &class.id {
items.push(ScriptItem::Declaration(ScriptDeclaration {
span: ctx.adjust_span(class.span),
name: Some(id.name.as_str()),
name_span: Some(ctx.adjust_span(id.span)),
kind: DeclarationKind::Class,
is_ref_like: false,
}));
}
}
}
Statement::ExpressionStatement(expr_stmt) => {
process_expression_statement(expr_stmt, ctx, setup_ctx, items);
}
Statement::BlockStatement(block) => {
setup_ctx.enter_block();
process_setup_statements(&block.body, ctx, setup_ctx, items, errors);
setup_ctx.leave_block();
}
Statement::IfStatement(if_stmt) => {
check_expression_for_async(&if_stmt.test, ctx, setup_ctx, items);
setup_ctx.enter_block();
process_setup_statement(&if_stmt.consequent, ctx, setup_ctx, items, errors);
setup_ctx.leave_block();
if let Some(alt) = &if_stmt.alternate {
setup_ctx.enter_block();
process_setup_statement(alt, ctx, setup_ctx, items, errors);
setup_ctx.leave_block();
}
}
Statement::ForStatement(for_stmt) => {
setup_ctx.enter_block();
process_setup_statement(&for_stmt.body, ctx, setup_ctx, items, errors);
setup_ctx.leave_block();
}
Statement::ForInStatement(for_in) => {
setup_ctx.enter_block();
process_setup_statement(&for_in.body, ctx, setup_ctx, items, errors);
setup_ctx.leave_block();
}
Statement::ForOfStatement(for_of) => {
if for_of.r#await {
setup_ctx.is_async = true;
items.push(ScriptItem::Async(ScriptAsync {
span: ctx.adjust_span(for_of.span),
}));
}
setup_ctx.enter_block();
process_setup_statement(&for_of.body, ctx, setup_ctx, items, errors);
setup_ctx.leave_block();
}
Statement::WhileStatement(while_stmt) => {
setup_ctx.enter_block();
process_setup_statement(&while_stmt.body, ctx, setup_ctx, items, errors);
setup_ctx.leave_block();
}
Statement::DoWhileStatement(do_while) => {
setup_ctx.enter_block();
process_setup_statement(&do_while.body, ctx, setup_ctx, items, errors);
setup_ctx.leave_block();
}
Statement::SwitchStatement(switch_stmt) => {
setup_ctx.enter_block();
for case in &switch_stmt.cases {
for stmt in &case.consequent {
process_setup_statement(stmt, ctx, setup_ctx, items, errors);
}
}
setup_ctx.leave_block();
}
Statement::TryStatement(try_stmt) => {
setup_ctx.enter_block();
process_setup_statements(&try_stmt.block.body, ctx, setup_ctx, items, errors);
setup_ctx.leave_block();
if let Some(handler) = &try_stmt.handler {
setup_ctx.enter_block();
process_setup_statements(&handler.body.body, ctx, setup_ctx, items, errors);
setup_ctx.leave_block();
}
if let Some(finalizer) = &try_stmt.finalizer {
setup_ctx.enter_block();
process_setup_statements(&finalizer.body, ctx, setup_ctx, items, errors);
setup_ctx.leave_block();
}
}
Statement::ExportDefaultDeclaration(export) => {
errors.push(ScriptError {
span: ctx.adjust_span(export.span),
message: ScriptErrorKind::ExportDefaultInSetup,
});
}
Statement::ReturnStatement(ret) => {
errors.push(ScriptError {
span: ctx.adjust_span(ret.span),
message: ScriptErrorKind::ReturnInSetup,
});
}
Statement::ExportNamedDeclaration(_) | Statement::ExportAllDeclaration(_) => {}
Statement::TSTypeAliasDeclaration(type_alias) => {
items.push(ScriptItem::TypeDeclaration(ScriptTypeDeclaration {
span: ctx.adjust_span(type_alias.span),
name: Some(type_alias.id.name.as_str()),
kind: TypeDeclarationKind::TypeAlias,
}));
}
Statement::TSInterfaceDeclaration(interface) => {
items.push(ScriptItem::TypeDeclaration(ScriptTypeDeclaration {
span: ctx.adjust_span(interface.span),
name: Some(interface.id.name.as_str()),
kind: TypeDeclarationKind::Interface,
}));
}
Statement::TSEnumDeclaration(ts_enum) => {
items.push(ScriptItem::TypeDeclaration(ScriptTypeDeclaration {
span: ctx.adjust_span(ts_enum.span),
name: Some(ts_enum.id.name.as_str()),
kind: TypeDeclarationKind::Enum,
}));
}
Statement::TSModuleDeclaration(module) => {
let name = match &module.id {
oxc_ast::ast::TSModuleDeclarationName::Identifier(id) => Some(id.name.as_str()),
oxc_ast::ast::TSModuleDeclarationName::StringLiteral(s) => Some(s.value.as_str()),
};
items.push(ScriptItem::TypeDeclaration(ScriptTypeDeclaration {
span: ctx.adjust_span(module.span),
name,
kind: TypeDeclarationKind::Module,
}));
}
_ => {}
}
}
fn is_ref_creating_call(init: &Expression<'_>) -> bool {
if let Expression::CallExpression(call) = init {
if let Expression::Identifier(id) = &call.callee {
return matches!(
id.name.as_str(),
"ref" | "computed" | "shallowRef" | "customRef" | "toRef"
);
}
}
false
}
fn process_variable_declaration<'a>(
var_decl: &VariableDeclaration<'a>,
ctx: &ScriptParseContext<'a>,
setup_ctx: &mut SetupContext,
items: &mut Vec<ScriptItem<'a>>,
) {
let kind = match var_decl.kind {
VariableDeclarationKind::Const => DeclarationKind::Const,
VariableDeclarationKind::Let => DeclarationKind::Let,
VariableDeclarationKind::Var => DeclarationKind::Var,
VariableDeclarationKind::Using => DeclarationKind::Const,
VariableDeclarationKind::AwaitUsing => {
setup_ctx.is_async = true;
items.push(ScriptItem::Async(ScriptAsync {
span: ctx.adjust_span(var_decl.span),
}));
DeclarationKind::Const
}
};
for declarator in &var_decl.declarations {
let is_ref_like = kind == DeclarationKind::Const
&& declarator
.init
.as_ref()
.is_some_and(|init| is_ref_creating_call(init));
if let Some(init) = &declarator.init {
check_expression_for_async(init, ctx, setup_ctx, items);
let macro_declarator = Some(MacroDeclarator {
name: extract_binding_name(&declarator.id),
binding_span: ctx.adjust_span(declarator.id.span()),
statement_span: ctx.adjust_span(var_decl.span),
});
if let Some(macro_item) = try_parse_macro_from_expression(init, ctx, macro_declarator) {
items.push(ScriptItem::Macro(macro_item));
}
}
if setup_ctx.should_track_declarations() {
collect_declarations_from_pattern(&declarator.id, kind, is_ref_like, ctx, items);
}
}
}
fn extract_binding_name<'a>(pattern: &BindingPattern<'a>) -> Option<&'a str> {
match pattern {
BindingPattern::BindingIdentifier(id) => Some(id.name.as_str()),
_ => None, }
}
fn process_function_declaration<'a>(
func: &Function<'a>,
ctx: &ScriptParseContext<'a>,
setup_ctx: &mut SetupContext,
items: &mut Vec<ScriptItem<'a>>,
) {
if setup_ctx.should_track_declarations() {
if let Some(id) = &func.id {
let kind = match (func.r#async, func.generator) {
(true, true) => DeclarationKind::AsyncGeneratorFunction,
(true, false) => DeclarationKind::AsyncFunction,
(false, true) => DeclarationKind::GeneratorFunction,
(false, false) => DeclarationKind::Function,
};
items.push(ScriptItem::Declaration(ScriptDeclaration {
span: ctx.adjust_span(func.span),
name: Some(id.name.as_str()),
name_span: Some(ctx.adjust_span(id.span)),
kind,
is_ref_like: false,
}));
}
}
}
fn process_expression_statement<'a>(
expr_stmt: &ExpressionStatement<'a>,
ctx: &ScriptParseContext<'a>,
setup_ctx: &mut SetupContext,
items: &mut Vec<ScriptItem<'a>>,
) {
check_expression_for_async(&expr_stmt.expression, ctx, setup_ctx, items);
if let Some(macro_item) = try_parse_macro_from_expression(&expr_stmt.expression, ctx, None) {
items.push(ScriptItem::Macro(macro_item));
}
}
fn check_expression_for_async<'a>(
expr: &Expression<'a>,
ctx: &ScriptParseContext<'a>,
setup_ctx: &mut SetupContext,
items: &mut Vec<ScriptItem<'a>>,
) {
match expr {
Expression::AwaitExpression(await_expr) => {
setup_ctx.is_async = true;
items.push(ScriptItem::Async(ScriptAsync {
span: ctx.adjust_span(await_expr.span),
}));
check_expression_for_async(&await_expr.argument, ctx, setup_ctx, items);
}
Expression::CallExpression(call) => {
check_expression_for_async(&call.callee, ctx, setup_ctx, items);
for arg in &call.arguments {
if let Argument::SpreadElement(spread) = arg {
check_expression_for_async(&spread.argument, ctx, setup_ctx, items);
} else if let Some(expr) = arg.as_expression() {
check_expression_for_async(expr, ctx, setup_ctx, items);
}
}
}
Expression::BinaryExpression(bin) => {
check_expression_for_async(&bin.left, ctx, setup_ctx, items);
check_expression_for_async(&bin.right, ctx, setup_ctx, items);
}
Expression::ConditionalExpression(cond) => {
check_expression_for_async(&cond.test, ctx, setup_ctx, items);
check_expression_for_async(&cond.consequent, ctx, setup_ctx, items);
check_expression_for_async(&cond.alternate, ctx, setup_ctx, items);
}
Expression::AssignmentExpression(assign) => {
check_expression_for_async(&assign.right, ctx, setup_ctx, items);
}
Expression::ParenthesizedExpression(paren) => {
check_expression_for_async(&paren.expression, ctx, setup_ctx, items);
}
Expression::SequenceExpression(seq) => {
for expr in &seq.expressions {
check_expression_for_async(expr, ctx, setup_ctx, items);
}
}
Expression::UnaryExpression(unary) => {
check_expression_for_async(&unary.argument, ctx, setup_ctx, items);
}
Expression::LogicalExpression(logical) => {
check_expression_for_async(&logical.left, ctx, setup_ctx, items);
check_expression_for_async(&logical.right, ctx, setup_ctx, items);
}
Expression::ComputedMemberExpression(computed) => {
check_expression_for_async(&computed.object, ctx, setup_ctx, items);
check_expression_for_async(&computed.expression, ctx, setup_ctx, items);
}
Expression::StaticMemberExpression(static_member) => {
check_expression_for_async(&static_member.object, ctx, setup_ctx, items);
}
Expression::PrivateFieldExpression(private) => {
check_expression_for_async(&private.object, ctx, setup_ctx, items);
}
Expression::ArrayExpression(arr) => {
for elem in &arr.elements {
if let ArrayExpressionElement::SpreadElement(spread) = elem {
check_expression_for_async(&spread.argument, ctx, setup_ctx, items);
} else if let Some(expr) = elem.as_expression() {
check_expression_for_async(expr, ctx, setup_ctx, items);
}
}
}
Expression::ObjectExpression(obj) => {
for prop in &obj.properties {
match prop {
ObjectPropertyKind::ObjectProperty(p) => {
check_expression_for_async(&p.value, ctx, setup_ctx, items);
}
ObjectPropertyKind::SpreadProperty(spread) => {
check_expression_for_async(&spread.argument, ctx, setup_ctx, items);
}
}
}
}
Expression::NewExpression(new_expr) => {
check_expression_for_async(&new_expr.callee, ctx, setup_ctx, items);
for arg in &new_expr.arguments {
if let Argument::SpreadElement(spread) = arg {
check_expression_for_async(&spread.argument, ctx, setup_ctx, items);
} else if let Some(expr) = arg.as_expression() {
check_expression_for_async(expr, ctx, setup_ctx, items);
}
}
}
Expression::TaggedTemplateExpression(tagged) => {
check_expression_for_async(&tagged.tag, ctx, setup_ctx, items);
}
Expression::TemplateLiteral(template) => {
for expr in &template.expressions {
check_expression_for_async(expr, ctx, setup_ctx, items);
}
}
Expression::YieldExpression(yield_expr) => {
if let Some(arg) = &yield_expr.argument {
check_expression_for_async(arg, ctx, setup_ctx, items);
}
}
Expression::FunctionExpression(_) | Expression::ArrowFunctionExpression(_) => {}
_ => {}
}
}
fn try_parse_macro_from_expression<'a>(
expr: &Expression<'a>,
ctx: &ScriptParseContext<'a>,
declarator: Option<MacroDeclarator<'a>>,
) -> Option<ScriptMacro<'a>> {
match expr {
Expression::CallExpression(call) => parse_macro_call(call, ctx, declarator),
_ => None,
}
}
pub fn parse_macro_call<'a>(
call: &CallExpression<'a>,
ctx: &ScriptParseContext<'a>,
declarator: Option<MacroDeclarator<'a>>,
) -> Option<ScriptMacro<'a>> {
let name = match &call.callee {
Expression::Identifier(id) => id.name.as_bytes(),
_ => return None,
};
let kind = detect_macro_kind(name)?;
let span = ctx.adjust_span(call.span);
let type_params = call
.type_arguments
.as_ref()
.map(|tp| extract_type_params(tp, ctx));
match kind {
VueMacroKind::DefineProps => {
let (object_arg, array_arg) = extract_arg_spans(call, ctx);
Some(ScriptMacro::DefineProps {
span,
declarator,
type_params,
object_arg,
array_arg,
})
}
VueMacroKind::DefineEmits => {
let (object_arg, array_arg) = extract_arg_spans(call, ctx);
Some(ScriptMacro::DefineEmits {
span,
declarator,
type_params,
object_arg,
array_arg,
})
}
VueMacroKind::DefineExpose => {
let object_arg = extract_object_arg_from_call(call, 0, ctx);
Some(ScriptMacro::DefineExpose {
span,
declarator,
object_arg,
})
}
VueMacroKind::DefineOptions => {
let object_arg = extract_object_arg_from_call(call, 0, ctx);
Some(ScriptMacro::DefineOptions {
span,
declarator,
object_arg,
})
}
VueMacroKind::DefineModel => {
let name_span = call.arguments.first().and_then(|arg| {
if let Some(Expression::StringLiteral(s)) = arg.as_expression() {
Some(ctx.adjust_span(s.span))
} else {
None
}
});
let options_idx = if name_span.is_some() { 1 } else { 0 };
let options_span = call.arguments.get(options_idx).and_then(|arg| {
if let Some(Expression::ObjectExpression(obj)) = arg.as_expression() {
Some(ctx.adjust_span(obj.span))
} else {
None
}
});
Some(ScriptMacro::DefineModel {
span,
declarator,
type_params,
name_span,
options_span,
})
}
VueMacroKind::DefineSlots => Some(ScriptMacro::DefineSlots {
span,
declarator,
type_params,
}),
VueMacroKind::WithDefaults => {
let (define_props_span, define_props_type_params) = call
.arguments
.first()
.and_then(|arg| {
if let Some(Expression::CallExpression(inner)) = arg.as_expression() {
if let Expression::Identifier(id) = &inner.callee {
if id.name.as_bytes() == b"defineProps" {
let inner_type_params = inner
.type_arguments
.as_ref()
.map(|tp| extract_type_params(tp, ctx));
return Some((
Some(ctx.adjust_span(inner.span)),
inner_type_params,
));
}
}
}
None
})
.unwrap_or((None, None));
let defaults = extract_object_arg_from_call(call, 1, ctx);
Some(ScriptMacro::WithDefaults {
span,
declarator,
define_props_span,
define_props_type_params,
defaults,
})
}
}
}
fn extract_type_params(
tp: &TSTypeParameterInstantiation<'_>,
ctx: &ScriptParseContext<'_>,
) -> MacroTypeParams {
let full_span = tp.span;
let lt_span = ctx.adjust_span(oxc_span::Span::new(full_span.start, full_span.start + 1));
let gt_span = ctx.adjust_span(oxc_span::Span::new(full_span.end - 1, full_span.end));
let type_span = ctx.adjust_span(oxc_span::Span::new(full_span.start + 1, full_span.end - 1));
let resolved = tp
.params
.first()
.map(|ts_type| resolve_type_elements(ts_type, ctx.base_offset))
.unwrap_or_default();
let runtime_types = tp
.params
.first()
.map(|ts_type| infer_runtime_type(ts_type))
.unwrap_or_default();
MacroTypeParams {
lt_span,
type_span,
gt_span,
resolved,
runtime_types,
}
}
fn extract_arg_spans<'a>(
call: &CallExpression<'a>,
ctx: &ScriptParseContext<'a>,
) -> (Option<MacroObjectArg<'a>>, Option<MacroArrayArg>) {
let object_arg = extract_object_arg_from_call(call, 0, ctx);
let array_arg = extract_array_arg_from_call(call, 0, ctx);
(object_arg, array_arg)
}
fn extract_object_arg_from_call<'a>(
call: &CallExpression<'a>,
index: usize,
ctx: &ScriptParseContext<'a>,
) -> Option<MacroObjectArg<'a>> {
call.arguments.get(index).and_then(|arg| {
if let Some(Expression::ObjectExpression(obj)) = arg.as_expression() {
Some(extract_object_arg(obj, ctx))
} else {
None
}
})
}
fn extract_array_arg_from_call(
call: &CallExpression<'_>,
index: usize,
ctx: &ScriptParseContext<'_>,
) -> Option<MacroArrayArg> {
call.arguments.get(index).and_then(|arg| {
if let Some(Expression::ArrayExpression(arr)) = arg.as_expression() {
Some(extract_array_arg(arr, ctx))
} else {
None
}
})
}
fn extract_object_arg<'a>(
obj: &ObjectExpression<'a>,
ctx: &ScriptParseContext<'a>,
) -> MacroObjectArg<'a> {
let mut properties = Vec::new();
for prop in &obj.properties {
if let ObjectPropertyKind::ObjectProperty(p) = prop {
if let Some((name, name_span)) = extract_property_key(&p.key, ctx) {
let value_span = if p.shorthand {
None
} else {
Some(ctx.adjust_span(p.value.span()))
};
properties.push(MacroProperty {
name,
name_span,
value_span,
});
}
}
}
MacroObjectArg {
span: ctx.adjust_span(obj.span),
properties,
}
}
fn extract_array_arg(arr: &ArrayExpression<'_>, ctx: &ScriptParseContext<'_>) -> MacroArrayArg {
let element_spans = arr
.elements
.iter()
.filter_map(|elem| match elem {
ArrayExpressionElement::SpreadElement(s) => Some(ctx.adjust_span(s.span)),
ArrayExpressionElement::Elision(_) => None,
_ => elem.as_expression().map(|e| ctx.adjust_span(e.span())),
})
.collect();
MacroArrayArg {
span: ctx.adjust_span(arr.span),
element_spans,
}
}
fn extract_property_key<'a>(
key: &PropertyKey<'a>,
ctx: &ScriptParseContext<'a>,
) -> Option<(&'a str, Span)> {
match key {
PropertyKey::StaticIdentifier(id) => Some((id.name.as_str(), ctx.adjust_span(id.span))),
PropertyKey::StringLiteral(s) => Some((s.value.as_str(), ctx.adjust_span(s.span))),
PropertyKey::NumericLiteral(n) => {
None
}
_ => None,
}
}
fn collect_declarations_from_pattern<'a>(
pattern: &BindingPattern<'a>,
kind: DeclarationKind,
is_ref_like: bool,
ctx: &ScriptParseContext<'a>,
items: &mut Vec<ScriptItem<'a>>,
) {
match pattern {
BindingPattern::BindingIdentifier(id) => {
items.push(ScriptItem::Declaration(ScriptDeclaration {
span: ctx.adjust_span(id.span),
name: Some(id.name.as_str()),
name_span: Some(ctx.adjust_span(id.span)),
kind,
is_ref_like,
}));
}
BindingPattern::ObjectPattern(obj) => {
for prop in &obj.properties {
collect_declarations_from_pattern(&prop.value, kind, false, ctx, items);
}
if let Some(rest) = &obj.rest {
collect_declarations_from_pattern(&rest.argument, kind, false, ctx, items);
}
}
BindingPattern::ArrayPattern(arr) => {
for elem in arr.elements.iter().flatten() {
collect_declarations_from_pattern(elem, kind, false, ctx, items);
}
if let Some(rest) = &arr.rest {
collect_declarations_from_pattern(&rest.argument, kind, false, ctx, items);
}
}
BindingPattern::AssignmentPattern(assign) => {
collect_declarations_from_pattern(&assign.left, kind, is_ref_like, ctx, items);
}
}
}
pub fn check_expression_for_usage<'a>(
expr: &Expression<'a>,
ctx: &ScriptParseContext<'a>,
usage_ctx: &mut UsageCollector<'a>,
current_binding_span: Option<Span>,
) {
match expr {
Expression::AwaitExpression(await_expr) => {
usage_ctx.record_await(ctx.adjust_span(await_expr.span));
check_expression_for_usage(&await_expr.argument, ctx, usage_ctx, None);
}
Expression::CallExpression(call) => {
if let Expression::Identifier(id) = &call.callee {
if let Some(api_kind) = detect_vue_api_call(id.name.as_bytes()) {
collect_api_usage(call, api_kind, ctx, usage_ctx, current_binding_span);
}
}
for arg in &call.arguments {
if let Some(expr) = arg.as_expression() {
check_expression_for_usage(expr, ctx, usage_ctx, None);
}
}
}
Expression::BinaryExpression(bin) => {
check_expression_for_usage(&bin.left, ctx, usage_ctx, None);
check_expression_for_usage(&bin.right, ctx, usage_ctx, None);
}
Expression::ConditionalExpression(cond) => {
check_expression_for_usage(&cond.test, ctx, usage_ctx, None);
check_expression_for_usage(&cond.consequent, ctx, usage_ctx, None);
check_expression_for_usage(&cond.alternate, ctx, usage_ctx, None);
}
Expression::AssignmentExpression(assign) => {
check_expression_for_usage(&assign.right, ctx, usage_ctx, None);
}
Expression::ParenthesizedExpression(paren) => {
check_expression_for_usage(&paren.expression, ctx, usage_ctx, current_binding_span);
}
Expression::SequenceExpression(seq) => {
for expr in &seq.expressions {
check_expression_for_usage(expr, ctx, usage_ctx, None);
}
}
Expression::LogicalExpression(logical) => {
check_expression_for_usage(&logical.left, ctx, usage_ctx, None);
check_expression_for_usage(&logical.right, ctx, usage_ctx, None);
}
Expression::ArrayExpression(arr) => {
for elem in &arr.elements {
if let Some(expr) = elem.as_expression() {
check_expression_for_usage(expr, ctx, usage_ctx, None);
}
}
}
Expression::ObjectExpression(obj) => {
for prop in &obj.properties {
if let ObjectPropertyKind::ObjectProperty(p) = prop {
check_expression_for_usage(&p.value, ctx, usage_ctx, None);
}
}
}
Expression::FunctionExpression(_) | Expression::ArrowFunctionExpression(_) => {}
_ => {}
}
}
fn collect_api_usage<'a>(
call: &CallExpression<'a>,
kind: VueApiKind,
ctx: &ScriptParseContext<'a>,
usage_ctx: &mut UsageCollector<'a>,
binding_span: Option<Span>,
) {
let span = ctx.adjust_span(call.span);
match kind.category() {
VueApiCategory::DependencyInjection => {
collect_di_usage(call, kind, ctx, usage_ctx, binding_span, span);
}
VueApiCategory::Reactivity => {
collect_reactivity_usage(kind, usage_ctx, binding_span, span, call, ctx);
}
VueApiCategory::Lifecycle => {
collect_lifecycle_usage(call, kind, ctx, usage_ctx, span);
}
VueApiCategory::Watchers => {
collect_watcher_usage(call, kind, ctx, usage_ctx, span);
}
VueApiCategory::TemplateUtils => {
collect_template_util_usage(call, kind, ctx, usage_ctx, binding_span, span);
}
VueApiCategory::InstanceAccess => {
collect_instance_access_usage(ctx, usage_ctx, binding_span, span);
}
}
}
fn collect_di_usage<'a>(
call: &CallExpression<'a>,
kind: VueApiKind,
ctx: &ScriptParseContext<'a>,
usage_ctx: &mut UsageCollector<'a>,
binding_span: Option<Span>,
span: Span,
) {
match kind {
VueApiKind::Provide => {
if let Some(usage) = extract_provide_usage(call, ctx, span) {
usage_ctx.record_provide(usage);
}
}
VueApiKind::Inject => {
if let Some(usage) = extract_inject_usage(call, ctx, binding_span, span) {
usage_ctx.record_inject(usage);
}
}
_ => {}
}
}
fn extract_provide_usage<'a>(
call: &CallExpression<'a>,
ctx: &ScriptParseContext<'a>,
span: Span,
) -> Option<ProvideUsage> {
let key = call
.arguments
.first()
.and_then(|arg| extract_provide_key(arg.as_expression()?, ctx))?;
let value_span = call
.arguments
.get(1)
.and_then(|a| a.as_expression())
.map(|e| ctx.adjust_span(e.span()))?;
Some(ProvideUsage {
span,
key,
value_span,
})
}
fn extract_inject_usage<'a>(
call: &CallExpression<'a>,
ctx: &ScriptParseContext<'a>,
binding_span: Option<Span>,
span: Span,
) -> Option<InjectUsage> {
let key = call
.arguments
.first()
.and_then(|arg| extract_provide_key(arg.as_expression()?, ctx))?;
let has_default = call.arguments.len() > 1;
Some(InjectUsage {
span,
key,
has_default,
binding_span,
})
}
fn extract_provide_key<'a>(
expr: &Expression<'a>,
ctx: &ScriptParseContext<'a>,
) -> Option<ProvideKey> {
match expr {
Expression::StringLiteral(s) => Some(ProvideKey {
span: ctx.adjust_span(s.span),
kind: ProvideKeyKind::StringLiteral,
}),
Expression::Identifier(id) => Some(ProvideKey {
span: ctx.adjust_span(id.span),
kind: ProvideKeyKind::Symbol,
}),
_ => Some(ProvideKey {
span: ctx.adjust_span(expr.span()),
kind: ProvideKeyKind::Dynamic,
}),
}
}
fn collect_reactivity_usage<'a>(
kind: VueApiKind,
usage_ctx: &mut UsageCollector<'a>,
binding_span: Option<Span>,
span: Span,
call: &CallExpression<'a>,
ctx: &ScriptParseContext<'a>,
) {
if let Some(reactive_kind) = ReactiveKind::from_api_kind(kind) {
if let Some(binding) = binding_span {
let initializer_span = call
.arguments
.first()
.and_then(|a| a.as_expression())
.map(|e| ctx.adjust_span(e.span()));
usage_ctx.record_reactive(ReactiveStateUsage {
kind: reactive_kind,
binding_span: binding,
initializer_span,
});
}
}
}
fn collect_lifecycle_usage<'a>(
call: &CallExpression<'a>,
kind: VueApiKind,
ctx: &ScriptParseContext<'a>,
usage_ctx: &mut UsageCollector<'a>,
span: Span,
) {
if let Some(hook) = LifecycleHook::from_api_kind(kind) {
let callback_span = call
.arguments
.first()
.and_then(|a| a.as_expression())
.map(|e| ctx.adjust_span(e.span()))
.unwrap_or(span);
usage_ctx.record_lifecycle(LifecycleUsage {
span,
hook,
callback_span,
});
}
}
fn collect_watcher_usage<'a>(
call: &CallExpression<'a>,
kind: VueApiKind,
ctx: &ScriptParseContext<'a>,
usage_ctx: &mut UsageCollector<'a>,
span: Span,
) {
let (callback_span, source_spans) = match kind {
VueApiKind::Watch => {
let source_spans: Vec<Span> = call
.arguments
.first()
.and_then(|a| a.as_expression())
.map(|e| vec![ctx.adjust_span(e.span())])
.unwrap_or_default();
let callback_span = call
.arguments
.get(1)
.and_then(|a| a.as_expression())
.map(|e| ctx.adjust_span(e.span()))
.unwrap_or(span);
(callback_span, source_spans)
}
_ => {
let callback_span = call
.arguments
.first()
.and_then(|a| a.as_expression())
.map(|e| ctx.adjust_span(e.span()))
.unwrap_or(span);
(callback_span, Vec::new())
}
};
usage_ctx.record_watcher(WatcherUsage {
span,
kind,
callback_span,
source_spans,
});
}
fn collect_template_util_usage<'a>(
call: &CallExpression<'a>,
kind: VueApiKind,
ctx: &ScriptParseContext<'a>,
usage_ctx: &mut UsageCollector<'a>,
binding_span: Option<Span>,
span: Span,
) {
let ref_name_span = if kind == VueApiKind::UseTemplateRef {
call.arguments
.first()
.and_then(|a| a.as_expression())
.and_then(|e| {
if let Expression::StringLiteral(s) = e {
Some(ctx.adjust_span(s.span))
} else {
None
}
})
} else {
None
};
usage_ctx.record_template_util(TemplateUtilUsage {
span,
kind,
binding_span,
ref_name_span,
});
}
fn collect_instance_access_usage(
_ctx: &ScriptParseContext<'_>,
usage_ctx: &mut UsageCollector<'_>,
binding_span: Option<Span>,
span: Span,
) {
let (context, preceding_await_span) = if usage_ctx.has_await_before(span.start) {
(CallSiteContext::AfterAwait, usage_ctx.first_await_span())
} else {
(CallSiteContext::BeforeAwait, None)
};
usage_ctx.record_sync_context_usage(SyncContextUsage {
span,
kind: VueApiKind::GetCurrentInstance,
context,
binding_span,
preceding_await_span,
});
}