From 1b7ddd5894c2636618e630f44b1bb7d598d29b48 Mon Sep 17 00:00:00 2001 From: Mica White Date: Sat, 3 Oct 2026 14:14:31 -0400 Subject: Planning for 0.2 --- src/analyze.rs | 16 ++++++---- src/compile.rs | 17 ++++++---- src/diagnostics.rs | 48 +++++++++++++++++++++++----- src/diagnostics/forbidden_top_level_tag.rs | 6 +--- src/diagnostics/non_self_closing_void_tag.rs | 3 +- src/diagnostics/non_void_self_closing_tag.rs | 3 +- src/lib.rs | 6 ++++ 7 files changed, 72 insertions(+), 27 deletions(-) diff --git a/src/analyze.rs b/src/analyze.rs index b8a7012..6526c2b 100644 --- a/src/analyze.rs +++ b/src/analyze.rs @@ -58,6 +58,10 @@ pub struct HeadSymbol<'a> { #[derive(Debug)] pub enum ConstantType<'a> { String, + Fragment { + direct_children_tags: Option<&'a [&'a str]>, + transitive_children_tags: Option<&'a [&'a str]>, + }, Block { direct_children_tags: Option<&'a [&'a str]>, transitive_children_tags: Option<&'a [&'a str]>, @@ -65,14 +69,14 @@ pub enum ConstantType<'a> { } #[derive(Debug)] -pub enum ConstantValue<'a> { +pub enum ConstantLiteral<'a> { String(&'a str), } #[derive(Debug)] pub enum ConstantExpression<'a> { Name(&'a str), - Value(ConstantValue<'a>), + Value(ConstantLiteral<'a>), Fragment(&'a [ConstantSymbolId]), Block { tag_name_lowercase: &'a str, @@ -104,7 +108,7 @@ fn element_attributes<'a>( id, name: None, ty: Some(ConstantType::String), - value: Some(ConstantExpression::Value(ConstantValue::String(value))), + value: Some(ConstantExpression::Value(ConstantLiteral::String(value))), definition_site: None, }, ); @@ -146,7 +150,7 @@ fn node_to_constant<'a>( id, name: None, ty: Some(ConstantType::String), - value: Some(ConstantExpression::Value(ConstantValue::String(value))), + value: Some(ConstantExpression::Value(ConstantLiteral::String(value))), definition_site: None, }, ); @@ -164,7 +168,7 @@ fn node_to_constant<'a>( ConstantSymbol { id, name: None, - // TODO fragment type + // TODO acquire the type ty: None, value: Some(fragment), definition_site: None, @@ -259,7 +263,7 @@ fn add_constant<'a>(symbol_table: &mut ScopedSymbolTable<'a>, element: &'a Eleme id, name, ty: Some(ConstantType::String), - value: value.map(|value| ConstantExpression::Value(ConstantValue::String(value))), + value: value.map(|value| ConstantExpression::Value(ConstantLiteral::String(value))), definition_site: Some(*element), }, ); diff --git a/src/compile.rs b/src/compile.rs index 9e19b18..a0877cd 100644 --- a/src/compile.rs +++ b/src/compile.rs @@ -1,6 +1,9 @@ use html_escape::{encode_double_quoted_attribute, encode_single_quoted_attribute}; -use crate::analyze::{ConstantExpression, ConstantSymbolId, ConstantValue, ScopedSymbolTable}; +use crate::{ + VOID_TAGS, + analyze::{ConstantExpression, ConstantLiteral, ConstantSymbolId, ScopedSymbolTable}, +}; fn symbol_table_from_path<'a>( symbol_table: &'a ScopedSymbolTable<'a>, @@ -62,7 +65,7 @@ fn compile_constant<'a>( }; match expression { ConstantExpression::Value(value) => match value { - ConstantValue::String(value) => buffer.push_str(value), + ConstantLiteral::String(value) => buffer.push_str(value), }, ConstantExpression::Name(name) => { let mut name = *name; @@ -97,10 +100,12 @@ fn compile_constant<'a>( compile_attribute_value(buffer, symbol_table, name, *value); } buffer.push('>'); - compile_constant(buffer, symbol_table, *child); - buffer.push_str("'); + if !VOID_TAGS.contains(tag_name_lowercase) { + compile_constant(buffer, symbol_table, *child); + buffer.push_str("'); + } } } } diff --git a/src/diagnostics.rs b/src/diagnostics.rs index d2db96b..295de94 100644 --- a/src/diagnostics.rs +++ b/src/diagnostics.rs @@ -6,6 +6,8 @@ use crate::{ tokenize::{AttributeToken, Token, TokenType}, }; +mod deprecated_attributes; +mod deprecated_tag; mod duplicate_attribute_names; mod empty_tag_name; mod end_tag_with_attributes; @@ -25,12 +27,6 @@ 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", "import", -]; -const ALLOWED_TOP_LEVEL_TAGS: &[&str] = &["import", "const", "block", "head", "body"]; - pub struct DiagnosticsContext { source_code: &'static str, } @@ -194,6 +190,8 @@ pub fn run_token_diagnostics(ctx: &DiagnosticsContext, tokens: &[Token<'_>]) -> ctx, tokens, ) .is_none(); + // TODO allow the AST diagnostics to still run if this fails + has_failed |= run_token_diagnostic::(ctx, tokens).is_none(); // TODO invalid character reference (!has_failed).then_some(()) } @@ -221,8 +219,29 @@ pub fn run_ast_diagnostics(ctx: &DiagnosticsContext, document: &Document<'_>) -> // TODO invalid // TODO invalid // TODO invalid - // TODO deprecated tag + // TODO invalid // TODO deprecated tag attribute + // TODO warning-only obsolete features + // TODO invalid name on anchor + // TODO invalid language on script + // TODO invalid type on style + // TODO base without href or target + // TODO multiple base elements + // TODO base target with a newline, tab, or < + // TODO invalid cite on
or + // TODO non html1 tag or attribute + // TODO empty, missing, or invalid image src + // TODO invalid form enctype + // TODO invalid form action + // TODO invalid form method + // TODO input without name + // TODO invalid input attributes for type + // TODO invalid select size + // TODO invalid select mutliple attribute + // TODO select without name + // TODO invalid option selected attribute + // TODO invalid cols and rows attributes for textarea + // TODO textarea without name // // TODO forbidden tag in noscript // TODO head tag outside head @@ -244,6 +263,20 @@ pub fn run_ast_diagnostics(ctx: &DiagnosticsContext, document: &Document<'_>) -> // TODO select inside select // TODO input, keygen, textarea inside select // TODO caption, table, tbody, tfoot, thread, tr, td, th inside select + // TODO more than one body + // TODO invalid head child + // TODO missing title + // TODO title outside head + // TODO warning for title longer than 64 characters + // TODO warning for more than one

+ // TODO invalid heading hierarchy + // TODO invalid

,

, 
, or content + // TODO wrap on
+	// TODO address inside address
+	// TODO heading inside addres
+	// TODO 
or