The validator's own error shape: never raised, always collected into a
list. Mirrors Statifier.Lowering.Error's shape (reason, message,
location) with character-identical field names, so a future common
diagnostic protocol can adopt both without either layer's reason union
leaking into the other's. The two layers' error lists are never observed
together - Statifier.Validator.validate/2 only ever receives a document
lowering already accepted - so sharing the shape rather than the type is
enough.
reason is a closed tagged-tuple union, declared once in full here even
though initially only :duplicate_id has a constructor. Later additions
add a constructor per reason rather than reopening the type, following
Statifier.Lowering.Error's own precedent.
code/1 returns the reason tuple's tag atom - the stable error code the
bead's acceptance criteria ask for - while reason itself keeps carrying
the offending ids as data, the way the rest of the repo does.
Summary
Types
Which slot a shared transition-shape violation (:transition_count,
:transition_missing_target, :transition_forbidden_attribute) came
from: a state's <initial> element, or a :history state's own default
transition. The id is nil exactly when the owning state's own id is.
Functions
Spec 5.4.2: an <assign> element carries both an expr attribute and
non-blank child content - "A conformant SCXML document MUST specify
either 'expr' or children of <assign>, but not both." expr is the
attribute's value as lowered, so the message quotes the expression rather
than the text, which has no span of its own. Reported at the <assign>'s
own element span (Statifier.Document.Assign.node_location, not the
SCXML location attribute that struct also carries -
Statifier.Document.Assign's moduledoc names the two spans apart).
Check 9 (spec 3.2.1): the root element's resolved namespace,
document.namespace, is not the SCXML namespace. uri is
document.namespace itself - nil for a fragment that declares no
namespace at all, the boilerplate-free case that relaxed parsing left
representable.
Check 10 (spec 3.2.1): the root element's version is not "1.0".
version is nil when the attribute is absent.
Spec 6.3.1: "A conformant SCXML document MUST specify exactly one of
sendid or sendidexpr." A <cancel> carrying neither. Reported at the
<cancel> element's own location.
Spec 6.3.1: "sendid ... Must not occur with sendidexpr." A <cancel>
carrying both. Reported at the <cancel> element's own location.
The reason tuple's tag - the stable error code.
Spec 5.6: a <content> element carries both an expr attribute and
inline content, whether that content is text or markup (ADR-0041) - the
spec's "MUST NOT specify both" that lib/statifier/document/content.ex
deliberately leaves representable so this check can report it. expr is
the attribute's value as lowered, and payload names which inline form
was actually present, :text or :markup - markup wins when both are
somehow present, since it is the more specific payload. The reason tuple
stays {:content_expr_and_text, expr} regardless of payload; only the
message varies. For :text, the message quotes the expression rather than
the text, which has no span of its own (Statifier.Document.Content's
moduledoc); for :markup, the message quotes the expression and names
markup by kind, since markup DOES have a span of its own
(markup_location) even though this diagnostic does not use it. Reported
at the <content> element's own location, the only span covering both
halves of the conflict.
Check 13 (spec 5.3.2): a <data> carries both an expr and a src
attribute - "MAY have either a 'src' or an 'expr' attribute, but MUST NOT
have both". id is the offending <data>'s own id.
Check 13 (spec 5.10): a <data id="..."> begins with _ - "A conformant
SCXML document MUST NOT contain ids beginning with '_' in the <data>
element".
Check 13 (spec 5.3.2): a <data> carries an expr or src attribute
and non-blank child text - "if either attribute is present, the
element MUST NOT have any children". Whitespace-only text does not count
(Checks.Data.blank?/1).
Check 13 (spec 3.2.2 / 3.3.2 / 3.4.2): a <datamodel> sits on a :final
or :history state - legal only under <scxml>, <state>, and
<parallel>. kind is the offending state's own kind.
Check 7 (spec 3.3): id names a compound state with no
initial attribute and no <initial> element, whose first child in
document order has child_kind - a :history pseudo-state cannot be
entered by the spec 3.3 default-entry fallback. Reported at the first
child's own location.
Spec 5.5: a <donedata> carries both a <content> child and one or more
<param> children - "either a single <content> element or one or more
<param> elements as children of <donedata>, but not both". id is the
owning :final state's own id (nil when unwritten, the same shape
final_parent_missing_id/2 allows). Reported at the <donedata>'s own
location, the only span covering both halves of the conflict.
Check 8 (spec 3.7, 5.7): id names a non-:final state carrying a
non-nil donedata - <donedata> is only legal on :final. Reported at
the donedata's own location.
Check 1 (spec 3.14): id names more than one state in the document. The
location is the offending (non-first) occurrence's own span, never the
canonical one.
Check 1 (spec 3.14): a state was written with id="". id is
typed as an XML Schema ID, whose lexical space is an XML Name and
therefore excludes the empty string, so an empty id is not a name a
transition could ever target.
Check 6 (spec 3.7): id names a state child of a :final - only
onentry, onexit, and donedata are legal there. Reported once per
offending child, at the child's own location (not the <final>'s),
so the caret lands on the element that should not be there.
Check 6 (spec 3.7): id names a :final carrying a <transition>. A
<final> is where a region stops, so it has no outgoing transitions to
take - spec 3.7's content model is onentry, onexit, and donedata
only. Reported once per offending transition, at the transition's own
location rather than the <final>'s, matching final_has_states/2.
Check 12 (spec 3.7, Appendix D enterStates): a <final> state's parent
carries no id. Entering a non-top-level <final> raises
done.state.{parent.id}, so a parent with no written id has a completion
event the spec names after a name it does not have - and no transition
could match "done.state." even if it were raised. final_id is the
offending <final> child's own id, which names the completion the
document loses; it rides in reason and stays out of message,
because it is itself optionally nil and interpolating that reads as a
bug ("containing <final> nil") in the one case the check exists for. The
location is the parent's span, since the parent is the element that
has to change.
Check 5 (spec 3.10): a :history state id is a child of parent_kind,
which is not a compound <state> or <parallel> - the only two legal
parents for a history pseudo-state. parent_kind is :scxml for the
document root itself.
Check 5 (spec 3.10): a <history type="..."> value that is neither
"shallow" nor "deep". raw is the source text as written
(Location.slice/2), so the message quotes what the author
actually typed rather than the default it silently lowered to.
Spec 4.3.2: an <if> with more than one <else> branch (each an
attribute-less, cond-less partition per Statifier.Document.If.Branch).
4.3 caps <else> at "0 or 1 times", so a second one is malformed
regardless of position. Reported at the second <else>'s own
location, never the first's.
Spec 4.3.2: "In a conformant SCXML document, <else> MUST occur after all
<elseif> tags." An <elseif> branch that follows the <if>'s <else>
branch violates that ordering. Reported at the offending <elseif>'s own
location (Statifier.Document.If.Branch.location), not the enclosing
<if>'s.
Check 4 (spec 3.3, 3.6): a state carries both an initial attribute and
an <initial> element - at most one is legal. Reported at the
<initial> element's own span.
Check 3 (spec 3.3, 3.6): a resolved initial target id is not a
descendant of the state it initializes, parent_id - ancestry, not
direct-child membership (v1's bug).
Check 3 (spec 3.3): id names a state carrying an initial
attribute or <initial> element, and the spec MUST NOT: an atomic state
(no states children, or a :parallel, :final, or :history kind) has
nothing to default into. Fires ahead of, and suppresses, the descendancy
check for the same state.
Spec 6.4.1: an <invoke autoforward="..."> value that is neither
"true" nor "false". Same slice-back substrate as
transition_bad_type/2 and Statifier.Validator.Checks.Enums's other
three enumerated attributes - Statifier.Lowering.Attributes.atom/4 maps
an unrecognised value onto the false default, so the atom alone cannot
tell autoforward="false" from autoforward="sideways". raw is the
source text as written (Location.slice/2).
Spec 6.4.1: "id: Must not occur with the 'idlocation' attribute." An
<invoke> carrying both. Reported at the <invoke> element's own
location.
Spec 6.4.1: "namelist: Must not occur with the <param> element." An
<invoke> carrying a written namelist alongside one or more <param>
children. Reported at the <invoke> element's own location.
Spec 6.4.1: the other half of src's "Must not occur with ... the
<content> element" constraint - an <invoke> carrying a <content>
child alongside src or srcexpr. Reported at the <invoke> element's
own location.
Spec 6.4.1: "src: Must not occur with the 'srcexpr' attribute or the
<content> element." The src/srcexpr half of that constraint; the
<content> half is invoke_src_and_content/1. An <invoke> carrying both
src and srcexpr. Reported at the <invoke> element's own location.
Spec 6.4.1: "type: Must not occur with the 'typeexpr' attribute." An
<invoke> carrying both. Reported at the <invoke> element's own
location - the reason carries no payload, matching {:if_duplicate_else}
and {:script_no_src_or_text}'s own nullary shape for a violation with no
natural id to name it by.
Spec 5.7: a <param> element carries both an expr attribute and a
location attribute - "A conformant SCXML document MUST specify either
the 'expr' attribute of <param> or the 'location' attribute, but MUST
NOT specify both." name is the offending <param>'s own name
attribute. Reported at the <param> element's own location.
Spec 5.7: a <param> element carries neither an expr attribute nor a
location attribute - the same "MUST specify either ... but MUST NOT
specify both" rule as param_expr_and_location/2, its other half. name
is the offending <param>'s own name attribute. Reported at the
<param> element's own location.
Spec 5.8.2: "A conformant SCXML document MUST specify either the 'src'
attribute or child content, but not both." src is unreachable in a
struct lowering ever builds (Statifier.Lowering.Builders.build_script/2
already refuses to build one when src is written - ADR-0026 decision
2), so the only way this document-layer check can still observe the
MUST's violation is the other missing half: a <script> with neither.
The reason carries no payload - a <script> has no id and no
attribute-value to name it by, matching {:empty_id} and
{:if_duplicate_else}'s own nullary shape.
Spec 3.2.1: an <scxml binding="..."> value that is neither
"early" nor "late". Same slice-back substrate as
transition_bad_type/2, against the :early default.
Check 13 (spec 3.2.1): a datamodel="..." value outside
["predicator", "elixir", "null", "ecmascript", "xpath"] - the spec's own
three named values ("null", "ecmascript", "xpath") plus this
platform's two ("predicator", its default; "elixir", an alias for
v1-converted documents). Accepting "ecmascript" and "xpath" does not
claim this engine implements either datamodel - spec 3.2.1's Valid Values
clause names "other platform-defined values" as legal too, and Appendix
B's intro states no rule for what a processor does with a datamodel it
does not implement. What this check actually catches is a typo
(datamodel="predicater", datamodel="javascript"); which datamodel this
engine runs is docs/datamodel.md's answer, not this attribute's. raw
is the source text as written (Location.slice/2), the same substrate as
scxml_bad_binding/2.
Spec 6.2.2: "delay ... Must not occur with 'delayexpr' or when the
attribute 'target' has the value "_internal"." The delay/delayexpr
half of that constraint; the target="_internal" half is
send_delay_and_internal_target/1. A <send> carrying both delay and
delayexpr. Reported at the <send> element's own location.
Spec 6.2.2: the other half of delay's "Must not occur with ... when the
attribute 'target' has the value "_internal"" constraint - a <send>
carrying delay or delayexpr alongside a literal target="_internal".
Detectable only when target is a literal attribute; with targetexpr
the value is not known until execute time, so this check stays silent
there rather than guessing (Statifier.Validator.Checks.Send's
moduledoc). Reported at the <send> element's own location.
Spec 6.2.2: "event ... Must not occur with 'eventexpr'." A <send>
carrying both. Reported at the <send> element's own location.
Spec 6.2.2: "id ... Must not occur with 'idlocation'." A <send>
carrying both. Reported at the <send> element's own location.
Spec 6.2.3: "A conformant document MUST NOT specify 'namelist' or
<param> with <content>." The namelist half of that constraint; the
<param> half is send_param_and_content/1. A <send> carrying a
written namelist alongside a <content> child. Reported at the
<send> element's own location.
Spec 6.2.3: the other half of "A conformant document MUST NOT specify
'namelist' or <param> with <content>" - a <send> carrying a
<param> child alongside a <content> child. Reported at the <send>
element's own location.
Spec 6.2.2: "target ... Must not occur with 'targetexpr'." A <send>
carrying both. Reported at the <send> element's own location.
Spec 6.2.2: "type ... Must not occur with 'typeexpr'." A <send>
carrying both. Reported at the <send> element's own location.
Spec 3.5: a <transition type="..."> value that is neither
"internal" nor "external". raw is the source text as written
(Location.slice/2) - lowering maps any out-of-range value
onto the :external default, so the atom alone cannot tell
type="external" from type="sideways".
Checks 4 and 5 (spec 3.6, 3.10): owner's content model is
exactly one <transition>, and it holds count instead. Fires for both
zero (nothing to default into) and two-or-more (which one wins is
undefined).
Checks 4 and 5 (spec 3.6, 3.10): owner's one transition carries attr
(:event or :cond), which neither an <initial> nor a <history>
default transition may specify.
Checks 4 and 5 (spec 3.6, 3.10): owner's one transition
carries no usable target - target="" and an absent target are the
same failure here.
Check 3 (spec 3.3, 3.6): an initial attribute or Document.initial
names id, and no state in the document carries that id. An <initial>
element's own transition targets are never reported by this constructor -
check 2 already owns their existence.
Check 2 (spec 3.5): a <transition>'s target names id, and no state
in the document carries that id. Reported once per unresolved id, at the
transition's own target span (or the transition's, when unwritten).
Types
Which slot a shared transition-shape violation (:transition_count,
:transition_missing_target, :transition_forbidden_attribute) came
from: a state's <initial> element, or a :history state's own default
transition. The id is nil exactly when the owning state's own id is.
@type reason() :: {:duplicate_id, id :: binary()} | {:empty_id} | {:unresolved_target, id :: binary()} | {:unresolved_initial, id :: binary()} | {:initial_not_descendant, id :: binary(), parent_id :: binary()} | {:initial_on_atomic_state, id :: binary()} | {:initial_attribute_and_element, id :: binary()} | {:transition_count, owner :: owner(), count :: non_neg_integer()} | {:transition_missing_target, owner :: owner()} | {:transition_forbidden_attribute, owner :: owner(), attr :: :event | :cond} | {:history_bad_parent, id :: binary(), parent_kind :: atom()} | {:history_bad_type, raw :: binary()} | {:transition_bad_type, raw :: binary()} | {:scxml_bad_binding, raw :: binary()} | {:final_has_states, id :: binary()} | {:final_has_transitions, id :: binary() | nil} | {:final_parent_missing_id, final_id :: binary() | nil} | {:default_entry_not_enterable, id :: binary(), child_kind :: atom()} | {:donedata_not_on_final, id :: binary()} | {:donedata_content_and_params, id :: binary() | nil} | {:content_expr_and_text, expr :: binary()} | {:param_expr_and_location, name :: binary()} | {:param_no_value, name :: binary()} | {:bad_namespace, uri :: binary() | nil} | {:bad_version, version :: binary() | nil} | {:scxml_bad_datamodel, raw :: binary()} | {:data_expr_and_src, id :: binary()} | {:data_value_and_children, id :: binary()} | {:data_reserved_id, id :: binary()} | {:datamodel_bad_parent, kind :: atom()} | {:assign_expr_and_text, expr :: binary()} | {:if_elseif_after_else} | {:if_duplicate_else} | {:script_no_src_or_text} | {:invoke_type_and_typeexpr} | {:invoke_src_and_srcexpr} | {:invoke_src_and_content} | {:invoke_id_and_idlocation} | {:invoke_namelist_and_param} | {:invoke_bad_autoforward, raw :: binary()} | {:send_event_and_eventexpr} | {:send_target_and_targetexpr} | {:send_type_and_typeexpr} | {:send_id_and_idlocation} | {:send_delay_and_delayexpr} | {:send_delay_and_internal_target} | {:send_namelist_and_content} | {:send_param_and_content} | {:cancel_sendid_and_sendidexpr} | {:cancel_no_sendid}
@type t() :: %Statifier.Validator.Error{ location: Statifier.Parser.Location.t(), message: binary(), reason: reason() }
Functions
@spec assign_expr_and_text( expr :: binary(), location :: Statifier.Parser.Location.t() ) :: t()
Spec 5.4.2: an <assign> element carries both an expr attribute and
non-blank child content - "A conformant SCXML document MUST specify
either 'expr' or children of <assign>, but not both." expr is the
attribute's value as lowered, so the message quotes the expression rather
than the text, which has no span of its own. Reported at the <assign>'s
own element span (Statifier.Document.Assign.node_location, not the
SCXML location attribute that struct also carries -
Statifier.Document.Assign's moduledoc names the two spans apart).
@spec bad_namespace(uri :: binary() | nil, location :: Statifier.Parser.Location.t()) :: t()
Check 9 (spec 3.2.1): the root element's resolved namespace,
document.namespace, is not the SCXML namespace. uri is
document.namespace itself - nil for a fragment that declares no
namespace at all, the boilerplate-free case that relaxed parsing left
representable.
@spec bad_version( version :: binary() | nil, location :: Statifier.Parser.Location.t() ) :: t()
Check 10 (spec 3.2.1): the root element's version is not "1.0".
version is nil when the attribute is absent.
@spec cancel_no_sendid(location :: Statifier.Parser.Location.t()) :: t()
Spec 6.3.1: "A conformant SCXML document MUST specify exactly one of
sendid or sendidexpr." A <cancel> carrying neither. Reported at the
<cancel> element's own location.
@spec cancel_sendid_and_sendidexpr(location :: Statifier.Parser.Location.t()) :: t()
Spec 6.3.1: "sendid ... Must not occur with sendidexpr." A <cancel>
carrying both. Reported at the <cancel> element's own location.
The reason tuple's tag - the stable error code.
@spec content_expr_and_text( expr :: binary(), location :: Statifier.Parser.Location.t(), payload :: :text | :markup ) :: t()
Spec 5.6: a <content> element carries both an expr attribute and
inline content, whether that content is text or markup (ADR-0041) - the
spec's "MUST NOT specify both" that lib/statifier/document/content.ex
deliberately leaves representable so this check can report it. expr is
the attribute's value as lowered, and payload names which inline form
was actually present, :text or :markup - markup wins when both are
somehow present, since it is the more specific payload. The reason tuple
stays {:content_expr_and_text, expr} regardless of payload; only the
message varies. For :text, the message quotes the expression rather than
the text, which has no span of its own (Statifier.Document.Content's
moduledoc); for :markup, the message quotes the expression and names
markup by kind, since markup DOES have a span of its own
(markup_location) even though this diagnostic does not use it. Reported
at the <content> element's own location, the only span covering both
halves of the conflict.
@spec data_expr_and_src(id :: binary(), location :: Statifier.Parser.Location.t()) :: t()
Check 13 (spec 5.3.2): a <data> carries both an expr and a src
attribute - "MAY have either a 'src' or an 'expr' attribute, but MUST NOT
have both". id is the offending <data>'s own id.
@spec data_reserved_id(id :: binary(), location :: Statifier.Parser.Location.t()) :: t()
Check 13 (spec 5.10): a <data id="..."> begins with _ - "A conformant
SCXML document MUST NOT contain ids beginning with '_' in the <data>
element".
@spec data_value_and_children( id :: binary(), location :: Statifier.Parser.Location.t() ) :: t()
Check 13 (spec 5.3.2): a <data> carries an expr or src attribute
and non-blank child text - "if either attribute is present, the
element MUST NOT have any children". Whitespace-only text does not count
(Checks.Data.blank?/1).
@spec datamodel_bad_parent(kind :: atom(), location :: Statifier.Parser.Location.t()) :: t()
Check 13 (spec 3.2.2 / 3.3.2 / 3.4.2): a <datamodel> sits on a :final
or :history state - legal only under <scxml>, <state>, and
<parallel>. kind is the offending state's own kind.
@spec default_entry_not_enterable( id :: binary() | nil, child_kind :: atom(), location :: Statifier.Parser.Location.t() ) :: t()
Check 7 (spec 3.3): id names a compound state with no
initial attribute and no <initial> element, whose first child in
document order has child_kind - a :history pseudo-state cannot be
entered by the spec 3.3 default-entry fallback. Reported at the first
child's own location.
@spec donedata_content_and_params( id :: binary() | nil, location :: Statifier.Parser.Location.t() ) :: t()
Spec 5.5: a <donedata> carries both a <content> child and one or more
<param> children - "either a single <content> element or one or more
<param> elements as children of <donedata>, but not both". id is the
owning :final state's own id (nil when unwritten, the same shape
final_parent_missing_id/2 allows). Reported at the <donedata>'s own
location, the only span covering both halves of the conflict.
@spec donedata_not_on_final( id :: binary() | nil, location :: Statifier.Parser.Location.t() ) :: t()
Check 8 (spec 3.7, 5.7): id names a non-:final state carrying a
non-nil donedata - <donedata> is only legal on :final. Reported at
the donedata's own location.
@spec duplicate_id(id :: binary(), location :: Statifier.Parser.Location.t()) :: t()
Check 1 (spec 3.14): id names more than one state in the document. The
location is the offending (non-first) occurrence's own span, never the
canonical one.
@spec empty_id(location :: Statifier.Parser.Location.t()) :: t()
Check 1 (spec 3.14): a state was written with id="". id is
typed as an XML Schema ID, whose lexical space is an XML Name and
therefore excludes the empty string, so an empty id is not a name a
transition could ever target.
This is a different case from an absent id, which is legal and reported
by nothing (lib/statifier/document/state.ex): the two are told apart by
the attribute_locations[:id] key, never by the value alone. The reason
carries no payload - the offending value is the empty string, and the
location is the span the author wrote it at.
@spec final_has_states( id :: binary() | nil, location :: Statifier.Parser.Location.t() ) :: t()
Check 6 (spec 3.7): id names a state child of a :final - only
onentry, onexit, and donedata are legal there. Reported once per
offending child, at the child's own location (not the <final>'s),
so the caret lands on the element that should not be there.
@spec final_has_transitions( id :: binary() | nil, location :: Statifier.Parser.Location.t() ) :: t()
Check 6 (spec 3.7): id names a :final carrying a <transition>. A
<final> is where a region stops, so it has no outgoing transitions to
take - spec 3.7's content model is onentry, onexit, and donedata
only. Reported once per offending transition, at the transition's own
location rather than the <final>'s, matching final_has_states/2.
@spec final_parent_missing_id( final_id :: binary() | nil, location :: Statifier.Parser.Location.t() ) :: t()
Check 12 (spec 3.7, Appendix D enterStates): a <final> state's parent
carries no id. Entering a non-top-level <final> raises
done.state.{parent.id}, so a parent with no written id has a completion
event the spec names after a name it does not have - and no transition
could match "done.state." even if it were raised. final_id is the
offending <final> child's own id, which names the completion the
document loses; it rides in reason and stays out of message,
because it is itself optionally nil and interpolating that reads as a
bug ("containing <final> nil") in the one case the check exists for. The
location is the parent's span, since the parent is the element that
has to change.
@spec history_bad_parent( id :: binary() | nil, parent_kind :: atom(), location :: Statifier.Parser.Location.t() ) :: t()
Check 5 (spec 3.10): a :history state id is a child of parent_kind,
which is not a compound <state> or <parallel> - the only two legal
parents for a history pseudo-state. parent_kind is :scxml for the
document root itself.
@spec history_bad_type(raw :: binary(), location :: Statifier.Parser.Location.t()) :: t()
Check 5 (spec 3.10): a <history type="..."> value that is neither
"shallow" nor "deep". raw is the source text as written
(Location.slice/2), so the message quotes what the author
actually typed rather than the default it silently lowered to.
@spec if_duplicate_else(location :: Statifier.Parser.Location.t()) :: t()
Spec 4.3.2: an <if> with more than one <else> branch (each an
attribute-less, cond-less partition per Statifier.Document.If.Branch).
4.3 caps <else> at "0 or 1 times", so a second one is malformed
regardless of position. Reported at the second <else>'s own
location, never the first's.
@spec if_elseif_after_else(location :: Statifier.Parser.Location.t()) :: t()
Spec 4.3.2: "In a conformant SCXML document, <else> MUST occur after all
<elseif> tags." An <elseif> branch that follows the <if>'s <else>
branch violates that ordering. Reported at the offending <elseif>'s own
location (Statifier.Document.If.Branch.location), not the enclosing
<if>'s.
@spec initial_attribute_and_element( id :: binary() | nil, location :: Statifier.Parser.Location.t() ) :: t()
Check 4 (spec 3.3, 3.6): a state carries both an initial attribute and
an <initial> element - at most one is legal. Reported at the
<initial> element's own span.
@spec initial_not_descendant( id :: binary(), parent_id :: binary(), location :: Statifier.Parser.Location.t() ) :: t()
Check 3 (spec 3.3, 3.6): a resolved initial target id is not a
descendant of the state it initializes, parent_id - ancestry, not
direct-child membership (v1's bug).
@spec initial_on_atomic_state( id :: binary(), location :: Statifier.Parser.Location.t() ) :: t()
Check 3 (spec 3.3): id names a state carrying an initial
attribute or <initial> element, and the spec MUST NOT: an atomic state
(no states children, or a :parallel, :final, or :history kind) has
nothing to default into. Fires ahead of, and suppresses, the descendancy
check for the same state.
@spec invoke_bad_autoforward( raw :: binary(), location :: Statifier.Parser.Location.t() ) :: t()
Spec 6.4.1: an <invoke autoforward="..."> value that is neither
"true" nor "false". Same slice-back substrate as
transition_bad_type/2 and Statifier.Validator.Checks.Enums's other
three enumerated attributes - Statifier.Lowering.Attributes.atom/4 maps
an unrecognised value onto the false default, so the atom alone cannot
tell autoforward="false" from autoforward="sideways". raw is the
source text as written (Location.slice/2).
@spec invoke_id_and_idlocation(location :: Statifier.Parser.Location.t()) :: t()
Spec 6.4.1: "id: Must not occur with the 'idlocation' attribute." An
<invoke> carrying both. Reported at the <invoke> element's own
location.
@spec invoke_namelist_and_param(location :: Statifier.Parser.Location.t()) :: t()
Spec 6.4.1: "namelist: Must not occur with the <param> element." An
<invoke> carrying a written namelist alongside one or more <param>
children. Reported at the <invoke> element's own location.
@spec invoke_src_and_content(location :: Statifier.Parser.Location.t()) :: t()
Spec 6.4.1: the other half of src's "Must not occur with ... the
<content> element" constraint - an <invoke> carrying a <content>
child alongside src or srcexpr. Reported at the <invoke> element's
own location.
@spec invoke_src_and_srcexpr(location :: Statifier.Parser.Location.t()) :: t()
Spec 6.4.1: "src: Must not occur with the 'srcexpr' attribute or the
<content> element." The src/srcexpr half of that constraint; the
<content> half is invoke_src_and_content/1. An <invoke> carrying both
src and srcexpr. Reported at the <invoke> element's own location.
@spec invoke_type_and_typeexpr(location :: Statifier.Parser.Location.t()) :: t()
Spec 6.4.1: "type: Must not occur with the 'typeexpr' attribute." An
<invoke> carrying both. Reported at the <invoke> element's own
location - the reason carries no payload, matching {:if_duplicate_else}
and {:script_no_src_or_text}'s own nullary shape for a violation with no
natural id to name it by.
@spec param_expr_and_location( name :: binary(), location :: Statifier.Parser.Location.t() ) :: t()
Spec 5.7: a <param> element carries both an expr attribute and a
location attribute - "A conformant SCXML document MUST specify either
the 'expr' attribute of <param> or the 'location' attribute, but MUST
NOT specify both." name is the offending <param>'s own name
attribute. Reported at the <param> element's own location.
@spec param_no_value(name :: binary(), location :: Statifier.Parser.Location.t()) :: t()
Spec 5.7: a <param> element carries neither an expr attribute nor a
location attribute - the same "MUST specify either ... but MUST NOT
specify both" rule as param_expr_and_location/2, its other half. name
is the offending <param>'s own name attribute. Reported at the
<param> element's own location.
@spec script_no_src_or_text(location :: Statifier.Parser.Location.t()) :: t()
Spec 5.8.2: "A conformant SCXML document MUST specify either the 'src'
attribute or child content, but not both." src is unreachable in a
struct lowering ever builds (Statifier.Lowering.Builders.build_script/2
already refuses to build one when src is written - ADR-0026 decision
2), so the only way this document-layer check can still observe the
MUST's violation is the other missing half: a <script> with neither.
The reason carries no payload - a <script> has no id and no
attribute-value to name it by, matching {:empty_id} and
{:if_duplicate_else}'s own nullary shape.
@spec scxml_bad_binding(raw :: binary(), location :: Statifier.Parser.Location.t()) :: t()
Spec 3.2.1: an <scxml binding="..."> value that is neither
"early" nor "late". Same slice-back substrate as
transition_bad_type/2, against the :early default.
@spec scxml_bad_datamodel(raw :: binary(), location :: Statifier.Parser.Location.t()) :: t()
Check 13 (spec 3.2.1): a datamodel="..." value outside
["predicator", "elixir", "null", "ecmascript", "xpath"] - the spec's own
three named values ("null", "ecmascript", "xpath") plus this
platform's two ("predicator", its default; "elixir", an alias for
v1-converted documents). Accepting "ecmascript" and "xpath" does not
claim this engine implements either datamodel - spec 3.2.1's Valid Values
clause names "other platform-defined values" as legal too, and Appendix
B's intro states no rule for what a processor does with a datamodel it
does not implement. What this check actually catches is a typo
(datamodel="predicater", datamodel="javascript"); which datamodel this
engine runs is docs/datamodel.md's answer, not this attribute's. raw
is the source text as written (Location.slice/2), the same substrate as
scxml_bad_binding/2.
@spec send_delay_and_delayexpr(location :: Statifier.Parser.Location.t()) :: t()
Spec 6.2.2: "delay ... Must not occur with 'delayexpr' or when the
attribute 'target' has the value "_internal"." The delay/delayexpr
half of that constraint; the target="_internal" half is
send_delay_and_internal_target/1. A <send> carrying both delay and
delayexpr. Reported at the <send> element's own location.
@spec send_delay_and_internal_target(location :: Statifier.Parser.Location.t()) :: t()
Spec 6.2.2: the other half of delay's "Must not occur with ... when the
attribute 'target' has the value "_internal"" constraint - a <send>
carrying delay or delayexpr alongside a literal target="_internal".
Detectable only when target is a literal attribute; with targetexpr
the value is not known until execute time, so this check stays silent
there rather than guessing (Statifier.Validator.Checks.Send's
moduledoc). Reported at the <send> element's own location.
@spec send_event_and_eventexpr(location :: Statifier.Parser.Location.t()) :: t()
Spec 6.2.2: "event ... Must not occur with 'eventexpr'." A <send>
carrying both. Reported at the <send> element's own location.
@spec send_id_and_idlocation(location :: Statifier.Parser.Location.t()) :: t()
Spec 6.2.2: "id ... Must not occur with 'idlocation'." A <send>
carrying both. Reported at the <send> element's own location.
@spec send_namelist_and_content(location :: Statifier.Parser.Location.t()) :: t()
Spec 6.2.3: "A conformant document MUST NOT specify 'namelist' or
<param> with <content>." The namelist half of that constraint; the
<param> half is send_param_and_content/1. A <send> carrying a
written namelist alongside a <content> child. Reported at the
<send> element's own location.
@spec send_param_and_content(location :: Statifier.Parser.Location.t()) :: t()
Spec 6.2.3: the other half of "A conformant document MUST NOT specify
'namelist' or <param> with <content>" - a <send> carrying a
<param> child alongside a <content> child. Reported at the <send>
element's own location.
@spec send_target_and_targetexpr(location :: Statifier.Parser.Location.t()) :: t()
Spec 6.2.2: "target ... Must not occur with 'targetexpr'." A <send>
carrying both. Reported at the <send> element's own location.
@spec send_type_and_typeexpr(location :: Statifier.Parser.Location.t()) :: t()
Spec 6.2.2: "type ... Must not occur with 'typeexpr'." A <send>
carrying both. Reported at the <send> element's own location.
@spec transition_bad_type(raw :: binary(), location :: Statifier.Parser.Location.t()) :: t()
Spec 3.5: a <transition type="..."> value that is neither
"internal" nor "external". raw is the source text as written
(Location.slice/2) - lowering maps any out-of-range value
onto the :external default, so the atom alone cannot tell
type="external" from type="sideways".
@spec transition_count( owner :: owner(), count :: non_neg_integer(), location :: Statifier.Parser.Location.t() ) :: t()
Checks 4 and 5 (spec 3.6, 3.10): owner's content model is
exactly one <transition>, and it holds count instead. Fires for both
zero (nothing to default into) and two-or-more (which one wins is
undefined).
@spec transition_forbidden_attribute( owner :: owner(), attr :: :event | :cond, location :: Statifier.Parser.Location.t() ) :: t()
Checks 4 and 5 (spec 3.6, 3.10): owner's one transition carries attr
(:event or :cond), which neither an <initial> nor a <history>
default transition may specify.
@spec transition_missing_target( owner :: owner(), location :: Statifier.Parser.Location.t() ) :: t()
Checks 4 and 5 (spec 3.6, 3.10): owner's one transition
carries no usable target - target="" and an absent target are the
same failure here.
@spec unresolved_initial(id :: binary(), location :: Statifier.Parser.Location.t()) :: t()
Check 3 (spec 3.3, 3.6): an initial attribute or Document.initial
names id, and no state in the document carries that id. An <initial>
element's own transition targets are never reported by this constructor -
check 2 already owns their existence.
@spec unresolved_target(id :: binary(), location :: Statifier.Parser.Location.t()) :: t()
Check 2 (spec 3.5): a <transition>'s target names id, and no state
in the document carries that id. Reported once per unresolved id, at the
transition's own target span (or the transition's, when unwritten).