#![allow(dead_code)]
use crate::common::Span;
use crate::utils::oxc::vue::{
ComponentUsageInfo,
DeclarationKind,
EmitCallUsage,
InjectUsage,
LifecycleUsage,
ProvideUsage,
ReactiveStateUsage,
ScriptImport,
ScriptItem,
ScriptMacro,
ScriptParseResult,
SlotDefinitionInfo,
SlotUsageInfo,
TemplateRefAttrUsage,
TemplateUsageCollector,
UsageCollector,
VueMacroKind,
WatcherUsage,
};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct FileUsageFlags {
bits: u32,
}
impl FileUsageFlags {
pub const HAS_PROVIDE: u32 = 1 << 0;
pub const HAS_INJECT: u32 = 1 << 1;
pub const HAS_LIFECYCLE_HOOKS: u32 = 1 << 2;
pub const HAS_REACTIVE_STATE: u32 = 1 << 3;
pub const HAS_WATCHERS: u32 = 1 << 4;
pub const HAS_EMIT_CALLS: u32 = 1 << 5;
pub const HAS_TEMPLATE_UTILS: u32 = 1 << 6;
pub const IS_ASYNC_SETUP: u32 = 1 << 7;
pub const HAS_TEMPLATE_REFS: u32 = 1 << 8;
pub const HAS_SLOT_USAGE: u32 = 1 << 9;
pub const HAS_COMPONENT_USAGE: u32 = 1 << 10;
pub const HAS_SLOT_DEFINITIONS: u32 = 1 << 11;
pub const HAS_IMPORTS: u32 = 1 << 12;
pub const HAS_EXPORTS: u32 = 1 << 13;
pub const HAS_MACROS: u32 = 1 << 14;
pub const IS_SETUP_SCRIPT: u32 = 1 << 15;
pub const HAS_DEFINE_PROPS: u32 = 1 << 16;
pub const HAS_DEFINE_EMITS: u32 = 1 << 17;
pub const HAS_DEFINE_MODEL: u32 = 1 << 18;
pub const HAS_DEFINE_EXPOSE: u32 = 1 << 19;
pub const HAS_DEFINE_OPTIONS: u32 = 1 << 20;
pub const HAS_DEFINE_SLOTS: u32 = 1 << 21;
pub const HAS_WITH_DEFAULTS: u32 = 1 << 22;
#[inline]
pub const fn new() -> Self {
Self { bits: 0 }
}
#[inline]
pub fn set(&mut self, flag: u32) {
self.bits |= flag;
}
#[inline]
pub const fn has(&self, flag: u32) -> bool {
(self.bits & flag) != 0
}
#[inline]
pub const fn bits(&self) -> u32 {
self.bits
}
#[inline]
pub fn merge(&mut self, other: &Self) {
self.bits |= other.bits;
}
#[inline]
pub const fn from_bits(bits: u32) -> Self {
Self { bits }
}
}
#[derive(Debug, Clone)]
pub struct ImportInfo {
pub span: Span,
pub source_span: Span,
pub binding_spans: Vec<Span>,
pub is_type_only: bool,
}
impl ImportInfo {
pub fn from_script_import(import: &ScriptImport<'_>) -> Self {
Self {
span: import.span,
source_span: import.source_span,
binding_spans: import.bindings.iter().map(|b| b.span).collect(),
is_type_only: import.is_type_only,
}
}
}
#[derive(Debug, Clone)]
pub struct MacroInfo {
pub span: Span,
pub kind: MacroKind,
pub is_type_based: bool,
pub binding_span: Option<Span>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[repr(u8)]
pub enum MacroKind {
DefineProps,
DefineEmits,
DefineModel,
DefineExpose,
DefineOptions,
DefineSlots,
WithDefaults,
}
impl From<VueMacroKind> for MacroKind {
fn from(kind: VueMacroKind) -> Self {
match kind {
VueMacroKind::DefineProps => Self::DefineProps,
VueMacroKind::DefineEmits => Self::DefineEmits,
VueMacroKind::DefineModel => Self::DefineModel,
VueMacroKind::DefineExpose => Self::DefineExpose,
VueMacroKind::DefineOptions => Self::DefineOptions,
VueMacroKind::DefineSlots => Self::DefineSlots,
VueMacroKind::WithDefaults => Self::WithDefaults,
}
}
}
impl MacroKind {
pub const fn flag(&self) -> u32 {
match self {
Self::DefineProps => FileUsageFlags::HAS_DEFINE_PROPS,
Self::DefineEmits => FileUsageFlags::HAS_DEFINE_EMITS,
Self::DefineModel => FileUsageFlags::HAS_DEFINE_MODEL,
Self::DefineExpose => FileUsageFlags::HAS_DEFINE_EXPOSE,
Self::DefineOptions => FileUsageFlags::HAS_DEFINE_OPTIONS,
Self::DefineSlots => FileUsageFlags::HAS_DEFINE_SLOTS,
Self::WithDefaults => FileUsageFlags::HAS_WITH_DEFAULTS,
}
}
}
impl MacroInfo {
pub fn from_script_macro(macro_item: &ScriptMacro<'_>) -> Self {
let (is_type_based, binding_span) = match macro_item {
ScriptMacro::DefineProps {
type_params,
declarator,
..
} => (
type_params.is_some(),
declarator.as_ref().map(|d| d.binding_span),
),
ScriptMacro::DefineEmits {
type_params,
declarator,
..
} => (
type_params.is_some(),
declarator.as_ref().map(|d| d.binding_span),
),
ScriptMacro::DefineExpose { declarator, .. } => {
(false, declarator.as_ref().map(|d| d.binding_span))
}
ScriptMacro::DefineOptions { declarator, .. } => {
(false, declarator.as_ref().map(|d| d.binding_span))
}
ScriptMacro::DefineModel {
type_params,
declarator,
..
} => (
type_params.is_some(),
declarator.as_ref().map(|d| d.binding_span),
),
ScriptMacro::DefineSlots {
type_params,
declarator,
..
} => (
type_params.is_some(),
declarator.as_ref().map(|d| d.binding_span),
),
ScriptMacro::WithDefaults {
define_props_type_params,
declarator,
..
} => (
define_props_type_params.is_some(),
declarator.as_ref().map(|d| d.binding_span),
),
};
Self {
span: macro_item.span(),
kind: macro_item.kind().into(),
is_type_based,
binding_span,
}
}
}
#[derive(Debug, Clone)]
pub struct DeclarationInfo {
pub span: Span,
pub name_span: Option<Span>,
pub kind: DeclarationKind,
}
#[derive(Debug, Default)]
pub struct FileUsageInfo {
pub imports: Vec<ImportInfo>,
pub declarations: Vec<DeclarationInfo>,
pub macros: Vec<MacroInfo>,
pub provides: Vec<ProvideUsage>,
pub injects: Vec<InjectUsage>,
pub lifecycle: Vec<LifecycleUsage>,
pub reactive: Vec<ReactiveStateUsage>,
pub watchers: Vec<WatcherUsage>,
pub emit_calls: Vec<EmitCallUsage>,
pub template_refs: Vec<TemplateRefAttrUsage>,
pub slot_usages: Vec<SlotUsageInfo>,
pub slot_definitions: Vec<SlotDefinitionInfo>,
pub component_usages: Vec<ComponentUsageInfo>,
pub flags: FileUsageFlags,
}
impl FileUsageInfo {
pub fn new() -> Self {
Self::default()
}
pub fn from_script_result(result: &ScriptParseResult<'_>) -> Self {
let mut info = Self::new();
if result.is_async {
info.flags.set(FileUsageFlags::IS_ASYNC_SETUP);
}
for item in &result.items {
match item {
ScriptItem::Import(import) => {
info.flags.set(FileUsageFlags::HAS_IMPORTS);
info.imports.push(ImportInfo::from_script_import(import));
}
ScriptItem::Declaration(decl) => {
info.declarations.push(DeclarationInfo {
span: decl.span,
name_span: decl.name_span,
kind: decl.kind,
});
}
ScriptItem::Macro(macro_item) => {
info.flags.set(FileUsageFlags::HAS_MACROS);
let macro_info = MacroInfo::from_script_macro(macro_item);
info.flags.set(macro_info.kind.flag());
info.macros.push(macro_info);
}
ScriptItem::Export(_) | ScriptItem::DefaultExport(_) => {
info.flags.set(FileUsageFlags::HAS_EXPORTS);
}
ScriptItem::Async(_) => {
info.flags.set(FileUsageFlags::IS_ASYNC_SETUP);
}
ScriptItem::TypeDeclaration(_) => {
}
}
}
info
}
pub fn merge_script_usage(&mut self, collector: UsageCollector<'_>) {
self.flags.bits |= collector.flags.bits();
self.provides = collector.provides;
self.injects = collector.injects;
self.lifecycle = collector.lifecycle;
self.reactive = collector.reactive;
self.watchers = collector.watchers;
self.emit_calls = collector.emit_calls;
}
pub fn merge_template_usage(&mut self, collector: TemplateUsageCollector) {
self.flags.bits |= collector.flags.bits();
self.template_refs = collector.ref_attrs;
self.slot_usages = collector.slot_usages;
self.slot_definitions = collector.slot_definitions;
self.component_usages = collector.component_usages;
}
#[inline]
pub fn has_provides(&self) -> bool {
self.flags.has(FileUsageFlags::HAS_PROVIDE)
}
#[inline]
pub fn has_injects(&self) -> bool {
self.flags.has(FileUsageFlags::HAS_INJECT)
}
#[inline]
pub fn is_async_setup(&self) -> bool {
self.flags.has(FileUsageFlags::IS_ASYNC_SETUP)
}
#[inline]
pub fn has_define_props(&self) -> bool {
self.flags.has(FileUsageFlags::HAS_DEFINE_PROPS)
}
#[inline]
pub fn has_define_emits(&self) -> bool {
self.flags.has(FileUsageFlags::HAS_DEFINE_EMITS)
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ImportInfoOwned {
pub source: String,
pub bindings: Vec<String>,
pub is_type_only: bool,
pub start: u32,
pub end: u32,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MacroInfoOwned {
pub kind: MacroKind,
pub is_type_based: bool,
pub binding_name: Option<String>,
pub start: u32,
pub end: u32,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ProvideUsageOwned {
pub key: Option<String>,
pub is_dynamic_key: bool,
pub start: u32,
pub end: u32,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct InjectUsageOwned {
pub key: Option<String>,
pub is_dynamic_key: bool,
pub has_default: bool,
pub binding_name: Option<String>,
pub start: u32,
pub end: u32,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ComponentUsageOwned {
pub name: Option<String>,
pub is_dynamic: bool,
pub start: u32,
pub end: u32,
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct FileUsageInfoOwned {
pub imports: Vec<ImportInfoOwned>,
pub macros: Vec<MacroInfoOwned>,
pub provides: Vec<ProvideUsageOwned>,
pub injects: Vec<InjectUsageOwned>,
pub components: Vec<ComponentUsageOwned>,
pub flags: u32,
}
impl FileUsageInfoOwned {
pub fn from_span_based(info: &FileUsageInfo, source: &[u8]) -> Self {
let extract = |span: Span| -> Option<String> {
let start = span.start as usize;
let end = span.end as usize;
if end <= source.len() {
std::str::from_utf8(&source[start..end])
.ok()
.map(|s| s.to_string())
} else {
None
}
};
let imports = info
.imports
.iter()
.map(|imp| ImportInfoOwned {
source: extract(imp.source_span).unwrap_or_default(),
bindings: imp
.binding_spans
.iter()
.filter_map(|&s| extract(s))
.collect(),
is_type_only: imp.is_type_only,
start: imp.span.start,
end: imp.span.end,
})
.collect();
let macros = info
.macros
.iter()
.map(|m| MacroInfoOwned {
kind: m.kind,
is_type_based: m.is_type_based,
binding_name: m.binding_span.and_then(&extract),
start: m.span.start,
end: m.span.end,
})
.collect();
let provides = info
.provides
.iter()
.map(|p| {
use crate::utils::oxc::vue::ProvideKeyKind;
let (key, is_dynamic) = match p.key.kind {
ProvideKeyKind::StringLiteral => (extract(p.key.span), false),
ProvideKeyKind::Symbol => (extract(p.key.span), false),
ProvideKeyKind::Dynamic => (None, true),
};
ProvideUsageOwned {
key,
is_dynamic_key: is_dynamic,
start: p.span.start,
end: p.span.end,
}
})
.collect();
let injects = info
.injects
.iter()
.map(|i| {
use crate::utils::oxc::vue::ProvideKeyKind;
let (key, is_dynamic) = match i.key.kind {
ProvideKeyKind::StringLiteral => (extract(i.key.span), false),
ProvideKeyKind::Symbol => (extract(i.key.span), false),
ProvideKeyKind::Dynamic => (None, true),
};
InjectUsageOwned {
key,
is_dynamic_key: is_dynamic,
has_default: i.has_default,
binding_name: i.binding_span.and_then(&extract),
start: i.span.start,
end: i.span.end,
}
})
.collect();
let components = info
.component_usages
.iter()
.map(|c| ComponentUsageOwned {
name: if c.is_dynamic {
None
} else {
extract(c.name_span)
},
is_dynamic: c.is_dynamic,
start: c.span.start,
end: c.span.end,
})
.collect();
Self {
imports,
macros,
provides,
injects,
components,
flags: info.flags.bits(),
}
}
#[inline]
pub const fn has_flag(&self, flag: u32) -> bool {
(self.flags & flag) != 0
}
pub fn provided_keys(&self) -> impl Iterator<Item = &str> {
self.provides.iter().filter_map(|p| p.key.as_deref())
}
pub fn injected_keys(&self) -> impl Iterator<Item = &str> {
self.injects.iter().filter_map(|i| i.key.as_deref())
}
pub fn used_components(&self) -> impl Iterator<Item = &str> {
self.components.iter().filter_map(|c| c.name.as_deref())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_file_usage_flags() {
let mut flags = FileUsageFlags::new();
assert!(!flags.has(FileUsageFlags::HAS_PROVIDE));
flags.set(FileUsageFlags::HAS_PROVIDE);
assert!(flags.has(FileUsageFlags::HAS_PROVIDE));
assert!(!flags.has(FileUsageFlags::HAS_INJECT));
flags.set(FileUsageFlags::HAS_INJECT);
assert!(flags.has(FileUsageFlags::HAS_PROVIDE));
assert!(flags.has(FileUsageFlags::HAS_INJECT));
}
#[test]
fn test_macro_kind_flags() {
assert_eq!(
MacroKind::DefineProps.flag(),
FileUsageFlags::HAS_DEFINE_PROPS
);
assert_eq!(
MacroKind::DefineEmits.flag(),
FileUsageFlags::HAS_DEFINE_EMITS
);
assert_eq!(
MacroKind::DefineModel.flag(),
FileUsageFlags::HAS_DEFINE_MODEL
);
}
#[test]
fn test_flags_merge() {
let mut flags1 = FileUsageFlags::new();
flags1.set(FileUsageFlags::HAS_PROVIDE);
let mut flags2 = FileUsageFlags::new();
flags2.set(FileUsageFlags::HAS_INJECT);
flags1.merge(&flags2);
assert!(flags1.has(FileUsageFlags::HAS_PROVIDE));
assert!(flags1.has(FileUsageFlags::HAS_INJECT));
}
#[test]
fn test_file_usage_info_helpers() {
let mut info = FileUsageInfo::new();
assert!(!info.has_provides());
assert!(!info.has_injects());
info.flags.set(FileUsageFlags::HAS_PROVIDE);
assert!(info.has_provides());
assert!(!info.has_injects());
info.flags.set(FileUsageFlags::HAS_DEFINE_PROPS);
assert!(info.has_define_props());
}
}