use super::convert::{ConvertError, ConvertErrorKind};
use crate::diagnostic::{codes, Diagnostic};
use crate::ParserError;
pub(crate) const FAST_LIGHT_DOM_UNSUPPORTED: &str = "fast-light-dom-unsupported";
pub(crate) const UNSUPPORTED_MULTIPLE_F_TEMPLATES: &str = "unsupported-multiple-f-templates";
pub(crate) const INVALID_FAST_TEMPLATE: &str = "invalid-fast-template";
pub(crate) const FAST_NAMED_FOR_UNSUPPORTED: &str = "fast-named-for-unsupported";
#[cold]
#[inline(never)]
pub(super) fn named_for_unsupported(
owner: &str,
source: &str,
element: &crate::html_parser::Element<'_>,
) -> ParserError {
Diagnostic::error("FAST client templates do not support named <for> references")
.code(FAST_NAMED_FOR_UNSUPPORTED)
.component(owner)
.element("for")
.at_offset(source, element.start)
.snippet(element.opening())
.help("use the WebUI plugin for recursive client rendering, or use ordinary nested loops in FAST components")
.into()
}
#[cold]
#[inline(never)]
pub(super) fn converter_error(
source: &str,
tag_name: &str,
error: &ConvertError<'_>,
) -> ParserError {
if matches!(error.kind(), ConvertErrorKind::MultipleFTemplates { .. }) {
return Diagnostic::error("multiple <f-template> elements are not supported")
.code(UNSUPPORTED_MULTIPLE_F_TEMPLATES)
.component(tag_name)
.at_offset(source, error.offset())
.snippet("<f-template>")
.help(
"keep only one <f-template> per component file; multiple f-template blocks are not currently supported",
)
.into();
}
let help = match error.kind() {
ConvertErrorKind::MissingInnerTemplate => {
"keep exactly one inner <template> element inside <f-template>"
}
ConvertErrorKind::ContentAroundInnerTemplate => {
"keep only a single inner <template> inside <f-template> (surrounding content may only be whitespace or comments)"
}
ConvertErrorKind::ContentOutsideTemplate => {
"keep <f-template> as the only top-level authored content (outside content may only be whitespace or comments)"
}
ConvertErrorKind::MissingValueAttribute { .. }
| ConvertErrorKind::InvalidDirectiveValue { .. } => {
"add the required value=\"{{expression}}\" attribute to the FAST directive"
}
ConvertErrorKind::UnexpectedDirectiveAttribute { .. } => {
"FAST directives (<f-when>/<f-repeat>) accept only a value=\"{{expression}}\" attribute; remove the others"
}
ConvertErrorKind::UnsupportedWrapperAttribute { .. } => {
"the <f-template> wrapper accepts only 'name' and 'shadowroot*' shadow options (e.g. shadowrootmode, shadowrootdelegatesfocus); remove the other attribute"
}
ConvertErrorKind::ConditionQuoteConflict { .. } => {
"rewrite the f-when condition to use a single quote style (only single or only double quotes)"
}
ConvertErrorKind::InvalidRepeatExpression { .. } => {
"use an f-repeat expression in \"item in items\" form"
}
ConvertErrorKind::UnclosedElement { .. } | ConvertErrorKind::UnclosedTag => {
"close the reported FAST template element or opening tag"
}
ConvertErrorKind::UnsupportedFAttribute { .. }
| ConvertErrorKind::UnsupportedFElement { .. } => {
"remove the unsupported FAST construct or replace it with supported declarative syntax"
}
ConvertErrorKind::UnexpectedClosingDirective { .. } => {
"remove the stray closing tag or add its matching opening FAST directive"
}
ConvertErrorKind::MultipleFTemplates { .. } => {
"keep only one <f-template> per component file"
}
};
let code = if matches!(
error.kind(),
ConvertErrorKind::UnclosedElement { .. } | ConvertErrorKind::UnclosedTag
) {
codes::UNCLOSED_HTML_TAG
} else {
INVALID_FAST_TEMPLATE
};
Diagnostic::error(format!("invalid FAST template: {error}"))
.code(code)
.component(tag_name)
.at_offset(source, error.offset())
.snippet(converter_error_snippet(error.kind()))
.help(help)
.into()
}
#[cold]
#[inline(never)]
pub(super) fn light_dom_unsupported(tag_name: &str) -> ParserError {
Diagnostic::error("FAST plugins require effective Shadow DOM")
.code(FAST_LIGHT_DOM_UNSUPPORTED)
.component(tag_name)
.help(
"build with `dom: \"shadow\"`, author an open declarative Shadow root for this component, or use the WebUI plugin for global Light DOM",
)
.into()
}
#[cold]
#[inline(never)]
fn converter_error_snippet(error: &ConvertErrorKind<'_>) -> String {
match error {
ConvertErrorKind::MultipleFTemplates { .. }
| ConvertErrorKind::MissingInnerTemplate
| ConvertErrorKind::ContentOutsideTemplate => "<f-template>".to_string(),
ConvertErrorKind::ContentAroundInnerTemplate => "<template>".to_string(),
ConvertErrorKind::UnclosedElement { tag }
| ConvertErrorKind::MissingValueAttribute { tag }
| ConvertErrorKind::InvalidDirectiveValue { tag, .. }
| ConvertErrorKind::UnsupportedFElement { tag } => {
let mut snippet = String::with_capacity(tag.len() + 2);
snippet.push('<');
snippet.push_str(tag);
snippet.push('>');
snippet
}
ConvertErrorKind::UnexpectedClosingDirective { tag } => {
let mut snippet = String::with_capacity(tag.len() + 3);
snippet.push_str("</");
snippet.push_str(tag);
snippet.push('>');
snippet
}
ConvertErrorKind::InvalidRepeatExpression { expr } => {
let mut snippet = String::with_capacity(expr.len() + 4);
snippet.push_str("{{");
snippet.push_str(expr);
snippet.push_str("}}");
snippet
}
ConvertErrorKind::ConditionQuoteConflict { value } => value.to_string(),
ConvertErrorKind::UnsupportedFAttribute { attribute }
| ConvertErrorKind::UnexpectedDirectiveAttribute { attribute, .. } => attribute.to_string(),
ConvertErrorKind::UnsupportedWrapperAttribute { attribute } => attribute.to_string(),
ConvertErrorKind::UnclosedTag => "<".to_string(),
}
}