···5757 | Content_model.Nothing -> false
5858 | Content_model.Text -> true
5959 | Content_model.Transparent -> true (* Inherits parent *)
6060- | Content_model.Categories _ -> false (* Elements only *)
6060+ | Content_model.Categories cats ->
6161+ (* Phrasing and Flow content include text *)
6262+ List.mem Content_category.Phrasing cats || List.mem Content_category.Flow cats
6163 | Content_model.Elements _ -> false (* Specific elements only *)
6264 | Content_model.Mixed _ -> true (* Text + elements *)
6365 | Content_model.One_or_more model -> allows_text model
···96989799 match spec_opt with
98100 | None ->
9999- (* Unknown element - emit warning *)
100100- Message_collector.add_typed collector
101101- (Error_code.Unknown_element { name })
101101+ (* Unknown element - first check if it's allowed in current context *)
102102+ validate_child_element state name collector
102103 | Some spec ->
103104 (* Check prohibited ancestors *)
104105 check_prohibited_ancestors state name spec collector;
+23-2
lib/html5_checker/error_code.ml
···135135 (** The "id" attribute on a "map" element must have the same value as the "name" attribute. *)
136136 | List_attr_requires_datalist
137137 (** The "list" attribute of "input" must refer to a "datalist" element. *)
138138+ | Input_list_not_allowed
139139+ (** Attribute "list" is only allowed on certain input types. *)
138140 | Label_too_many_labelable
139141 (** The "label" element may contain at most one labelable descendant. *)
140142 | Label_for_id_mismatch
···151153 (** Element "summary" is missing required attribute "role". *)
152154 | Summary_missing_attrs
153155 (** Element "summary" is missing one or more of [aria-checked, aria-level, role]. *)
156156+ | Summary_role_not_allowed
157157+ (** The "role" attribute must not be used on any "summary" for its parent "details". *)
154158 | Autocomplete_webauthn_on_select
155159 (** The value of "autocomplete" for "select" must not contain "webauthn". *)
156160 | Commandfor_invalid_target
···234238 | Importmap_scopes_values_not_object
235239 (** The value of "scopes" property values must also be JSON objects. *)
236240 | Importmap_scopes_invalid_url
237237- (** The "scopes" property must only contain valid URL values. *)
241241+ (** The "scopes" property keys must be valid URL strings. *)
242242+ | Importmap_scopes_value_invalid_url
243243+ (** The specifier map within "scopes" must only contain valid URL values. *)
238244239245 (* ===== Style Element ===== *)
240246 | Style_type_invalid
···270276 | Unnecessary_role _ -> Warning
271277 | Aria_should_not_be_used _ -> Warning
272278 | Unknown_element _ -> Warning
279279+ | Not_nfc _ -> Warning
273280 | _ -> Error
274281275282(** Get a short code string for categorization *)
···333340 | Picture_missing_img -> "picture-missing-img"
334341 | Map_id_name_mismatch -> "map-id-name"
335342 | List_attr_requires_datalist -> "list-datalist"
343343+ | Input_list_not_allowed -> "list-not-allowed"
336344 | Label_too_many_labelable -> "label-multiple"
337345 | Label_for_id_mismatch -> "label-for-mismatch"
338346 | Role_on_label_ancestor -> "role-on-label"
···341349 | Input_value_constraint _ -> "input-value"
342350 | Summary_missing_role -> "summary-role"
343351 | Summary_missing_attrs -> "summary-attrs"
352352+ | Summary_role_not_allowed -> "summary-role"
344353 | Autocomplete_webauthn_on_select -> "autocomplete"
345354 | Commandfor_invalid_target -> "commandfor"
346355 | Forbidden_codepoint _ -> "forbidden-codepoint"
···377386 | Importmap_scopes_not_object -> "importmap"
378387 | Importmap_scopes_values_not_object -> "importmap"
379388 | Importmap_scopes_invalid_url -> "importmap"
389389+ | Importmap_scopes_value_invalid_url -> "importmap"
380390 | Style_type_invalid -> "style-type"
381391 | Headingoffset_invalid -> "headingoffset"
382392 | Media_empty -> "media-empty"
···410420 | Bad_attr_value_generic { message } -> message
411421 | Duplicate_id { id } ->
412422 Printf.sprintf "Duplicate ID %s." (q id)
413413- | Data_attr_invalid_name { reason } -> reason
423423+ | Data_attr_invalid_name { reason } ->
424424+ Printf.sprintf "%s attribute names %s." (q "data-*") reason
414425 | Data_attr_uppercase ->
415426 Printf.sprintf "%s attributes must not have characters from the range %s\xe2\x80\xa6%s in the name."
416427 (q "data-*") (q "A") (q "Z")
···566577 | List_attr_requires_datalist ->
567578 Printf.sprintf "The %s attribute of the %s element must refer to a %s element."
568579 (q "list") (q "input") (q "datalist")
580580+ | Input_list_not_allowed ->
581581+ Printf.sprintf "Attribute %s is only allowed when the input type is %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, or %s."
582582+ (q "list") (q "color") (q "date") (q "datetime-local") (q "email") (q "month")
583583+ (q "number") (q "range") (q "search") (q "tel") (q "text") (q "time") (q "url") (q "week")
569584 | Label_too_many_labelable ->
570585 Printf.sprintf "The %s element may contain at most one %s, %s, %s, %s, %s, %s, or %s descendant."
571586 (q "label") (q "button") (q "input") (q "meter") (q "output") (q "progress") (q "select") (q "textarea")
···588603 | Summary_missing_attrs ->
589604 Printf.sprintf "Element %s is missing one or more of the following attributes: [aria-checked, aria-level, role]."
590605 (q "summary")
606606+ | Summary_role_not_allowed ->
607607+ Printf.sprintf "The %s attribute must not be used on any %s element that is a summary for its parent %s element."
608608+ (q "role") (q "summary") (q "details")
591609 | Autocomplete_webauthn_on_select ->
592610 Printf.sprintf "The value of the %s attribute for the %s element must not contain %s."
593611 (q "autocomplete") (q "select") (q "webauthn")
···692710 Printf.sprintf "The value of the %s property within the content of a %s element with a %s attribute whose value is %s must be a JSON object whose values are also JSON objects."
693711 (q "scopes") (q "script") (q "type") (q "importmap")
694712 | Importmap_scopes_invalid_url ->
713713+ Printf.sprintf "The value of the %s property within the content of a %s element with a %s attribute whose value is %s must be a JSON object whose keys are valid URL strings."
714714+ (q "scopes") (q "script") (q "type") (q "importmap")
715715+ | Importmap_scopes_value_invalid_url ->
695716 Printf.sprintf "A specifier map defined in a %s property within the content of a %s element with a %s attribute whose value is %s must only contain valid URL values."
696717 (q "scopes") (q "script") (q "type") (q "importmap")
697718
···691691 if name_lower = "summary" then begin
692692 let parent = get_parent_element state in
693693 let is_in_details = parent = Some "details" in
694694+ let has_role_attr = List.exists (fun (k, _) -> String.lowercase_ascii k = "role") attrs in
695695+ let has_aria_expanded = List.assoc_opt "aria-expanded" attrs <> None in
696696+ let has_aria_pressed = List.assoc_opt "aria-pressed" attrs <> None in
694697 if is_in_details then begin
695698 (* summary that is the first child of details *)
696696- (* Cannot have role=paragraph (or other non-button roles) *)
697697- if explicit_roles <> [] then begin
698698- let first_role = List.hd explicit_roles in
699699- if first_role <> "button" && first_role <> "none" && first_role <> "presentation" then
700700- Message_collector.add_typed collector Error_code.Summary_missing_role
701701- end;
699699+ if has_role_attr then
700700+ (* Must not have role attribute *)
701701+ Message_collector.add_typed collector Error_code.Summary_role_not_allowed
702702+ else if has_aria_pressed then
703703+ (* aria-pressed without role requires role *)
704704+ Message_collector.add_typed collector Error_code.Summary_missing_role
705705+ else if has_aria_expanded then
706706+ (* aria-expanded without role requires role *)
707707+ Message_collector.add_typed collector Error_code.Summary_missing_attrs
708708+ end else begin
709709+ (* summary NOT in details context - different rules apply *)
702710 (* If has aria-expanded or aria-pressed, must have role *)
703703- let has_aria_expanded = List.assoc_opt "aria-expanded" attrs <> None in
704704- let has_aria_pressed = List.assoc_opt "aria-pressed" attrs <> None in
705711 if (has_aria_expanded || has_aria_pressed) && explicit_roles = [] then begin
706712 if has_aria_pressed then
707713 Message_collector.add_typed collector Error_code.Summary_missing_role
···287287 | None -> "text" (* default type is text *)
288288 in
289289 if not (List.mem input_type input_types_allowing_list) then
290290- Message_collector.add_typed collector Error_code.List_attr_requires_datalist
290290+ Message_collector.add_typed collector Error_code.Input_list_not_allowed
291291 end
292292 end;
293293···317317 | Some xmllang ->
318318 (match lang_value with
319319 | None ->
320320+ (* xml:lang without lang attribute *)
320321 Message_collector.add_typed collector Error_code.Xml_lang_without_lang
321322 | Some lang when String.lowercase_ascii lang <> String.lowercase_ascii xmllang ->
322322- Message_collector.add_typed collector Error_code.Xml_lang_lang_mismatch
323323+ (* xml:lang and lang have different values - "lang present with same value" message *)
324324+ Message_collector.add_typed collector Error_code.Xml_lang_without_lang
323325 | _ -> ())
324326 | None -> ()
325327 end;