use std::path::Path;
use vize_carton::{String as CompactString, cstr};
use vize_atelier_sfc::{SfcDescriptor, SfcError, validate_script_setup_semantics_located};
use crate::batch::source_map::SfcBlockRange;
use crate::batch::{Diagnostic, SfcBlockType};
pub(super) fn collect_sfc_compile_diagnostic(
path: &Path,
source: &str,
descriptor: &SfcDescriptor,
) -> Option<Diagnostic> {
let script_setup = descriptor.script_setup.as_ref()?;
if !script_setup_has_validator_candidates(&script_setup.content) {
return None;
}
match validate_script_setup_semantics_located(
&script_setup.content,
script_setup.loc.start,
source,
) {
Ok(()) => None,
Err(error) => Some(sfc_error_to_diagnostic(path, source, descriptor, &error)),
}
}
fn script_setup_has_validator_candidates(content: &str) -> bool {
content.contains("defineProps<") && content.contains("= defineProps")
}
fn sfc_error_to_diagnostic(
path: &Path,
source: &str,
descriptor: &SfcDescriptor,
error: &SfcError,
) -> Diagnostic {
let (line, column, block_type) = if let Some(loc) = error.loc.as_ref() {
let line = (loc.start_line as u32).saturating_sub(1);
let column = (loc.start_column as u32).saturating_sub(1);
(line, column, None)
} else {
let (offset, block_type) = default_diagnostic_offset(descriptor);
let (line, column) = line_column_for_offset(source, offset);
(line, column, Some(block_type))
};
let message = match error.code.as_deref() {
Some(code) => cstr!("Vue compile error [{}]: {}", code, error.message),
None => cstr!("Vue compile error: {}", error.message),
};
Diagnostic {
file: path.to_path_buf(),
line,
column,
message,
code: None,
severity: 1,
block_type,
}
}
fn default_diagnostic_offset(descriptor: &SfcDescriptor) -> (u32, SfcBlockType) {
if let Some(setup) = descriptor.script_setup.as_ref() {
return (setup.loc.start as u32, SfcBlockType::ScriptSetup);
}
if let Some(script) = descriptor.script.as_ref() {
return (script.loc.start as u32, SfcBlockType::Script);
}
if let Some(template) = descriptor.template.as_ref() {
return (template.loc.start as u32, SfcBlockType::Template);
}
(0, SfcBlockType::Script)
}
pub(super) fn invalid_sfc_fallback_virtual_ts() -> CompactString {
"declare const __vize_component: any;\nexport default __vize_component;\n".into()
}
pub(super) fn diagnostic_for_offset(
path: &Path,
source: &str,
start: u32,
message: CompactString,
block_type: SfcBlockType,
) -> Diagnostic {
let (line, column) = line_column_for_offset(source, start);
Diagnostic {
file: path.to_path_buf(),
line,
column,
message,
code: None,
severity: 1,
block_type: Some(block_type),
}
}
fn line_column_for_offset(source: &str, offset: u32) -> (u32, u32) {
let target = (offset as usize).min(source.len());
let mut line = 0;
let mut line_start = 0;
for (index, character) in source.char_indices() {
if index >= target {
break;
}
if character == '\n' {
line += 1;
line_start = index + 1;
}
}
(line, target.saturating_sub(line_start) as u32)
}
pub(super) fn collect_sfc_block_ranges(descriptor: &SfcDescriptor) -> Vec<SfcBlockRange> {
let mut blocks = Vec::with_capacity(3);
if let Some(template) = descriptor.template.as_ref() {
push_block_range(
&mut blocks,
template.loc.start as u32,
template.content.len() as u32,
SfcBlockType::Template,
);
}
if let Some(script) = descriptor.script.as_ref() {
push_block_range(
&mut blocks,
script.loc.start as u32,
script.content.len() as u32,
SfcBlockType::Script,
);
}
if let Some(script_setup) = descriptor.script_setup.as_ref() {
push_block_range(
&mut blocks,
script_setup.loc.start as u32,
script_setup.content.len() as u32,
SfcBlockType::ScriptSetup,
);
}
blocks
}
fn push_block_range(
blocks: &mut Vec<SfcBlockRange>,
start: u32,
len: u32,
block_type: SfcBlockType,
) {
if len == 0 {
return;
}
blocks.push(SfcBlockRange {
start,
end: start + len,
block_type,
});
}