use super::{VaporError, VaporErrorKind, error, roots};
use crate::expr::{AccessError, AccessProvider, AccessSpelling, write_expression};
use crate::module::setup::write_selected_setup_runtime_segments;
use crate::module::{ModuleParts, ScriptPart, assemble};
use crate::runtime::{Runtime, Vocabulary};
use crate::write::{Emitted, LinkSink, Writer};
use vize_l0::Span;
use vize_l2::resolution::{Occurrence, Usage};
use vize_l3::decision::vapor::{NativeSelectedSetupVaporAnalysis, VaporPart, VaporValueKind};
pub fn emit_selected_setup_component<L: LinkSink>(
analysis: &NativeSelectedSetupVaporAnalysis<'_, '_, '_>,
runtime_version: &str,
) -> Result<Emitted<L>, VaporError> {
let selected = analysis.owner().selected();
if let Some(script) = selected.ordinary() {
return Err(VaporError {
node: None,
span: script.block().span(),
kind: VaporErrorKind::ScriptSource,
});
}
if selected.has_styles() {
return Err(VaporError {
node: None,
span: Span::new(0, selected.component().block().root_source().len() as u32),
kind: VaporErrorKind::StyledSource,
});
}
let facts = analysis
.vapor()
.ok_or_else(|| error(VaporErrorKind::MissingFacts))?;
if let Some(rejection) = facts.unsupported().first() {
return Err(VaporError {
node: Some(rejection.node),
span: rejection.span,
kind: VaporErrorKind::Unsupported(rejection.reason),
});
}
if facts.roots().is_empty() {
return Err(error(VaporErrorKind::ComponentEmptySetupTemplate));
}
if facts.roots().len() > 1 {
return Err(error(VaporErrorKind::ComponentFragmentLifecycle));
}
for binding in analysis.setup().bindings() {
let declaration = binding
.declaration()
.ok_or_else(|| error(VaporErrorKind::GeneratedBindingCollision))?;
if matches!(
declaration.name.as_str(),
"_sfc_main"
| "__props"
| "_template"
| "_defineVaporComponent"
| "_setText"
| "_toDisplayString"
| "_unref"
| "_renderEffect"
) || (!facts.roots().is_empty() && matches!(declaration.name.as_str(), "t0" | "n0"))
{
return Err(VaporError {
node: None,
span: declaration.span,
kind: VaporErrorKind::GeneratedBindingCollision,
});
}
}
let mut module = ModuleParts::for_runtime(Runtime::VueVapor, runtime_version, "_sfc_main")
.map_err(|assembly| error(VaporErrorKind::Assembly(assembly)))?;
let mut prelude = Writer::default();
let mut body = Writer::default();
helper(&mut body, module.vocabulary, "defineVaporComponent")?;
body.push(
"const _sfc_main = _defineVaporComponent({\n __multiRoot: false,\n setup(__props) {\n",
);
write_selected_setup_runtime_segments(analysis.setup(), &mut body)
.map_err(|setup| error(VaporErrorKind::Setup(setup)))?;
body.push("\n;\n");
body.indent();
body.indent();
if let Some(root) = facts.roots().first() {
let parts = facts
.parts(root)
.ok_or_else(|| error(VaporErrorKind::ForeignRoot))?;
if let [
VaporPart::Interpolation {
node, expression, ..
},
] = parts
{
helper(&mut prelude, module.vocabulary, "template")?;
prelude.push("const t0 = _template(\" \")\n");
body.newline();
body.push("const n0 = t0()");
body.newline();
let mutable = expression.kind() == VaporValueKind::SetupMutable;
if mutable {
helper(&mut body, module.vocabulary, "renderEffect")?;
helper(&mut body, module.vocabulary, "unref")?;
body.push("_renderEffect(() => ");
}
helper(&mut body, module.vocabulary, "setText")?;
helper(&mut body, module.vocabulary, "toDisplayString")?;
body.push("_setText(n0, _toDisplayString(");
if mutable {
body.push("_unref(");
}
let resolution = expression.resolution();
if !core::ptr::eq(resolution.file(), analysis.file()) || resolution.node() != *node {
return Err(error(VaporErrorKind::ForeignRoot));
}
let table = resolution
.table()
.ok_or_else(|| error(VaporErrorKind::MissingFacts))?;
write_expression(&mut body, analysis.artifact().source(), table, &LexicalRead)
.map_err(|expression| error(VaporErrorKind::Expression(expression)))?;
body.push("\n");
if mutable {
body.push(")");
}
body.push("))");
if mutable {
body.push(")");
}
} else {
roots::static_roots(&mut prelude, &mut body, facts)?;
}
}
roots::return_roots(&mut body, facts.roots().len());
body.deindent();
body.deindent();
body.newline();
body.push(" }\n})\n");
module.prelude = Some(prelude);
module.script = Some(ScriptPart::Body(body));
assemble(module).map_err(|assembly| error(VaporErrorKind::Assembly(assembly)))
}
fn helper<L: LinkSink>(
writer: &mut Writer<L>,
vocabulary: &Vocabulary,
name: &str,
) -> Result<(), VaporError> {
writer.use_helper(
vocabulary
.helper(name)
.ok_or_else(|| error(VaporErrorKind::RuntimeHelper))?,
);
Ok(())
}
struct LexicalRead;
impl AccessProvider for LexicalRead {
fn spelling(&self, occurrence: &Occurrence<'_>) -> Result<AccessSpelling<'_>, AccessError> {
if occurrence.usage == Usage::Read {
Ok(AccessSpelling::Verbatim)
} else {
Err(AccessError::UnsupportedUsage)
}
}
}