diff options
Diffstat (limited to 'src/diagnostics.rs')
| -rw-r--r-- | src/diagnostics.rs | 217 |
1 files changed, 217 insertions, 0 deletions
diff --git a/src/diagnostics.rs b/src/diagnostics.rs new file mode 100644 index 0000000..514818a --- /dev/null +++ b/src/diagnostics.rs @@ -0,0 +1,217 @@ +use miette::{Diagnostic, Error}; +use thiserror::Error; + +use crate::{ + parse::{Attribute, Document, Element, Node}, + tokenize::{AttributeToken, Token, TokenType}, +}; + +mod duplicate_attribute_names; +mod empty_tag_name; +mod end_tag_with_attributes; +mod forbidden_top_level_tag; +mod html_tag; +mod image_tag; +mod non_alphabetic_attribute_name; +mod non_alphabetic_tag_name; +mod non_self_closing_void_tag; +mod non_void_self_closing_tag; +mod self_closing_end_tag; +mod stray_end_tag; +mod stray_equal_sign; +mod too_many_equal_signs; +mod top_level_character_data; +mod unclosed_start_tag; +mod unterminated_comment; +mod unterminated_tag; + +const VOID_TAGS: &[&str] = &[ + "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", + "track", "wbr", +]; +const ALLOWED_TOP_LEVEL_TAGS: &[&str] = &["import", "const", "block", "head", "body"]; + +pub struct DiagnosticsContext { + source_code: &'static str, +} + +trait TokenDiagnostic: Sized + Send + Sync + 'static { + fn handle_token(_: &Token<'_>) -> Option<Self> { + None + } + + fn handle_attribute_token(_: &AttributeToken<'_>) -> Option<Self> { + None + } +} + +trait AstDiagnostic: Sized + Send + Sync + 'static { + fn handle_document(_: &Document<'_>) -> Option<Self> { + None + } + + fn handle_node(_: &Node<'_>) -> Option<Self> { + None + } + + fn handle_element(_: &Element<'_>) -> Option<Self> { + None + } + + fn handle_attribute(_: &Attribute<'_>) -> Option<Self> { + None + } +} + +fn maybe_print_diagnostic<D: Diagnostic + Send + Sync + 'static>( + ctx: &DiagnosticsContext, + diagnostic: Option<D>, +) { + if let Some(diagnostic) = diagnostic { + eprintln!( + "{}", + Error::new(diagnostic).with_source_code(ctx.source_code) + ); + } +} + +#[derive(Debug, Error, Diagnostic)] +#[error("This file is not valid UTF-8")] +#[diagnostic(code(NonUtf8), severity(Error))] +struct NonUtf8; + +#[derive(Debug, Error, Diagnostic)] +#[error("Forbidden NUL character found")] +#[diagnostic(code(NonUtf8), severity(Error))] +struct NulCharacter { + #[label("Forbidden NUL byte found here")] + location: usize, +} + +#[derive(Debug, Error, Diagnostic)] +#[error("Forbidden control character found")] +#[diagnostic(code(NonUtf8), severity(Error))] +struct ControlCharacter { + #[label("Control character found")] + location: usize, +} + +pub fn run_character_diagnostics(source: &'static [u8]) -> Option<&'static str> { + let Ok(source) = str::from_utf8(source) else { + eprintln!("{}", Error::new(NonUtf8)); + return None; + }; + let ctx = DiagnosticsContext { + source_code: source, + }; + for (i, char) in source.char_indices() { + maybe_print_diagnostic(&ctx, (char == '\0').then_some(NulCharacter { location: i })); + maybe_print_diagnostic( + &ctx, + (char.is_control() && !char.is_ascii_whitespace()) + .then_some(ControlCharacter { location: i }), + ); + } + + Some(source) +} + +fn run_token_diagnostic<D: Diagnostic + TokenDiagnostic>( + ctx: &DiagnosticsContext, + tokens: &[Token<'_>], +) { + for token in tokens { + maybe_print_diagnostic(ctx, D::handle_token(token)); + + let TokenType::Tag { + attribute_tokens, .. + } = token.ty + else { + continue; + }; + + for token in attribute_tokens { + maybe_print_diagnostic(ctx, D::handle_attribute_token(token)); + } + } +} + +fn run_ast_diagnostic<D: AstDiagnostic + Diagnostic>( + ctx: &DiagnosticsContext, + document: &Document<'_>, +) { + maybe_print_diagnostic(ctx, D::handle_document(document)); + for node in document.children { + maybe_print_diagnostic(ctx, D::handle_node(node)); + + let Node::Element(element) = node else { + continue; + }; + maybe_print_diagnostic(ctx, D::handle_element(element)); + + for attribute in element.attributes { + maybe_print_diagnostic(ctx, D::handle_attribute(attribute)); + } + } +} + +pub fn run_token_diagnostics(ctx: &DiagnosticsContext, tokens: &[Token<'_>]) { + run_token_diagnostic::<non_void_self_closing_tag::NonVoidSelfClosingTag>(ctx, tokens); + run_token_diagnostic::<non_self_closing_void_tag::NonSelfClosingVoidTag>(ctx, tokens); + run_token_diagnostic::<end_tag_with_attributes::EndTagWithAttributes>(ctx, tokens); + run_token_diagnostic::<self_closing_end_tag::SelfClosingEndTag>(ctx, tokens); + run_token_diagnostic::<non_alphabetic_tag_name::NonAlphabeticTagName>(ctx, tokens); + run_token_diagnostic::<empty_tag_name::EmptyTagName>(ctx, tokens); + run_token_diagnostic::<unterminated_tag::UnterminatedTag>(ctx, tokens); + run_token_diagnostic::<unterminated_comment::UnterminatedComment>(ctx, tokens); + run_token_diagnostic::<non_alphabetic_attribute_name::NonAlphabeticAttributeName>(ctx, tokens); + // TODO invalid character reference +} + +pub fn run_ast_diagnostics(ctx: &DiagnosticsContext, document: &Document<'_>) { + run_ast_diagnostic::<stray_equal_sign::StrayEqualSign>(ctx, document); + run_ast_diagnostic::<too_many_equal_signs::TooManyEqualSigns>(ctx, document); + run_ast_diagnostic::<duplicate_attribute_names::DuplicateAttributeNames>(ctx, document); + run_ast_diagnostic::<top_level_character_data::TopLevelCharacterData>(ctx, document); + run_ast_diagnostic::<forbidden_top_level_tag::ForbiddenTopLevelTag>(ctx, document); + run_ast_diagnostic::<stray_end_tag::StrayEndTag>(ctx, document); + run_ast_diagnostic::<unclosed_start_tag::UnclosedStartTag>(ctx, document); + run_ast_diagnostic::<html_tag::HtmlTag>(ctx, document); + run_ast_diagnostic::<image_tag::ImageTag>(ctx, document); + // TODO invalid <kuht-var /> + // TODO invalid <import /> + // TODO invalid <const /> + // TODO invalid <block /> + // TODO deprecated tag + // TODO deprecated tag attribute + // + // TODO forbidden tag in noscript + // TODO head tag outside head + // TODO html tag in body + // TODO heading inside heading + // TODO form inside form + // TODO button inside button + // TODO anchor inside anchor + // TODO head inside body + // TODO caption, col, colgroup, tbody, tfoot, thead, tr outside table + // TODO table inside table + // TODO non-hidden input inside table + // TODO form inside table + // TODO body inside body + // TODO col, colgroup, tbody, tfoot, thead, tr inside caption + // TODO th, td outside tr + // TODO caption, col, colgroup, tr in tbody + // TODO caption, col, colgroup, tbody, td, tfoot, th, thead, tr inside table cell + // TODO select inside select + // TODO input, keygen, textarea inside select + // TODO caption, table, tbody, tfoot, thread, tr, td, th inside select + // + // TODO anchor with invalid href + // TODO dt or dd outside dl + // TODO li outside ul, ol, menu + // TODO invalid dl, ul, menu + // TODO invalid attribute values (see microsyntaxes) + // TODO aria tags don't match tag semantics + // TODO empty or missing image src + // TODO missing alt text for image (unless inside figure with caption, or has title, or meta name = "generator") +} |
