@formicoidea/labre-framework-uml 0.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +384 -0
- package/dist/actions.d.ts +430 -0
- package/dist/actions.js +724 -0
- package/dist/background.d.ts +153 -0
- package/dist/background.js +557 -0
- package/dist/board-hit.d.ts +137 -0
- package/dist/board-hit.js +191 -0
- package/dist/commands-manifest.d.ts +19 -0
- package/dist/commands-manifest.js +571 -0
- package/dist/commands.d.ts +4 -0
- package/dist/commands.js +983 -0
- package/dist/component.d.ts +521 -0
- package/dist/component.js +708 -0
- package/dist/consts.d.ts +365 -0
- package/dist/consts.js +463 -0
- package/dist/descriptor.d.ts +12 -0
- package/dist/descriptor.js +10 -0
- package/dist/drawio-decode.d.ts +32 -0
- package/dist/drawio-decode.js +96 -0
- package/dist/drawio-import.d.ts +133 -0
- package/dist/drawio-import.js +885 -0
- package/dist/edge-morph.d.ts +154 -0
- package/dist/edge-morph.js +153 -0
- package/dist/edge-styles.d.ts +78 -0
- package/dist/edge-styles.js +210 -0
- package/dist/effects.d.ts +9 -0
- package/dist/effects.js +6 -0
- package/dist/element-renderer.d.ts +64 -0
- package/dist/element-renderer.js +114 -0
- package/dist/element-view.d.ts +234 -0
- package/dist/element-view.js +430 -0
- package/dist/export.d.ts +61 -0
- package/dist/export.js +41 -0
- package/dist/filename.d.ts +17 -0
- package/dist/filename.js +26 -0
- package/dist/frame-tag.d.ts +70 -0
- package/dist/frame-tag.js +78 -0
- package/dist/grammar.d.ts +695 -0
- package/dist/grammar.js +1436 -0
- package/dist/import.d.ts +306 -0
- package/dist/import.js +1270 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +67 -0
- package/dist/interchange.d.ts +125 -0
- package/dist/interchange.js +372 -0
- package/dist/keywords.d.ts +204 -0
- package/dist/keywords.js +361 -0
- package/dist/kinds.d.ts +73 -0
- package/dist/kinds.js +91 -0
- package/dist/legend.d.ts +30 -0
- package/dist/legend.js +255 -0
- package/dist/model.d.ts +862 -0
- package/dist/model.js +1316 -0
- package/dist/morph.d.ts +178 -0
- package/dist/morph.js +496 -0
- package/dist/node/act-glyphs.d.ts +30 -0
- package/dist/node/act-glyphs.js +142 -0
- package/dist/node/compartment-watcher.d.ts +107 -0
- package/dist/node/compartment-watcher.js +163 -0
- package/dist/node/fit.d.ts +85 -0
- package/dist/node/fit.js +129 -0
- package/dist/node/node-renderer.d.ts +49 -0
- package/dist/node/node-renderer.js +611 -0
- package/dist/node/node-view.d.ts +51 -0
- package/dist/node/node-view.js +89 -0
- package/dist/node/paint.d.ts +84 -0
- package/dist/node/paint.js +143 -0
- package/dist/node/sd-glyphs.d.ts +36 -0
- package/dist/node/sd-glyphs.js +79 -0
- package/dist/node/stm-glyphs.d.ts +22 -0
- package/dist/node/stm-glyphs.js +111 -0
- package/dist/node/tier-metrics.d.ts +38 -0
- package/dist/node/tier-metrics.js +45 -0
- package/dist/operators.d.ts +45 -0
- package/dist/operators.js +75 -0
- package/dist/plantuml-import.d.ts +113 -0
- package/dist/plantuml-import.js +1723 -0
- package/dist/plantuml.d.ts +94 -0
- package/dist/plantuml.js +985 -0
- package/dist/presets.d.ts +149 -0
- package/dist/presets.js +302 -0
- package/dist/profiles.d.ts +2 -0
- package/dist/profiles.js +358 -0
- package/dist/reading.d.ts +160 -0
- package/dist/reading.js +648 -0
- package/dist/roles.d.ts +257 -0
- package/dist/roles.js +997 -0
- package/dist/rules.d.ts +267 -0
- package/dist/rules.js +3090 -0
- package/dist/templates.d.ts +2 -0
- package/dist/templates.js +197 -0
- package/dist/toolbar/config.d.ts +228 -0
- package/dist/toolbar/config.js +833 -0
- package/dist/toolbar/icons.d.ts +440 -0
- package/dist/toolbar/icons.js +711 -0
- package/dist/toolbar/senior-tool.d.ts +11 -0
- package/dist/toolbar/senior-tool.js +21 -0
- package/dist/toolbar/uml-menu.d.ts +17 -0
- package/dist/toolbar/uml-menu.js +20 -0
- package/dist/toolbar/uml-senior-button.d.ts +18 -0
- package/dist/toolbar/uml-senior-button.js +22 -0
- package/dist/translations.d.ts +27 -0
- package/dist/translations.js +117 -0
- package/dist/view.d.ts +33 -0
- package/dist/view.js +236 -0
- package/dist/xmi-import.d.ts +174 -0
- package/dist/xmi-import.js +2268 -0
- package/dist/xmi.d.ts +61 -0
- package/dist/xmi.js +1860 -0
- package/dist/xml-reader.d.ts +157 -0
- package/dist/xml-reader.js +430 -0
- package/dist/xml.d.ts +72 -0
- package/dist/xml.js +96 -0
- package/package.json +38 -0
package/dist/rules.js
ADDED
|
@@ -0,0 +1,3090 @@
|
|
|
1
|
+
import { UML_SUBJECT_FRAME } from './background.js';
|
|
2
|
+
import { UML_PORT_BORDER_TOLERANCE } from './consts.js';
|
|
3
|
+
import { checkEndLabelMultiplicity, checkLifelineIdent, checkMessageLabel, checkOperationLine, checkPropertyLine, checkTransitionLabel, } from './grammar.js';
|
|
4
|
+
import { UML_ROLE, UML_ROLES } from './roles.js';
|
|
5
|
+
/**
|
|
6
|
+
* UML validation rules — the notation's own grammar, as DATA.
|
|
7
|
+
*
|
|
8
|
+
* DATA owned by the framework and versioned per rule: the engine
|
|
9
|
+
* (`@labre/affine-block-surface`) knows how to evaluate a FAMILY, never a
|
|
10
|
+
* concrete rule, so adding a UML rule is adding an entry to the array at the
|
|
11
|
+
* bottom of this file. Registered from the flag-gated `UmlViewExtension`, so
|
|
12
|
+
* switching the `uml` flag off removes the rules with the rest of the tooling —
|
|
13
|
+
* diagrams already drawn keep rendering, they simply stop being checked
|
|
14
|
+
* (`docs/adr/0009`).
|
|
15
|
+
*
|
|
16
|
+
* ## Where the rules come from, which is unlike every pack before it
|
|
17
|
+
*
|
|
18
|
+
* UML has a SPECIFICATION. Where `c4/rules.ts` opens by explaining that its
|
|
19
|
+
* authority is a review checklist because there is nothing else to cite, this
|
|
20
|
+
* pack cites clauses: §9.9.7 says a generalization's two ends are of the same
|
|
21
|
+
* kind, §10.4.3 types an interface realization's contract, §11.5.3 gives a part
|
|
22
|
+
* at most one composite owner, §18.1.3 types the ends of an include and forbids
|
|
23
|
+
* an association between two actors, §19.2.3 types a deployment's two ends and
|
|
24
|
+
* §19.4.3 says a communication path joins two DeploymentTargets. The behaviour
|
|
25
|
+
* sheets cite seven more, and they are the arithmetic ones: §15.3.3 gives an
|
|
26
|
+
* initial node no incoming edge, §15.7.19.4 gives a final node no outgoing one,
|
|
27
|
+
* §15.7.11.4 caps a decision at two incoming edges, §15.7.9.4 keeps an object
|
|
28
|
+
* node off a control flow, §14.5.11.4 gives a final state no outgoing transition
|
|
29
|
+
* and §14.5.12 types a transition's two ends. The four SPELLING rules cite four
|
|
30
|
+
* more, and they are the only clauses in the pack quoted as productions rather
|
|
31
|
+
* than as sentences: §9.5.4 (a Property), §9.6.4 (an Operation), §14.2.4.8 (a
|
|
32
|
+
* transition label) and §7.5.4 (a multiplicity range). TWENTY-ONE rules
|
|
33
|
+
* therefore declare `provenance.source: 'standard'` — the most in this library
|
|
34
|
+
* by a distance — and they are the twenty-one a conformance report may present
|
|
35
|
+
* as defects.
|
|
36
|
+
*
|
|
37
|
+
* The rest are `recommendation` (a reading of the notation the spec draws but
|
|
38
|
+
* does not constrain: which artefacts belong on which KIND of sheet, whether an
|
|
39
|
+
* artefact is named at all) or `labre-convention` (membership on this canvas, and
|
|
40
|
+
* the role-less connector this whiteboard can produce and the notation never
|
|
41
|
+
* anticipated). Each says so in its own citation, so a reader who opens the
|
|
42
|
+
* bubble is never told UML forbids what UML does not.
|
|
43
|
+
*
|
|
44
|
+
* ## Severity: the CROQUIS primes, so everything is `audit`
|
|
45
|
+
*
|
|
46
|
+
* The same promise `wardley/rules.ts:30`, `bpmn/rules.ts` and `c4/rules.ts`
|
|
47
|
+
* make. Every rule here is declared `audit` and the default profile —
|
|
48
|
+
* `uml.sketch` — keeps it there. A class diagram is drawn boxes-first,
|
|
49
|
+
* arrows-second, words-last, and for the whole of that the drawing contradicts
|
|
50
|
+
* half this file. `uml.strict` is the level somebody chooses once the diagram is
|
|
51
|
+
* a deliverable, and it promotes the twenty-five rules that restate the
|
|
52
|
+
* specification
|
|
53
|
+
* or the sheet's own declaration (`profiles.ts`).
|
|
54
|
+
*
|
|
55
|
+
* `blocking-overridable` appears nowhere: nothing downstream implements a
|
|
56
|
+
* blocking level, so shipping the value would be data claiming an effect that
|
|
57
|
+
* does not exist.
|
|
58
|
+
*
|
|
59
|
+
* ## The ALPHABET and the GRAMMAR are two different tables
|
|
60
|
+
*
|
|
61
|
+
* The lesson `c4/rules.ts` records at length, and UML needs it more, having nine
|
|
62
|
+
* edge roles where C4 has one. `relation-endpoints` raises an off-matrix finding
|
|
63
|
+
* for every rule declaring a matrix, so two rules sharing one grammar report the
|
|
64
|
+
* same wrong sentence TWICE. Here the split is per EDGE ROLE: each grammar table
|
|
65
|
+
* below is held by exactly one rule, and the one place two rules read the same
|
|
66
|
+
* edge role — {@link untypedEdge} and {@link actorActorAssociation}, both on
|
|
67
|
+
* `uml:association` — is the C4 arrangement exactly, the neutral rule carrying a
|
|
68
|
+
* matrix so permissive that off-matrix is structurally unreachable for it.
|
|
69
|
+
*
|
|
70
|
+
* ## The ALPHABET is also how a rule gets something to SAY
|
|
71
|
+
*
|
|
72
|
+
* The other half of the same mechanism, and the one that shapes three tables
|
|
73
|
+
* here. An end whose role is outside a rule's alphabet is not evaluated at all
|
|
74
|
+
* — the family's hard proportionality requirement — so a rule whose only
|
|
75
|
+
* sanctioned sentence is `use case → use case` has an alphabet of one role and
|
|
76
|
+
* can NEVER fire: an include drawn from an actor is silently outside it. Data
|
|
77
|
+
* that can never fire and never says why is the worst thing declarative data can
|
|
78
|
+
* do.
|
|
79
|
+
*
|
|
80
|
+
* So {@link UML_USE_CASE_MATRIX} and {@link UML_GENERALIZATION_MATRIX} each
|
|
81
|
+
* carry one or two triplets whose EDGE role is not the rule's own. They are true
|
|
82
|
+
* sentences of the notation — an actor associates with a use case (§18.1.3), two
|
|
83
|
+
* instances are joined by a link (§9.8.4) — and `inMatrix` tests the edge role
|
|
84
|
+
* with `roleIsA`, so such a triplet can never sanction the edge the rule is
|
|
85
|
+
* about. Their whole effect is to put `uml:actor` and `uml:object` into the
|
|
86
|
+
* alphabet, which is what makes "an include with an actor at one end" and "a
|
|
87
|
+
* generalization pointing at an instance" findings rather than silence.
|
|
88
|
+
*
|
|
89
|
+
* ## What the whole file stays silent about
|
|
90
|
+
*
|
|
91
|
+
* A diagram drawn before the roles existed carries no role on anything, so it is
|
|
92
|
+
* never evaluated and never says a word (PRD principle 8). A sheet drawn with no
|
|
93
|
+
* `uml:diagram` frame round it has no frame for the membership rules to be
|
|
94
|
+
* about, and they are silent by construction — a croquis on bare canvas is a
|
|
95
|
+
* croquis. A use case diagram with no SUBJECT drawn on it is the same silence one
|
|
96
|
+
* frame in: {@link useCaseOutsideSubject} and {@link actorInsideSubject} both
|
|
97
|
+
* need a subject to be inside or outside OF.
|
|
98
|
+
*
|
|
99
|
+
* ## Six questions this pack deliberately does NOT ask
|
|
100
|
+
*
|
|
101
|
+
* Not gaps in the notation — gaps in what the engine's families can be asked of
|
|
102
|
+
* a UML element. The first two are phase 1's; the next is the structural
|
|
103
|
+
* sheets', one of the two rules that brief asked for and this file could not
|
|
104
|
+
* honestly write. The last three are the behaviour sheets', and two of them are
|
|
105
|
+
* the same shape: a rule the brief asked for that ALREADY EXISTS under another
|
|
106
|
+
* name, and would have reported one mistake twice.
|
|
107
|
+
*
|
|
108
|
+
* It was SEVEN until the recette of 2026-09-16, and the one that left is worth
|
|
109
|
+
* a line here because of how it left. `uml.port-on-border` was recorded in this
|
|
110
|
+
* list as unwritable — no family expressed "within a band of that box's
|
|
111
|
+
* outline" — and the PO dragged a port into the middle of a component and
|
|
112
|
+
* asked why nothing happened. The answer was not to approximate it with a
|
|
113
|
+
* family that asks a neighbouring question; it was to write the family the
|
|
114
|
+
* question needs (`border-proximity`, ADR 0024), which is the second time this
|
|
115
|
+
* pack has done that and the second time it was the right call (ADR 0021 is the
|
|
116
|
+
* first). A recorded gap is a gap somebody can close. The six below are the ones
|
|
117
|
+
* still open, and each says what would close it.
|
|
118
|
+
*
|
|
119
|
+
* - **an artefact of any kind drawn outside the frame.** `element-in-background`
|
|
120
|
+
* names ONE subject role, and UML's vocabulary has no single role meaning "any
|
|
121
|
+
* UML artefact": `uml:classifier` is the spec's own generalisation of a class,
|
|
122
|
+
* an interface and an enumeration (§9.2) and reaches nothing else, because an
|
|
123
|
+
* object is an instance and a package is a namespace — filing them under it
|
|
124
|
+
* would make every rule about classifiers fall on them. So
|
|
125
|
+
* {@link elementOutsideFrame} is written on the classifiers, which is what a
|
|
126
|
+
* class diagram is made of, and an object, a package, a note, an actor or a use
|
|
127
|
+
* case parked beside the sheet is silence. The same shape of limit C4 records
|
|
128
|
+
* for its empty-boundary rule, and it closes the day a family accepts several
|
|
129
|
+
* subject roles.
|
|
130
|
+
* - **the MULTIPLICITY on an association end.** UML writes it at the ends, this
|
|
131
|
+
* canvas has one label per connector, and `docs/adr/0018` is the record of why
|
|
132
|
+
* phase 1 puts the words in the centre label. Until a connector carries per-end
|
|
133
|
+
* text there is nothing for a rule to read, and asking for a multiplicity in
|
|
134
|
+
* the middle of the line would be asking the author to write the notation
|
|
135
|
+
* wrongly so the tool could check it.
|
|
136
|
+
* - **a lollipop or socket attached to nothing** (`uml.interface-near-component`,
|
|
137
|
+
* §10.4.4, §11.6.4). The port's neighbour, and `border-proximity` does NOT
|
|
138
|
+
* close it: that family judges a glyph the author has already put ON a box —
|
|
139
|
+
* overlap is its gate — and a lollipop is drawn BESIDE one, touching it at a
|
|
140
|
+
* gap rather than straddling its edge. Asking "is this stub within 24 units of
|
|
141
|
+
* some component" is a third geometry again (`attachment` measures adjacency to
|
|
142
|
+
* a path, `no-overlap` forbids a collision rather than requiring a
|
|
143
|
+
* proximity), and it is the one `model.ts` already reads for the exporters. So
|
|
144
|
+
* the adjacency stays there and no rule is written on it. What would close it
|
|
145
|
+
* is `border-proximity` gaining an outward reading — "near the outline, inside
|
|
146
|
+
* or out" — and that is a different requirement rather than a wider tolerance:
|
|
147
|
+
* a stub floating in open canvas would then need a rule about it, which is a
|
|
148
|
+
* remark about a sketch.
|
|
149
|
+
* - **an ACTION with no name** (`uml.unnamed-action`) and **a STATE with no
|
|
150
|
+
* name** (`uml.unnamed-state`). Both are already reported, and adding them
|
|
151
|
+
* would report them twice. `label-presence` reads an element's OWN words and
|
|
152
|
+
* names one subject role, so the subject of a naming rule is the TIER and
|
|
153
|
+
* never the shape it is grouped with — which is why
|
|
154
|
+
* {@link unnamedActorOrUseCase} is one rule for two artefacts and says so at
|
|
155
|
+
* length. An action, an object node and the three signal artefacts carry
|
|
156
|
+
* `uml:label`, so an emptied one is already that rule's finding; a state
|
|
157
|
+
* carries `uml:name`, so an emptied one is already {@link unnamedClassifier}'s.
|
|
158
|
+
* A second rule on either tier would put two brackets and two suggestions on
|
|
159
|
+
* one word to fix, one of which would always be about the wrong shape —
|
|
160
|
+
* exactly the failure the existing pair was collapsed to avoid.
|
|
161
|
+
*
|
|
162
|
+
* What IS wrong is the wording: "This actor or use case has no name" is the
|
|
163
|
+
* sentence an author of an emptied action reads. The fix is a rename of the
|
|
164
|
+
* two existing rules to the tier they are actually about
|
|
165
|
+
* (`uml.unnamed-label`, `uml.unnamed-name`) with the words widened to match,
|
|
166
|
+
* which touches their ids, their message keys, both profiles and the host
|
|
167
|
+
* catalogues — a change worth making on its own rather than smuggled in
|
|
168
|
+
* beside fifteen new rules. Recorded here so it stays a decision.
|
|
169
|
+
*
|
|
170
|
+
* **2026-09-16: the rename is BLOCKED, and the list above is why it looked
|
|
171
|
+
* cheap.** It misses the one reader that is not code: a DOCUMENT. An id is
|
|
172
|
+
* not merely declared and displayed, it is STORED — "excuse this finding"
|
|
173
|
+
* writes it into the element's own
|
|
174
|
+
* `GfxPrimitiveElementModel.validationExceptions`, a `@field()`, so it lands
|
|
175
|
+
* in the Y.Map, syncs to every peer and survives a copy. Rename the rule and
|
|
176
|
+
* no stored exception matches it any more: every waiver a user granted
|
|
177
|
+
* against these two lapses in silence, the finding returns on a board that
|
|
178
|
+
* had settled it, and the orphaned entry stays behind for good. Only
|
|
179
|
+
* `validationProfile` was thought to reach a document; exceptions are the
|
|
180
|
+
* second path and they are keyed by RULE id. So the wording can still be
|
|
181
|
+
* widened — `messageFallback` and the message keys are display and nothing
|
|
182
|
+
* stores them — but the IDS are frozen until something migrates
|
|
183
|
+
* `validationExceptions`, which is a store concern. See `docs/adr/0017`.
|
|
184
|
+
* - **one beginning per REGION** rather than per sheet. §14.5.6.4 bounds the
|
|
185
|
+
* initial vertex per Region and a state machine with three composite states
|
|
186
|
+
* legitimately draws four discs; `role-count` counts per instance of ONE
|
|
187
|
+
* frame, so {@link initialSingle} counts per `uml:diagram` and the per-region
|
|
188
|
+
* count is a second rule of the same family against `uml:region`. It is a
|
|
189
|
+
* phase-3 refinement rather than a limit of the engine — the family already
|
|
190
|
+
* expresses it — and until it lands the rule is `audit` in both profiles for
|
|
191
|
+
* that reason as much as for any other.
|
|
192
|
+
*
|
|
193
|
+
* ## Two things a HOST reading these findings has to know
|
|
194
|
+
*
|
|
195
|
+
* Both came out of the tranche-F recette, both are engine behaviour rather than
|
|
196
|
+
* anything this file declares, and both make a check-up look wrong to somebody
|
|
197
|
+
* who does not know them.
|
|
198
|
+
*
|
|
199
|
+
* - **a membership finding is attributed to the LANE, not to the sheet.**
|
|
200
|
+
* `uml.node-in-partition` and the `*-history-outside-region` pair are
|
|
201
|
+
* `element-in-background` rules whose SUBJECT is the band or the composite
|
|
202
|
+
* state — that is what "inside" is measured against — so the finding is filed
|
|
203
|
+
* under the `uml:partition` or the `uml:region` it is about. A check-up run on
|
|
204
|
+
* the FRAME alone therefore shows none of them, and a host that offers "check
|
|
205
|
+
* this diagram" by selecting the `umlDiagram` has to include the backgrounds
|
|
206
|
+
* drawn on it, or it will report a clean sheet over an action drawn between
|
|
207
|
+
* two lanes.
|
|
208
|
+
* - **a strict profile MOVES a finding, it does not add one.** Promoting a rule
|
|
209
|
+
* past `audit` takes it out of the check-up's own list and into the live
|
|
210
|
+
* `violations$` path, by the engine's design. So switching `uml.sketch` for
|
|
211
|
+
* `uml.strict` makes a check-up report FEWER rows, not more, and the rows that
|
|
212
|
+
* left are the ones now drawn on the canvas. A host presenting the two counts
|
|
213
|
+
* side by side has to say which is which.
|
|
214
|
+
*/
|
|
215
|
+
/* ── The alphabets and the grammars ─────────────────────────────────────── */
|
|
216
|
+
/**
|
|
217
|
+
* Every UML role that is an ELEMENT of the model — the alphabet a role-less
|
|
218
|
+
* connector is read against, and nothing more.
|
|
219
|
+
*
|
|
220
|
+
* `uml:classifier` stands for its three children by declaration, so the class,
|
|
221
|
+
* the interface and the enumeration arrive through `roleIsA` rather than as
|
|
222
|
+
* three entries. The two FRAMES are absent and meant to be: a connector dragged
|
|
223
|
+
* onto the sheet or onto a subject is somebody pointing at something, and
|
|
224
|
+
* pointing at things is what a whiteboard is for. The three TIER roles are
|
|
225
|
+
* absent for the same reason — a line drawn onto an attributes compartment is an
|
|
226
|
+
* annotation.
|
|
227
|
+
*
|
|
228
|
+
* ## Phase 2 appended six, and the two frames are still absent
|
|
229
|
+
*
|
|
230
|
+
* The component, its port, the two interface glyphs, the artefact and the node
|
|
231
|
+
* (parent of the device and the execution environment) joined the day the
|
|
232
|
+
* structural sheets did. They had to: a wiring gesture between two ports and a
|
|
233
|
+
* quick-connect between two cubes are exactly the drawings §11.6.4 and §19.4.4
|
|
234
|
+
* expect to be TYPED, and a role-less line between them says nothing to the
|
|
235
|
+
* grammar, nothing to the degree counts and nothing to either exporter — which
|
|
236
|
+
* is the whole subject of {@link untypedEdge}.
|
|
237
|
+
*/
|
|
238
|
+
const UML_ELEMENT_ROLES = [
|
|
239
|
+
UML_ROLE.classifier,
|
|
240
|
+
UML_ROLE.object,
|
|
241
|
+
UML_ROLE.package,
|
|
242
|
+
UML_ROLE.note,
|
|
243
|
+
UML_ROLE.actor,
|
|
244
|
+
UML_ROLE['use-case'],
|
|
245
|
+
// Phase 2 — the structural vocabulary. `uml:node` stands for its two children.
|
|
246
|
+
UML_ROLE.component,
|
|
247
|
+
UML_ROLE.port,
|
|
248
|
+
UML_ROLE['provided-interface'],
|
|
249
|
+
UML_ROLE['required-interface'],
|
|
250
|
+
UML_ROLE.artifact,
|
|
251
|
+
UML_ROLE.node,
|
|
252
|
+
// Phase 2 — the BEHAVIOUR vocabulary, and the same argument a third time: a
|
|
253
|
+
// quick-connect between two actions, or between two states, is the drawing
|
|
254
|
+
// §15.2.4 and §14.2.4 expect to be a control flow and a transition. A line
|
|
255
|
+
// carrying no role between them says nothing to either grammar, nothing to the
|
|
256
|
+
// degree counts, nothing to the two reachability walks and nothing to either
|
|
257
|
+
// exporter. `uml:control-node` stands for its five children and
|
|
258
|
+
// `uml:pseudostate` for its seven.
|
|
259
|
+
UML_ROLE.action,
|
|
260
|
+
UML_ROLE['control-node'],
|
|
261
|
+
UML_ROLE['object-node'],
|
|
262
|
+
UML_ROLE['send-signal'],
|
|
263
|
+
UML_ROLE['accept-event'],
|
|
264
|
+
UML_ROLE['time-event'],
|
|
265
|
+
UML_ROLE.state,
|
|
266
|
+
UML_ROLE['final-state'],
|
|
267
|
+
UML_ROLE.pseudostate,
|
|
268
|
+
];
|
|
269
|
+
/**
|
|
270
|
+
* Every ordered pair of the element roles — the ALPHABET, and not a grammar.
|
|
271
|
+
*
|
|
272
|
+
* This table sanctions all four hundred and forty-one sentences — twenty-one
|
|
273
|
+
* element roles squared, since the behaviour vocabulary joined the alphabet —
|
|
274
|
+
* so a rule holding it judges none of them. It exists so {@link untypedEdge}
|
|
275
|
+
* can say "between two
|
|
276
|
+
* UML artefacts" — which is the only thing `flagNeutral` reads a matrix FOR —
|
|
277
|
+
* without inheriting a judgement that belongs to {@link actorActorAssociation}.
|
|
278
|
+
*/
|
|
279
|
+
export const UML_ELEMENT_MATRIX = UML_ELEMENT_ROLES.flatMap(source => UML_ELEMENT_ROLES.map(target => ({
|
|
280
|
+
source,
|
|
281
|
+
edge: UML_ROLE.association,
|
|
282
|
+
target,
|
|
283
|
+
})));
|
|
284
|
+
/**
|
|
285
|
+
* What an ASSOCIATION may run between: anything the notation associates, except
|
|
286
|
+
* two actors.
|
|
287
|
+
*
|
|
288
|
+
* §18.1.3 is explicit — an Actor may have Associations with UseCases, Components
|
|
289
|
+
* and Classes, and with nothing else — so two stick figures joined by a solid
|
|
290
|
+
* line is the one removal, exactly as person → person is C4's. Everything else
|
|
291
|
+
* in the alphabet is sanctioned deliberately: a classifier associates with a
|
|
292
|
+
* classifier (§11.5.4), two instances are joined by a LINK drawn the same way
|
|
293
|
+
* (§9.8.4), and an actor associates with a use case.
|
|
294
|
+
*
|
|
295
|
+
* The package and the note are outside the alphabet, so an association drawn
|
|
296
|
+
* onto one is not evaluated. That is the family's proportionality requirement
|
|
297
|
+
* doing its job: a line to a note is an anchor somebody drew with the wrong
|
|
298
|
+
* tool, which {@link untypedEdge} is the rule for.
|
|
299
|
+
*
|
|
300
|
+
* Exported so a test asserts THIS table rather than a copy of it.
|
|
301
|
+
*/
|
|
302
|
+
const UML_ASSOCIATION_ROLES = [
|
|
303
|
+
UML_ROLE.classifier,
|
|
304
|
+
UML_ROLE.object,
|
|
305
|
+
UML_ROLE.actor,
|
|
306
|
+
UML_ROLE['use-case'],
|
|
307
|
+
];
|
|
308
|
+
export const UML_ASSOCIATION_MATRIX = UML_ASSOCIATION_ROLES.flatMap(source => UML_ASSOCIATION_ROLES.map(target => ({
|
|
309
|
+
source,
|
|
310
|
+
edge: UML_ROLE.association,
|
|
311
|
+
target,
|
|
312
|
+
}))).filter(triplet => !(triplet.source === UML_ROLE.actor && triplet.target === UML_ROLE.actor));
|
|
313
|
+
/**
|
|
314
|
+
* The roles a GENERALIZATION may be drawn between — the alphabet of both
|
|
315
|
+
* generalization rules, and the reason the instance is in it.
|
|
316
|
+
*
|
|
317
|
+
* §9.9.7: "the general classifier must be of the same kind as the specific
|
|
318
|
+
* classifier", which gives the four diagonal sentences of
|
|
319
|
+
* {@link UML_GENERALIZATION_MATRIX}. `uml:object` is here and is sanctioned by
|
|
320
|
+
* nothing: an instance specialises nothing and is specialised by nothing (§9.8),
|
|
321
|
+
* and a hollow triangle pointing at one is a generalization drawn onto the
|
|
322
|
+
* picture of a runtime value.
|
|
323
|
+
*/
|
|
324
|
+
const UML_GENERALIZATION_ROLES = [
|
|
325
|
+
UML_ROLE.class,
|
|
326
|
+
UML_ROLE.interface,
|
|
327
|
+
UML_ROLE.actor,
|
|
328
|
+
UML_ROLE['use-case'],
|
|
329
|
+
UML_ROLE.object,
|
|
330
|
+
];
|
|
331
|
+
/**
|
|
332
|
+
* The permissive table — every ordered pair of {@link UML_GENERALIZATION_ROLES}.
|
|
333
|
+
*
|
|
334
|
+
* Held by {@link generalizationSelfLoop} alone, for the reason C4 states: a
|
|
335
|
+
* second rule carrying the real grammar would report every wrong sentence a
|
|
336
|
+
* second time, with two brackets and two suggestions for one gesture to fix. The
|
|
337
|
+
* loop rule needs the same ALPHABET (so a generalization looped onto a note or a
|
|
338
|
+
* frame stays outside the conversation) and no grammar at all.
|
|
339
|
+
*/
|
|
340
|
+
export const UML_GENERALIZATION_ALPHABET = UML_GENERALIZATION_ROLES.flatMap(source => UML_GENERALIZATION_ROLES.map(target => ({
|
|
341
|
+
source,
|
|
342
|
+
edge: UML_ROLE.generalization,
|
|
343
|
+
target,
|
|
344
|
+
})));
|
|
345
|
+
/**
|
|
346
|
+
* The generalization GRAMMAR: like specialises like, and an instance is not a
|
|
347
|
+
* kind of anything.
|
|
348
|
+
*
|
|
349
|
+
* Four diagonal sentences and one ALPHABET entry. The fifth triplet is a link
|
|
350
|
+
* between two instances (§9.8.4) — a true sentence, carrying the ASSOCIATION
|
|
351
|
+
* role — and it is here so that `uml:object` is a role this rule speaks about:
|
|
352
|
+
* without it a generalization drawn onto an instance would be outside the
|
|
353
|
+
* alphabet and silently unjudged. `inMatrix` matches the edge role with
|
|
354
|
+
* `roleIsA`, so it can never sanction a generalization.
|
|
355
|
+
*
|
|
356
|
+
* The enumeration is absent from both ends, and that is a silence rather than a
|
|
357
|
+
* permission: a generalization between two enumerations is legal in the
|
|
358
|
+
* metamodel (an Enumeration is a DataType) and vanishingly rare on a drawing, so
|
|
359
|
+
* the pack declines to have an opinion about it.
|
|
360
|
+
*/
|
|
361
|
+
export const UML_GENERALIZATION_MATRIX = [
|
|
362
|
+
{
|
|
363
|
+
source: UML_ROLE.class,
|
|
364
|
+
edge: UML_ROLE.generalization,
|
|
365
|
+
target: UML_ROLE.class,
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
source: UML_ROLE.interface,
|
|
369
|
+
edge: UML_ROLE.generalization,
|
|
370
|
+
target: UML_ROLE.interface,
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
source: UML_ROLE.actor,
|
|
374
|
+
edge: UML_ROLE.generalization,
|
|
375
|
+
target: UML_ROLE.actor,
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
source: UML_ROLE['use-case'],
|
|
379
|
+
edge: UML_ROLE.generalization,
|
|
380
|
+
target: UML_ROLE['use-case'],
|
|
381
|
+
},
|
|
382
|
+
// The ALPHABET entry — see the header. A link between two instances, on the
|
|
383
|
+
// association role, so it sanctions no generalization whatever.
|
|
384
|
+
{
|
|
385
|
+
source: UML_ROLE.object,
|
|
386
|
+
edge: UML_ROLE.association,
|
|
387
|
+
target: UML_ROLE.object,
|
|
388
|
+
},
|
|
389
|
+
];
|
|
390
|
+
/**
|
|
391
|
+
* What a REALIZATION points at: an interface, and nothing else (§10.4.3, whose
|
|
392
|
+
* `contract` end is typed `Interface`).
|
|
393
|
+
*
|
|
394
|
+
* One sentence, and an alphabet of one role — `uml:interface` specialises
|
|
395
|
+
* `uml:classifier`, so the source entry covers both ends of every realization
|
|
396
|
+
* drawn between two compartmented boxes. A realization onto a package, an
|
|
397
|
+
* object, an actor or a use case is outside the alphabet and is not judged:
|
|
398
|
+
* those are drawings of something else entirely, and the notation has no reading
|
|
399
|
+
* of them to correct.
|
|
400
|
+
*/
|
|
401
|
+
export const UML_REALIZATION_MATRIX = [
|
|
402
|
+
{
|
|
403
|
+
source: UML_ROLE.classifier,
|
|
404
|
+
edge: UML_ROLE.realization,
|
|
405
|
+
target: UML_ROLE.interface,
|
|
406
|
+
},
|
|
407
|
+
];
|
|
408
|
+
/**
|
|
409
|
+
* What a DEPENDENCY may run between: the model's named elements, and never TO an
|
|
410
|
+
* instance.
|
|
411
|
+
*
|
|
412
|
+
* A dependency is a statement about the model — this classifier needs that one,
|
|
413
|
+
* this package needs that one (§7.8.4) — and an INSTANCE is not part of the
|
|
414
|
+
* model but an illustration of it (§9.8.3). An arrow pointing at `o1 : Order` is
|
|
415
|
+
* almost always an arrow meant for `Order`.
|
|
416
|
+
*
|
|
417
|
+
* The object is therefore a legal SOURCE and never a legal TARGET, which is the
|
|
418
|
+
* whole content of the table: three sources, two targets, six sentences, and
|
|
419
|
+
* `uml:object` in the alphabet so the seventh is a finding.
|
|
420
|
+
*/
|
|
421
|
+
const UML_DEPENDENCY_SOURCES = [
|
|
422
|
+
UML_ROLE.classifier,
|
|
423
|
+
UML_ROLE.package,
|
|
424
|
+
UML_ROLE.object,
|
|
425
|
+
];
|
|
426
|
+
const UML_DEPENDENCY_TARGETS = [
|
|
427
|
+
UML_ROLE.classifier,
|
|
428
|
+
UML_ROLE.package,
|
|
429
|
+
];
|
|
430
|
+
export const UML_DEPENDENCY_MATRIX = UML_DEPENDENCY_SOURCES.flatMap(source => UML_DEPENDENCY_TARGETS.map(target => ({
|
|
431
|
+
source,
|
|
432
|
+
edge: UML_ROLE.dependency,
|
|
433
|
+
target,
|
|
434
|
+
})));
|
|
435
|
+
/**
|
|
436
|
+
* The use case diagram's grammar, whole — read by TWO rules, each seeing the
|
|
437
|
+
* slice its own edge role selects.
|
|
438
|
+
*
|
|
439
|
+
* §18.1.3 types both ends of an Include (`addition: UseCase`) and both ends of
|
|
440
|
+
* an Extend (`extendedCase: UseCase`), so the only sentence either sanctions is
|
|
441
|
+
* use case → use case. That alone would give each rule an alphabet of one role
|
|
442
|
+
* and nothing it could ever report — an include drawn from an ACTOR, which is
|
|
443
|
+
* the mistake these two exist for, would be outside the alphabet and silent.
|
|
444
|
+
*
|
|
445
|
+
* The two ASSOCIATION triplets are what fix that. They are true sentences of
|
|
446
|
+
* §18.1.3 (an actor associates with a use case) and they carry the association
|
|
447
|
+
* role, so `inMatrix` — which matches a triplet's edge with `roleIsA` — can never
|
|
448
|
+
* let them sanction an include or an extend. Their only effect is to put
|
|
449
|
+
* `uml:actor` in the alphabet both rules speak.
|
|
450
|
+
*
|
|
451
|
+
* The two rules never double-report: an edge carries one role, and the include
|
|
452
|
+
* rule reads `uml:include` while the extend rule reads `uml:extend` — flat
|
|
453
|
+
* siblings, so neither reaches the other's edges, and neither reaches the
|
|
454
|
+
* associations this table also describes.
|
|
455
|
+
*/
|
|
456
|
+
export const UML_USE_CASE_MATRIX = [
|
|
457
|
+
{
|
|
458
|
+
source: UML_ROLE['use-case'],
|
|
459
|
+
edge: UML_ROLE.include,
|
|
460
|
+
target: UML_ROLE['use-case'],
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
source: UML_ROLE['use-case'],
|
|
464
|
+
edge: UML_ROLE.extend,
|
|
465
|
+
target: UML_ROLE['use-case'],
|
|
466
|
+
},
|
|
467
|
+
// The ALPHABET entries — see the header.
|
|
468
|
+
{
|
|
469
|
+
source: UML_ROLE.actor,
|
|
470
|
+
edge: UML_ROLE.association,
|
|
471
|
+
target: UML_ROLE['use-case'],
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
source: UML_ROLE['use-case'],
|
|
475
|
+
edge: UML_ROLE.association,
|
|
476
|
+
target: UML_ROLE.actor,
|
|
477
|
+
},
|
|
478
|
+
];
|
|
479
|
+
/**
|
|
480
|
+
* What a DEPLOY may run between: an artefact onto a node, and nothing else.
|
|
481
|
+
*
|
|
482
|
+
* §19.4.4 draws it as the dashed arrow with the `«deploy»` keyword, and §19.2.3
|
|
483
|
+
* types its two ends — a DeployedArtifact onto a DeploymentTarget. `uml:node` is
|
|
484
|
+
* the parent of `uml:device` and `uml:execution-environment` (`roles.ts`), so the
|
|
485
|
+
* single sentence below reaches all three targets through `roleIsA` rather than
|
|
486
|
+
* being restated once per cube.
|
|
487
|
+
*
|
|
488
|
+
* The second triplet is an ALPHABET entry, the device the file header explains:
|
|
489
|
+
* a manifestation is a true sentence of §19.3.4, it carries the `uml:manifest`
|
|
490
|
+
* role and can therefore never sanction a deploy, and its whole effect is to put
|
|
491
|
+
* `uml:component` into the alphabet this rule speaks. Without it a `«deploy»`
|
|
492
|
+
* drawn from a component — which is the mistake this rule exists for, since a
|
|
493
|
+
* component is not what gets deployed, its artefact is — would be outside the
|
|
494
|
+
* alphabet and silently unjudged.
|
|
495
|
+
*/
|
|
496
|
+
export const UML_DEPLOY_MATRIX = [
|
|
497
|
+
{
|
|
498
|
+
source: UML_ROLE.artifact,
|
|
499
|
+
edge: UML_ROLE.deploy,
|
|
500
|
+
// The PARENT role: a node, a device and an execution environment alike.
|
|
501
|
+
target: UML_ROLE.node,
|
|
502
|
+
},
|
|
503
|
+
// The ALPHABET entry — see the header.
|
|
504
|
+
{
|
|
505
|
+
source: UML_ROLE.artifact,
|
|
506
|
+
edge: UML_ROLE.manifest,
|
|
507
|
+
target: UML_ROLE.component,
|
|
508
|
+
},
|
|
509
|
+
];
|
|
510
|
+
/**
|
|
511
|
+
* What a MANIFEST may run between: an artefact onto the element it embodies.
|
|
512
|
+
*
|
|
513
|
+
* §19.3.3 — "An Artifact may embody, or manifest, a number of model elements" —
|
|
514
|
+
* and §19.3.4 draws it as the dashed open arrow labelled `«manifest»`. The
|
|
515
|
+
* metamodel's `utilizedElement` is a PackageableElement, so a manifestation may
|
|
516
|
+
* point at a great many things; this pack draws the one §19.3.5's own figure
|
|
517
|
+
* draws, "A Manifestation relationship between an Artifact and a Component",
|
|
518
|
+
* because a component diagram is where a manifestation is drawn at all.
|
|
519
|
+
*
|
|
520
|
+
* {@link UML_DEPLOY_MATRIX}'s twin, mirrored: the foreign triplet here is the
|
|
521
|
+
* DEPLOY, which puts `uml:node` in the alphabet so that a `«manifest»` aimed at
|
|
522
|
+
* a cube — the same arrow drawn at the wrong end of the deployment story — is a
|
|
523
|
+
* finding rather than silence.
|
|
524
|
+
*/
|
|
525
|
+
export const UML_MANIFEST_MATRIX = [
|
|
526
|
+
{
|
|
527
|
+
source: UML_ROLE.artifact,
|
|
528
|
+
edge: UML_ROLE.manifest,
|
|
529
|
+
target: UML_ROLE.component,
|
|
530
|
+
},
|
|
531
|
+
// The ALPHABET entry — see the header.
|
|
532
|
+
{
|
|
533
|
+
source: UML_ROLE.artifact,
|
|
534
|
+
edge: UML_ROLE.deploy,
|
|
535
|
+
target: UML_ROLE.node,
|
|
536
|
+
},
|
|
537
|
+
];
|
|
538
|
+
/**
|
|
539
|
+
* What a COMMUNICATION PATH may run between: two nodes.
|
|
540
|
+
*
|
|
541
|
+
* §19.4.3 is as narrow as a clause gets — "A CommunicationPath is an Association
|
|
542
|
+
* between two DeploymentTargets, through which they may exchange Signals and
|
|
543
|
+
* Messages" — and §19.4.4 draws it as the plain association link between two
|
|
544
|
+
* cubes. One sentence, and `uml:node` on both ends, so a device talking to an
|
|
545
|
+
* execution environment is sanctioned through the parent role.
|
|
546
|
+
*
|
|
547
|
+
* TWO alphabet entries this time, and both are foreign edges: they put
|
|
548
|
+
* `uml:artifact` and `uml:component` into the alphabet, which is what makes a
|
|
549
|
+
* network line drawn onto a `.jar` or onto a component box a finding. A
|
|
550
|
+
* communication path is about MACHINES talking to machines, and the wire drawn
|
|
551
|
+
* to the software running on one of them is the confusion the rule is for.
|
|
552
|
+
*/
|
|
553
|
+
export const UML_COMMUNICATION_PATH_MATRIX = [
|
|
554
|
+
{
|
|
555
|
+
source: UML_ROLE.node,
|
|
556
|
+
edge: UML_ROLE['communication-path'],
|
|
557
|
+
target: UML_ROLE.node,
|
|
558
|
+
},
|
|
559
|
+
// The ALPHABET entries — see the header.
|
|
560
|
+
{
|
|
561
|
+
source: UML_ROLE.artifact,
|
|
562
|
+
edge: UML_ROLE.deploy,
|
|
563
|
+
target: UML_ROLE.node,
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
source: UML_ROLE.artifact,
|
|
567
|
+
edge: UML_ROLE.manifest,
|
|
568
|
+
target: UML_ROLE.component,
|
|
569
|
+
},
|
|
570
|
+
];
|
|
571
|
+
/* ── Membership: is the drawing on the sheet it claims to be on? ────────── */
|
|
572
|
+
/**
|
|
573
|
+
* **U1** — a classifier drawn beside the diagram frame.
|
|
574
|
+
*
|
|
575
|
+
* Annex A draws a UML diagram inside a frame carrying its kind and its name, and
|
|
576
|
+
* what is outside the frame is outside the diagram: a class parked on bare
|
|
577
|
+
* canvas belongs to no sheet, is written into no export (`export.ts` attributes
|
|
578
|
+
* by frame) and says nothing a reader can situate.
|
|
579
|
+
*
|
|
580
|
+
* Requiring it to be drawn INSIDE the rectangle is ours rather than the
|
|
581
|
+
* specification's — Annex A describes the frame, it does not forbid ink beside
|
|
582
|
+
* one — so the provenance says `labre-convention`, the same call C4's
|
|
583
|
+
* `homeless-component` makes.
|
|
584
|
+
*
|
|
585
|
+
* ## Written on the CLASSIFIERS, and the limit that follows
|
|
586
|
+
*
|
|
587
|
+
* `element-in-background` names one subject role and UML declares no ancestor
|
|
588
|
+
* meaning "any artefact" (the file header says why at length). So this rule
|
|
589
|
+
* reaches a class, an interface and an enumeration — what a class diagram is made
|
|
590
|
+
* of — and an object, a package, a note, an actor or a use case parked outside
|
|
591
|
+
* the frame raises nothing. A narrower rule that is right is worth more than a
|
|
592
|
+
* wider one that guesses.
|
|
593
|
+
*
|
|
594
|
+
* Phase 2 widened `appliesTo` by nothing, and that is the same limit rather than
|
|
595
|
+
* an oversight: a component, a port, an interface glyph, an artefact and a node
|
|
596
|
+
* have no common ancestor with the three classifiers that does not ALSO reach
|
|
597
|
+
* the frames and the tiers, and inventing one — `uml:artefact`, say — would be
|
|
598
|
+
* this pack adding a word to a vocabulary whose whole authority is that every
|
|
599
|
+
* role in it is the specification's. Six artefacts parked beside the sheet
|
|
600
|
+
* raised nothing before; twelve do now.
|
|
601
|
+
*
|
|
602
|
+
* ## Silence on a frameless sketch
|
|
603
|
+
*
|
|
604
|
+
* A board with no `uml:diagram` on it has no frame for this question to be about,
|
|
605
|
+
* and the family answers that with silence — which covers every diagram drawn
|
|
606
|
+
* before the frame existed, and every croquis somebody is still deciding the
|
|
607
|
+
* shape of.
|
|
608
|
+
*/
|
|
609
|
+
const elementOutsideFrame = {
|
|
610
|
+
id: 'uml.element-outside-frame',
|
|
611
|
+
framework: 'uml',
|
|
612
|
+
family: 'element-in-background',
|
|
613
|
+
severity: 'audit',
|
|
614
|
+
appliesTo: UML_ROLE.classifier,
|
|
615
|
+
roles: UML_ROLES,
|
|
616
|
+
messageKey: 'com.labre.uml.validation.element-outside-frame',
|
|
617
|
+
messageFallback: 'This classifier sits outside the diagram frame.',
|
|
618
|
+
suggestionKey: 'com.labre.uml.validation.element-outside-frame.suggestion',
|
|
619
|
+
suggestionFallback: 'A UML diagram is what the frame encloses: its heading says which kind of diagram this is, and the export reads the artefacts inside it. Move the box onto the sheet, or stretch the frame round it.',
|
|
620
|
+
version: 1,
|
|
621
|
+
provenance: {
|
|
622
|
+
source: 'labre-convention',
|
|
623
|
+
reference: 'Labre convention — Annex A draws the frame; requiring an artefact to be inside one is membership on this canvas',
|
|
624
|
+
},
|
|
625
|
+
// The frame, and the whole question. No `background` declaration:
|
|
626
|
+
// `element-in-background` measures against the element BOX and reads no
|
|
627
|
+
// margin, so a geometry declaration here would be data nothing reads.
|
|
628
|
+
backgroundRole: UML_ROLE.diagram,
|
|
629
|
+
};
|
|
630
|
+
/**
|
|
631
|
+
* The five SEQUENCE artefacts, as one entry for the eight deny-lists that all
|
|
632
|
+
* refuse the same five (§17.2.4, §17.3.4, §17.4.4, §17.6.4).
|
|
633
|
+
*
|
|
634
|
+
* Spread into each list rather than named eight times, and that is a departure
|
|
635
|
+
* from how the `act` and `stm` lists are written — they spell their strangers
|
|
636
|
+
* out one by one, on purpose, because each of them refuses a DIFFERENT subset of
|
|
637
|
+
* the other's routing glyphs and a shared constant would have hidden which. Here
|
|
638
|
+
* the subset is identical in all eight, and writing it once is what makes it
|
|
639
|
+
* impossible for a ninth sheet to be added with four of the five.
|
|
640
|
+
*
|
|
641
|
+
* ## `uml:message` is the first EDGE role this table names, and it belongs
|
|
642
|
+
*
|
|
643
|
+
* Every other entry in `forbidden` is a node or a frame, which is a gap in the
|
|
644
|
+
* eight lists rather than a reading of the family: `evaluateViewAdmissibility`
|
|
645
|
+
* judges any element carrying a role, and a connector carries one and has
|
|
646
|
+
* bounds. A message arrow is also the one edge in this vocabulary whose meaning
|
|
647
|
+
* is entirely about the sheet it is on — §17.4.4 reads it against a vertical
|
|
648
|
+
* axis of TIME — so a message drawn across a class diagram is not an edge of the
|
|
649
|
+
* wrong type between two boxes, it is an arrow whose whole notation is missing.
|
|
650
|
+
* The parent role reaches all five sorts in one entry.
|
|
651
|
+
*/
|
|
652
|
+
const UML_SEQUENCE_ROLES = [
|
|
653
|
+
UML_ROLE.lifeline,
|
|
654
|
+
UML_ROLE.execution,
|
|
655
|
+
UML_ROLE.destruction,
|
|
656
|
+
UML_ROLE.fragment,
|
|
657
|
+
UML_ROLE.message,
|
|
658
|
+
];
|
|
659
|
+
/**
|
|
660
|
+
* **U2** — an artefact drawn on a sheet whose own heading says it is a different
|
|
661
|
+
* diagram.
|
|
662
|
+
*
|
|
663
|
+
* The rule whose subject is the SHEET, and the one the diagram-kind picker
|
|
664
|
+
* exists for. A UML frame does not merely have a name: Annex A writes its
|
|
665
|
+
* heading as `<kind> <name>` — `class Orders`, `uc Checkout` — so every frame
|
|
666
|
+
* states which of the six diagrams it draws, and stating it is not optional the
|
|
667
|
+
* way a C4 board's level is (`kinds.ts` says why). The rule reads that statement
|
|
668
|
+
* back and confronts it with what has been drawn.
|
|
669
|
+
*
|
|
670
|
+
* Nine kinds since phase 3, and the table below grows by APPENDING a key: a
|
|
671
|
+
* `forbidden` entry is looked up by the frame's own `kind` string, so a kind with
|
|
672
|
+
* no entry is a sheet this rule has nothing to say about — which is exactly what
|
|
673
|
+
* a phase-1 build does with a `cmp` frame, and a phase-2 build with an `sd` one.
|
|
674
|
+
*
|
|
675
|
+
* ## The deny-lists, and the much longer list they do not name
|
|
676
|
+
*
|
|
677
|
+
* A **class** diagram refuses the use case vocabulary: actors, use cases and the
|
|
678
|
+
* subject rectangle are §18 and belong on a `uc` sheet. It admits classes,
|
|
679
|
+
* interfaces, enumerations, objects, packages and notes — a class diagram
|
|
680
|
+
* legitimately shows an instance beside the classifier it illustrates.
|
|
681
|
+
*
|
|
682
|
+
* A **package** diagram refuses that same vocabulary and the OBJECT besides:
|
|
683
|
+
* §12.2.4 draws packages, the classifiers they contain and the dependencies
|
|
684
|
+
* between them, and an instance on such a sheet is a level the diagram has not
|
|
685
|
+
* zoomed to.
|
|
686
|
+
*
|
|
687
|
+
* An **object** diagram refuses the use case vocabulary and the CLASSIFIERS:
|
|
688
|
+
* §9.8.4 draws instances and the links between them, which is the whole point of
|
|
689
|
+
* the sheet — a class drawn among them is the type, not one of its values.
|
|
690
|
+
*
|
|
691
|
+
* A **use case** diagram refuses the class-diagram vocabulary: classifiers,
|
|
692
|
+
* objects and packages. It admits actors, use cases, the subject and notes.
|
|
693
|
+
*
|
|
694
|
+
* Nothing else is named anywhere: notes, legend glyphs, sticky notes, free
|
|
695
|
+
* rectangles somebody thought with and every artefact of another framework are
|
|
696
|
+
* left alone, because a deny-list says only what the notation refuses (PRD
|
|
697
|
+
* principle 8).
|
|
698
|
+
*
|
|
699
|
+
* ## What it stays silent about
|
|
700
|
+
*
|
|
701
|
+
* An artefact drawn OUTSIDE the frame — the family judges a subject against the
|
|
702
|
+
* frame whose plot contains its centre, so a box beside the sheet is
|
|
703
|
+
* {@link elementOutsideFrame}'s business and not this rule's. And a frame
|
|
704
|
+
* carrying a kind this build has never heard of (a phase-2 value on a phase-1
|
|
705
|
+
* build, an import) is a level with no entry in the table, which the engine
|
|
706
|
+
* answers by walking nothing at all.
|
|
707
|
+
*/
|
|
708
|
+
const notAdmissibleOnKind = {
|
|
709
|
+
id: 'uml.not-admissible-on-kind',
|
|
710
|
+
framework: 'uml',
|
|
711
|
+
family: 'view-admissibility',
|
|
712
|
+
severity: 'audit',
|
|
713
|
+
// No `appliesTo`: the subjects are declared per KIND, in `admissibility`, and
|
|
714
|
+
// naming a single one here would be data that lies.
|
|
715
|
+
roles: UML_ROLES,
|
|
716
|
+
messageKey: 'com.labre.uml.validation.not-admissible-on-kind',
|
|
717
|
+
messageFallback: 'This artefact is not drawn on the kind of diagram the frame says this is.',
|
|
718
|
+
suggestionKey: 'com.labre.uml.validation.not-admissible-on-kind.suggestion',
|
|
719
|
+
suggestionFallback: 'The frame’s heading names the diagram — class, pkg, obj, uc, cmp, dep, act, stm or sd — and each draws its own vocabulary. Move the artefact to a sheet that draws it, or change the frame’s kind to the one it really shows.',
|
|
720
|
+
version: 1,
|
|
721
|
+
provenance: {
|
|
722
|
+
source: 'recommendation',
|
|
723
|
+
reference: 'OMG UML 2.5.1 — Annex A (diagram kinds and frame headings), §9.8.4 (object diagrams), §12.2.4 (package diagrams), §18.1.4 (use case diagrams), §17.2.4 (sequence diagrams)',
|
|
724
|
+
},
|
|
725
|
+
backgroundRole: UML_ROLE.diagram,
|
|
726
|
+
admissibility: {
|
|
727
|
+
// The prop the frame writes its kind in. The rule names it; the engine reads
|
|
728
|
+
// it; nothing in between knows the word "UML".
|
|
729
|
+
levelProp: 'kind',
|
|
730
|
+
forbidden: {
|
|
731
|
+
class: [
|
|
732
|
+
UML_ROLE.actor,
|
|
733
|
+
UML_ROLE['use-case'],
|
|
734
|
+
UML_ROLE.subject,
|
|
735
|
+
// Phase 3 — the SEQUENCE vocabulary, refused by every sheet that is not
|
|
736
|
+
// `sd`. A lifeline, an execution bar, a destruction cross, a combined
|
|
737
|
+
// fragment and a message arrow are §17's own notation, and the spine of
|
|
738
|
+
// a lifeline draws TIME running down the page — the one frame of
|
|
739
|
+
// reference the other eight sheets explicitly do not have
|
|
740
|
+
// (`background.ts`: "a UML diagram is a GRAPH"). So they are refused
|
|
741
|
+
// everywhere else, and the seven lists below name the same five.
|
|
742
|
+
...UML_SEQUENCE_ROLES,
|
|
743
|
+
],
|
|
744
|
+
pkg: [
|
|
745
|
+
UML_ROLE.actor,
|
|
746
|
+
UML_ROLE['use-case'],
|
|
747
|
+
UML_ROLE.subject,
|
|
748
|
+
UML_ROLE.object,
|
|
749
|
+
...UML_SEQUENCE_ROLES,
|
|
750
|
+
],
|
|
751
|
+
obj: [
|
|
752
|
+
UML_ROLE.actor,
|
|
753
|
+
UML_ROLE['use-case'],
|
|
754
|
+
UML_ROLE.subject,
|
|
755
|
+
// The PARENT role: a class, an interface and an enumeration alike.
|
|
756
|
+
UML_ROLE.classifier,
|
|
757
|
+
...UML_SEQUENCE_ROLES,
|
|
758
|
+
],
|
|
759
|
+
uc: [
|
|
760
|
+
UML_ROLE.classifier,
|
|
761
|
+
UML_ROLE.object,
|
|
762
|
+
UML_ROLE.package,
|
|
763
|
+
...UML_SEQUENCE_ROLES,
|
|
764
|
+
],
|
|
765
|
+
// Phase 2 — the two structural sheets.
|
|
766
|
+
//
|
|
767
|
+
// A **component** diagram (§11.6.4) refuses the use case vocabulary and
|
|
768
|
+
// the instance, for the reasons the class and package lists refuse them,
|
|
769
|
+
// and it refuses the DEPLOYMENT cubes besides: a node, a device and an
|
|
770
|
+
// execution environment are §19.4's answer to "where does this run", which
|
|
771
|
+
// is the question the `dep` sheet is drawn to ask. It admits the component
|
|
772
|
+
// itself, its ports, the two interface glyphs, an interface drawn as a
|
|
773
|
+
// full rectangle instead (§11.6.4 offers both), the packages that group
|
|
774
|
+
// them, the notes, and the ARTEFACTS that manifest a component — §11.6.5's
|
|
775
|
+
// own "white box" figure lists them in a compartment.
|
|
776
|
+
cmp: [
|
|
777
|
+
UML_ROLE.actor,
|
|
778
|
+
UML_ROLE['use-case'],
|
|
779
|
+
UML_ROLE.subject,
|
|
780
|
+
UML_ROLE.object,
|
|
781
|
+
// The PARENT role: a node, a device and an execution environment alike.
|
|
782
|
+
UML_ROLE.node,
|
|
783
|
+
...UML_SEQUENCE_ROLES,
|
|
784
|
+
],
|
|
785
|
+
// A **deployment** diagram (§19.2.4) refuses the use case vocabulary, the
|
|
786
|
+
// instance and the three CLASSIFIERS: §19 draws machines, the software
|
|
787
|
+
// environments on them, the artefacts deployed onto those and the
|
|
788
|
+
// components those artefacts manifest — a class, an interface or an
|
|
789
|
+
// enumeration on such a sheet is a level the diagram has not zoomed to.
|
|
790
|
+
//
|
|
791
|
+
// The three are named one by one where the `obj` list above names their
|
|
792
|
+
// parent, and that is deliberate rather than untidy. `uml:classifier` is
|
|
793
|
+
// the spec's own generalisation (§9.2) and the metamodel ALSO makes a
|
|
794
|
+
// Component (§11.6.2) and an Artifact (§19.3.2) Classifiers; `roles.ts`
|
|
795
|
+
// declares those two flat, for the reasons it gives, and this list must
|
|
796
|
+
// not depend on that call going one way — a deployment diagram that
|
|
797
|
+
// stopped admitting artefacts because the vocabulary was re-parented
|
|
798
|
+
// would be a rule broken at a distance, by an edit that never mentioned
|
|
799
|
+
// it. Naming the three compartmented boxes says what the sheet refuses.
|
|
800
|
+
dep: [
|
|
801
|
+
UML_ROLE.actor,
|
|
802
|
+
UML_ROLE['use-case'],
|
|
803
|
+
UML_ROLE.subject,
|
|
804
|
+
UML_ROLE.object,
|
|
805
|
+
UML_ROLE.class,
|
|
806
|
+
UML_ROLE.interface,
|
|
807
|
+
UML_ROLE.enumeration,
|
|
808
|
+
...UML_SEQUENCE_ROLES,
|
|
809
|
+
],
|
|
810
|
+
// Phase 2 — the two BEHAVIOUR sheets, and the first pair whose deny-lists
|
|
811
|
+
// are mostly about EACH OTHER.
|
|
812
|
+
//
|
|
813
|
+
// An **activity** (§15.2.4) refuses the use case vocabulary, the instance,
|
|
814
|
+
// the classifiers and the structural artefacts, for the reasons every list
|
|
815
|
+
// above refuses them — and it refuses the STATE MACHINE vocabulary, which
|
|
816
|
+
// is the half that earns its place: §14.2.4 and §15.3.4 draw a rounded
|
|
817
|
+
// rectangle each and a diamond each, the two sheets sit next to each other
|
|
818
|
+
// in one menu, and a state drawn among actions is the confusion this pair
|
|
819
|
+
// of diagrams produces every time. `uml:pseudostate` reaches the choice,
|
|
820
|
+
// the junction, the two histories, the two connection points and the
|
|
821
|
+
// terminate cross in one entry.
|
|
822
|
+
//
|
|
823
|
+
// What it ADMITS is the whole activity vocabulary, the swimlanes, the
|
|
824
|
+
// notes and the packages — and the initial disc and the fork bar, which
|
|
825
|
+
// are NOT refused on either sheet because one role carries both notations
|
|
826
|
+
// (`roles.ts`).
|
|
827
|
+
act: [
|
|
828
|
+
UML_ROLE.actor,
|
|
829
|
+
UML_ROLE['use-case'],
|
|
830
|
+
UML_ROLE.subject,
|
|
831
|
+
UML_ROLE.object,
|
|
832
|
+
// The PARENT role: a class, an interface and an enumeration alike.
|
|
833
|
+
UML_ROLE.classifier,
|
|
834
|
+
UML_ROLE.component,
|
|
835
|
+
UML_ROLE.port,
|
|
836
|
+
UML_ROLE['provided-interface'],
|
|
837
|
+
UML_ROLE['required-interface'],
|
|
838
|
+
UML_ROLE.artifact,
|
|
839
|
+
// The PARENT role: a node, a device and an execution environment alike.
|
|
840
|
+
UML_ROLE.node,
|
|
841
|
+
// The state machine's own vocabulary — see above.
|
|
842
|
+
UML_ROLE.state,
|
|
843
|
+
UML_ROLE['final-state'],
|
|
844
|
+
UML_ROLE.pseudostate,
|
|
845
|
+
UML_ROLE.region,
|
|
846
|
+
...UML_SEQUENCE_ROLES,
|
|
847
|
+
],
|
|
848
|
+
// A **state machine** (§14.2.4) refuses the same strangers and the
|
|
849
|
+
// ACTIVITY vocabulary, named glyph by glyph rather than through
|
|
850
|
+
// `uml:control-node`: that parent covers the initial disc and the fork bar
|
|
851
|
+
// as well, and both of those are §14.2.4's own notation. A deny-list
|
|
852
|
+
// written on the parent would refuse the two artefacts a state machine
|
|
853
|
+
// most needs — which is exactly the rule broken at a distance the `dep`
|
|
854
|
+
// list above spells its three classifiers out to avoid.
|
|
855
|
+
//
|
|
856
|
+
// The DECISION is refused and the CHOICE is not, and the pair is the
|
|
857
|
+
// reason this list exists at all: they are the same diamond, one per
|
|
858
|
+
// sheet, and swapping them is one click in the morph menu.
|
|
859
|
+
stm: [
|
|
860
|
+
UML_ROLE.actor,
|
|
861
|
+
UML_ROLE['use-case'],
|
|
862
|
+
UML_ROLE.subject,
|
|
863
|
+
UML_ROLE.object,
|
|
864
|
+
UML_ROLE.classifier,
|
|
865
|
+
UML_ROLE.component,
|
|
866
|
+
UML_ROLE.port,
|
|
867
|
+
UML_ROLE['provided-interface'],
|
|
868
|
+
UML_ROLE['required-interface'],
|
|
869
|
+
UML_ROLE.artifact,
|
|
870
|
+
UML_ROLE.node,
|
|
871
|
+
// The activity's own vocabulary, one by one — see above.
|
|
872
|
+
UML_ROLE.action,
|
|
873
|
+
UML_ROLE['activity-final'],
|
|
874
|
+
UML_ROLE['flow-final'],
|
|
875
|
+
UML_ROLE.decision,
|
|
876
|
+
UML_ROLE['object-node'],
|
|
877
|
+
UML_ROLE['send-signal'],
|
|
878
|
+
UML_ROLE['accept-event'],
|
|
879
|
+
UML_ROLE['time-event'],
|
|
880
|
+
UML_ROLE.partition,
|
|
881
|
+
...UML_SEQUENCE_ROLES,
|
|
882
|
+
],
|
|
883
|
+
// Phase 3 — the SEQUENCE sheet (§17.2.4), and the STRICTEST list in the
|
|
884
|
+
// table by some distance.
|
|
885
|
+
//
|
|
886
|
+
// Every other kind here refuses a neighbouring vocabulary and admits its
|
|
887
|
+
// own plus whatever a whiteboard is made of. An `sd` frame refuses all
|
|
888
|
+
// EIGHT of the others, and the reason is the sheet's frame of reference
|
|
889
|
+
// rather than its vocabulary: a sequence diagram's vertical axis is TIME
|
|
890
|
+
// (§17.4.4 — "every line fragment is either horizontal or downwards when
|
|
891
|
+
// traversed from send event to receive event"), and an artefact that is
|
|
892
|
+
// not a participant has no position on it. A class dropped between two
|
|
893
|
+
// lifelines is not a class at the wrong level of detail, the way an
|
|
894
|
+
// instance on a package diagram is; it is a box the reader cannot date.
|
|
895
|
+
//
|
|
896
|
+
// So the list is every role the other lists name, plus the two behaviour
|
|
897
|
+
// vocabularies, plus the PACKAGE — which `act` and `stm` admit and this
|
|
898
|
+
// sheet does not, for the reason above. What is left is what §17 draws:
|
|
899
|
+
// the lifeline, the execution bar, the destruction cross, the combined
|
|
900
|
+
// fragment and the five message arrows — and the NOTE, which every sheet
|
|
901
|
+
// in this table admits because Annex A draws one on all of them.
|
|
902
|
+
//
|
|
903
|
+
// `uml:control-node` and `uml:pseudostate` are named as PARENTS here
|
|
904
|
+
// where the `act` and `stm` lists spell their children out. Those two
|
|
905
|
+
// lists had to: each sheet draws half the other's routing glyphs, so a
|
|
906
|
+
// deny-list on a parent would have refused the notation the author came
|
|
907
|
+
// for. This sheet draws none of them, which makes the parent the honest
|
|
908
|
+
// entry — and keeps the list from growing every time a routing glyph is
|
|
909
|
+
// added to a diagram this one has nothing to do with.
|
|
910
|
+
sd: [
|
|
911
|
+
UML_ROLE.actor,
|
|
912
|
+
UML_ROLE['use-case'],
|
|
913
|
+
UML_ROLE.subject,
|
|
914
|
+
UML_ROLE.object,
|
|
915
|
+
UML_ROLE.package,
|
|
916
|
+
// The PARENT role: a class, an interface and an enumeration alike.
|
|
917
|
+
UML_ROLE.classifier,
|
|
918
|
+
UML_ROLE.component,
|
|
919
|
+
UML_ROLE.port,
|
|
920
|
+
UML_ROLE['provided-interface'],
|
|
921
|
+
UML_ROLE['required-interface'],
|
|
922
|
+
UML_ROLE.artifact,
|
|
923
|
+
// The PARENT role: a node, a device and an execution environment alike.
|
|
924
|
+
UML_ROLE.node,
|
|
925
|
+
// The activity's vocabulary — the parent reaches the initial disc, the
|
|
926
|
+
// two finals, the decision and the fork bar in one entry.
|
|
927
|
+
UML_ROLE.action,
|
|
928
|
+
UML_ROLE['control-node'],
|
|
929
|
+
UML_ROLE['object-node'],
|
|
930
|
+
UML_ROLE['send-signal'],
|
|
931
|
+
UML_ROLE['accept-event'],
|
|
932
|
+
UML_ROLE['time-event'],
|
|
933
|
+
UML_ROLE.partition,
|
|
934
|
+
// …and the state machine's, the parent reaching the choice, the
|
|
935
|
+
// junction, the two histories, the two connection points and the
|
|
936
|
+
// terminate cross.
|
|
937
|
+
UML_ROLE.state,
|
|
938
|
+
UML_ROLE['final-state'],
|
|
939
|
+
UML_ROLE.pseudostate,
|
|
940
|
+
UML_ROLE.region,
|
|
941
|
+
],
|
|
942
|
+
},
|
|
943
|
+
},
|
|
944
|
+
};
|
|
945
|
+
/**
|
|
946
|
+
* **U3** — a use case drawn outside every subject.
|
|
947
|
+
*
|
|
948
|
+
* §18.1.4 draws the SUBJECT as a rectangle round the use cases a system offers,
|
|
949
|
+
* with the actors outside it: the rectangle is what says "these are the things
|
|
950
|
+
* this system does", and a use case beside it is a behaviour attributed to
|
|
951
|
+
* nothing.
|
|
952
|
+
*
|
|
953
|
+
* ## `element-in-background`, which is the family that fits
|
|
954
|
+
*
|
|
955
|
+
* The brief asked for "outside every subject while one exists", and that is
|
|
956
|
+
* precisely what this family answers: a subject on the board is the frame, a use
|
|
957
|
+
* case not contained by ANY of them is the finding, and a board with no subject
|
|
958
|
+
* at all is silence. No polarity has to be turned round — the question is already
|
|
959
|
+
* the positive one — so the `element-in-zone` arrangement {@link actorInsideSubject}
|
|
960
|
+
* needs is not needed here.
|
|
961
|
+
*
|
|
962
|
+
* The silence is the half worth stating: a use case diagram whose author has not
|
|
963
|
+
* drawn the subject yet is a sketch, and most of them never draw one at all. The
|
|
964
|
+
* rule speaks only once somebody has declared where the system's boundary is.
|
|
965
|
+
*/
|
|
966
|
+
const useCaseOutsideSubject = {
|
|
967
|
+
id: 'uml.use-case-outside-subject',
|
|
968
|
+
framework: 'uml',
|
|
969
|
+
family: 'element-in-background',
|
|
970
|
+
severity: 'audit',
|
|
971
|
+
appliesTo: UML_ROLE['use-case'],
|
|
972
|
+
roles: UML_ROLES,
|
|
973
|
+
messageKey: 'com.labre.uml.validation.use-case-outside-subject',
|
|
974
|
+
messageFallback: 'This use case sits outside the subject.',
|
|
975
|
+
suggestionKey: 'com.labre.uml.validation.use-case-outside-subject.suggestion',
|
|
976
|
+
suggestionFallback: 'The subject rectangle is what says which system offers these use cases. Move the ellipse inside the one it belongs to, or stretch the subject round it — the actors stay outside.',
|
|
977
|
+
version: 1,
|
|
978
|
+
provenance: {
|
|
979
|
+
source: 'recommendation',
|
|
980
|
+
reference: 'OMG UML 2.5.1 §18.1.4 — the subject rectangle encloses the UseCases the system offers, with the Actors outside it',
|
|
981
|
+
},
|
|
982
|
+
backgroundRole: UML_ROLE.subject,
|
|
983
|
+
};
|
|
984
|
+
/**
|
|
985
|
+
* **U4** — an actor drawn inside the subject.
|
|
986
|
+
*
|
|
987
|
+
* The other half of §18.1.4, and the one the notation is most explicit about: an
|
|
988
|
+
* actor is a role played by somebody OUTSIDE the system, and the subject
|
|
989
|
+
* rectangle is the system. A use case diagram whose stick figures are inside the
|
|
990
|
+
* box has erased the only line it had.
|
|
991
|
+
*
|
|
992
|
+
* ## The family is `element-in-zone`, and why
|
|
993
|
+
*
|
|
994
|
+
* The question is "is this element OUTSIDE that frame", and
|
|
995
|
+
* `element-in-background` cannot ask it: it has one polarity, INSIDE, with no
|
|
996
|
+
* `expect` to turn round. `element-in-zone` has the polarity, and the subject
|
|
997
|
+
* declares exactly one zone covering its whole plot (`background.ts` — it carries
|
|
998
|
+
* the subject's name and paints nothing), so citing that zone is citing the
|
|
999
|
+
* inside of the rectangle. Nothing is restated: the geometry comes from the very
|
|
1000
|
+
* declaration the renderer paints from, so a subject that is moved, resized or
|
|
1001
|
+
* re-margined moves this rule with it. C4's `person-in-boundary` is the same
|
|
1002
|
+
* arrangement for the same reason, one framework over.
|
|
1003
|
+
*
|
|
1004
|
+
* `element-in-zone` judges a subject only against the frame that CONTAINS it, so
|
|
1005
|
+
* an actor straddling the edge — half in, half out — raises nothing: only an
|
|
1006
|
+
* actor drawn wholly inside is an actor the author has put inside the system.
|
|
1007
|
+
*/
|
|
1008
|
+
const actorInsideSubject = {
|
|
1009
|
+
id: 'uml.actor-inside-subject',
|
|
1010
|
+
framework: 'uml',
|
|
1011
|
+
family: 'element-in-zone',
|
|
1012
|
+
severity: 'audit',
|
|
1013
|
+
appliesTo: UML_ROLE.actor,
|
|
1014
|
+
roles: UML_ROLES,
|
|
1015
|
+
messageKey: 'com.labre.uml.validation.actor-inside-subject',
|
|
1016
|
+
messageFallback: 'This actor is drawn inside the subject.',
|
|
1017
|
+
suggestionKey: 'com.labre.uml.validation.actor-inside-subject.suggestion',
|
|
1018
|
+
suggestionFallback: 'An actor is a role played from outside the system, and the subject rectangle is the system. Move the figure out and let the association cross the edge.',
|
|
1019
|
+
version: 1,
|
|
1020
|
+
provenance: {
|
|
1021
|
+
source: 'recommendation',
|
|
1022
|
+
reference: 'OMG UML 2.5.1 §18.1.3 — an Actor models a role played by an entity external to the subject',
|
|
1023
|
+
},
|
|
1024
|
+
backgroundRole: UML_ROLE.subject,
|
|
1025
|
+
// The frame's own declaration, carried as data exactly like `roles` is: it is
|
|
1026
|
+
// where the plot and its one zone are written, and the engine resolves the
|
|
1027
|
+
// rectangle from it rather than knowing anything about UML.
|
|
1028
|
+
background: UML_SUBJECT_FRAME,
|
|
1029
|
+
inZone: {
|
|
1030
|
+
// The subject's single full-plot zone — see the header. It exists to carry
|
|
1031
|
+
// the subject's name and paints nothing, so it IS the inside of the frame.
|
|
1032
|
+
zoneIds: ['name'],
|
|
1033
|
+
expect: 'outside',
|
|
1034
|
+
},
|
|
1035
|
+
};
|
|
1036
|
+
/* ── Naming: does the drawing say anything at all? ──────────────────────── */
|
|
1037
|
+
/**
|
|
1038
|
+
* **U5** — a name compartment somebody has emptied.
|
|
1039
|
+
*
|
|
1040
|
+
* Written on `uml:name`, the TEXT role, and never on the artefact: since a UML
|
|
1041
|
+
* node became a group the shape carries no words at all, so a rule written on
|
|
1042
|
+
* `uml:class` would read nothing on every box on the sheet and report every one
|
|
1043
|
+
* of them unnamed. The same move C4's `unnamed-element` makes, and for the same
|
|
1044
|
+
* reason.
|
|
1045
|
+
*
|
|
1046
|
+
* ## What it actually fires on, which is narrower than it sounds
|
|
1047
|
+
*
|
|
1048
|
+
* Every tier is SEEDED at creation (`actions.ts`: `«interface»\nInterface`,
|
|
1049
|
+
* `Package`, `+ attribute : Type`), so a fresh artefact is prompted rather than
|
|
1050
|
+
* nameless and `label-presence` sees words. The rule therefore fires on exactly
|
|
1051
|
+
* one thing: a name compartment an author has emptied.
|
|
1052
|
+
*
|
|
1053
|
+
* ## It reaches the package and the note too, and that is the honest reading
|
|
1054
|
+
*
|
|
1055
|
+
* `uml:name` is the name compartment of the classifiers AND of the object, and it
|
|
1056
|
+
* is also the package's single line and the note's body (`roles.ts` says why the
|
|
1057
|
+
* two share the role: a package name is a namespace and a note's text is what the
|
|
1058
|
+
* exporter writes as the comment's body — both are NAMES in the sense the
|
|
1059
|
+
* grammar means, where an actor's one word is a `uml:label`). So an emptied
|
|
1060
|
+
* package and an emptied note are findings here, under one sentence that fits
|
|
1061
|
+
* all four: the drawing has a box and no words.
|
|
1062
|
+
*
|
|
1063
|
+
* ## The known limit: a DELETED compartment is silence
|
|
1064
|
+
*
|
|
1065
|
+
* If the text element is removed outright rather than emptied there is no
|
|
1066
|
+
* `uml:name` on the sheet for this rule to be about. Closing it means asking
|
|
1067
|
+
* whether a GROUP has a name among its children, which is a question about
|
|
1068
|
+
* membership and not about a label's presence, and no family expresses it today.
|
|
1069
|
+
* `umlComponentOf` (`component.ts`) is where the answer would come from.
|
|
1070
|
+
*/
|
|
1071
|
+
const unnamedClassifier = {
|
|
1072
|
+
id: 'uml.unnamed-classifier',
|
|
1073
|
+
framework: 'uml',
|
|
1074
|
+
family: 'label-presence',
|
|
1075
|
+
severity: 'audit',
|
|
1076
|
+
appliesTo: UML_ROLE.name,
|
|
1077
|
+
roles: UML_ROLES,
|
|
1078
|
+
messageKey: 'com.labre.uml.validation.unnamed-classifier',
|
|
1079
|
+
messageFallback: 'This artefact has no name.',
|
|
1080
|
+
suggestionKey: 'com.labre.uml.validation.unnamed-classifier.suggestion',
|
|
1081
|
+
suggestionFallback: 'Write the name the rest of the model refers to it by — "Order", "«interface» Payable", "o1 : Order". The compartments below already say what it has and what it does, so the name is free to say which one it is.',
|
|
1082
|
+
version: 1,
|
|
1083
|
+
provenance: {
|
|
1084
|
+
source: 'recommendation',
|
|
1085
|
+
reference: 'OMG UML 2.5.1 §9.2.4 — a Classifier is drawn as a rectangle with its name in the top compartment',
|
|
1086
|
+
},
|
|
1087
|
+
// Explicitly on-demand, and it stays explicit: `moment: undefined` means
|
|
1088
|
+
// REALTIME, and the engine watches `text` for exactly this family when a
|
|
1089
|
+
// real-time rule of it is registered. Dropping this line would hand the
|
|
1090
|
+
// drawing path a debounced re-evaluation per keystroke, in every compartment.
|
|
1091
|
+
moment: 'on-demand',
|
|
1092
|
+
// Attribution only — the check reads no geometry — so an arbitration made on
|
|
1093
|
+
// one sheet covers that sheet and no other.
|
|
1094
|
+
backgroundRole: UML_ROLE.diagram,
|
|
1095
|
+
label: { present: true },
|
|
1096
|
+
};
|
|
1097
|
+
/**
|
|
1098
|
+
* **U6** — an actor's or a use case's one word, emptied.
|
|
1099
|
+
*
|
|
1100
|
+
* ONE rule for the two artefacts, and the brief asked for two ids. They cannot
|
|
1101
|
+
* be two rules, and the reason is in `roles.ts`: an actor and a use case carry
|
|
1102
|
+
* the SAME tier role, `uml:label` — "the same tier, named differently because it
|
|
1103
|
+
* is not a compartment" — so two `label-presence` rules would name the same
|
|
1104
|
+
* subject role, fire on the same emptied text and put two brackets and two
|
|
1105
|
+
* sentences on one word to fix, one of which would always be about the wrong
|
|
1106
|
+
* shape. `label-presence` reads the subject's own words and cannot see what the
|
|
1107
|
+
* text is grouped WITH.
|
|
1108
|
+
*
|
|
1109
|
+
* So the SUBJECT is the tier rather than either shape — `appliesTo` is
|
|
1110
|
+
* `UML_ROLE.label` and says so — the sentence fits both, and the day a family
|
|
1111
|
+
* can ask "which kind of artefact is this text grouped with" the rule splits in
|
|
1112
|
+
* two with no data to migrate.
|
|
1113
|
+
*
|
|
1114
|
+
* The ID does not say that, and it is stuck not saying it: `uml.unnamed-label`
|
|
1115
|
+
* is the name this rule should carry, and the id is frozen because documents
|
|
1116
|
+
* store it (see the "what this pack does NOT report" note above). The tier is
|
|
1117
|
+
* therefore stated here and in `appliesTo`, which is where a reader who needs
|
|
1118
|
+
* it will look.
|
|
1119
|
+
*/
|
|
1120
|
+
const unnamedActorOrUseCase = {
|
|
1121
|
+
id: 'uml.unnamed-actor-or-use-case',
|
|
1122
|
+
framework: 'uml',
|
|
1123
|
+
family: 'label-presence',
|
|
1124
|
+
severity: 'audit',
|
|
1125
|
+
appliesTo: UML_ROLE.label,
|
|
1126
|
+
roles: UML_ROLES,
|
|
1127
|
+
messageKey: 'com.labre.uml.validation.unnamed-actor-or-use-case',
|
|
1128
|
+
messageFallback: 'This actor or use case has no name.',
|
|
1129
|
+
suggestionKey: 'com.labre.uml.validation.unnamed-actor-or-use-case.suggestion',
|
|
1130
|
+
suggestionFallback: 'Name the role or the behaviour in the reader’s own words — "Customer", "Place an order". A use case diagram is read as sentences, and an unnamed ellipse leaves one of them half written.',
|
|
1131
|
+
version: 1,
|
|
1132
|
+
provenance: {
|
|
1133
|
+
source: 'recommendation',
|
|
1134
|
+
reference: 'OMG UML 2.5.1 §18.1.4 — a UseCase is drawn as an ellipse carrying its name, an Actor as a stick figure with its name beneath',
|
|
1135
|
+
},
|
|
1136
|
+
moment: 'on-demand',
|
|
1137
|
+
backgroundRole: UML_ROLE.diagram,
|
|
1138
|
+
label: { present: true },
|
|
1139
|
+
};
|
|
1140
|
+
/* ── Spelling: are the words written the way the clause writes them? ─────── */
|
|
1141
|
+
/**
|
|
1142
|
+
* **U35–U38** — the four `label-syntax` rules, and the first rules in this
|
|
1143
|
+
* library whose verdict is a PARSER rather than a table.
|
|
1144
|
+
*
|
|
1145
|
+
* `label-presence` asks whether a compartment says anything; these ask whether
|
|
1146
|
+
* what it says parses. The four clauses are the four the pack already reads for
|
|
1147
|
+
* its exporters — §9.5.4 (a Property), §9.6.4 (an Operation), §14.2.4.8 (a
|
|
1148
|
+
* transition label) and §7.5.4 (a multiplicity range) — and the checkers are
|
|
1149
|
+
* `grammar.ts`'s own, so a finding here is exactly the line that will export
|
|
1150
|
+
* short. That is the strongest claim any rule in this library makes: the tool
|
|
1151
|
+
* and the file agree by construction, because they read the same function.
|
|
1152
|
+
*
|
|
1153
|
+
* ## Why they are `standard`, and what keeps that honest
|
|
1154
|
+
*
|
|
1155
|
+
* Each restates a BNF the specification prints. What would make the claim
|
|
1156
|
+
* dishonest is a checker stricter than the clause — "an attribute must carry a
|
|
1157
|
+
* type" indicts `balance`, which §9.5.4 permits — so `grammar.ts` states the one
|
|
1158
|
+
* rule its checkers follow and no rule here is allowed to add to it: a line is
|
|
1159
|
+
* reported only when the lenient parse LOST something the author wrote.
|
|
1160
|
+
*
|
|
1161
|
+
* ## Why they are `on-demand`, explicitly
|
|
1162
|
+
*
|
|
1163
|
+
* The same reason {@link unnamedClassifier} is, one order of magnitude worse.
|
|
1164
|
+
* These read WORDS, which a user changes by typing, and they read all of them:
|
|
1165
|
+
* a class's attribute compartment is parsed line by line. Real-time, every
|
|
1166
|
+
* keystroke in every compartment on the sheet would wake the debounced pass and
|
|
1167
|
+
* re-run four parsers. `audit` already implies the moment (PF7.6); the line is
|
|
1168
|
+
* written anyway, because `moment: undefined` means REALTIME and a later change
|
|
1169
|
+
* of severity must not silently move these onto the gesture path.
|
|
1170
|
+
*/
|
|
1171
|
+
const attributeSyntax = {
|
|
1172
|
+
id: 'uml.attribute-syntax',
|
|
1173
|
+
framework: 'uml',
|
|
1174
|
+
family: 'label-syntax',
|
|
1175
|
+
severity: 'audit',
|
|
1176
|
+
appliesTo: UML_ROLE.attributes,
|
|
1177
|
+
roles: UML_ROLES,
|
|
1178
|
+
messageKey: 'com.labre.uml.validation.attribute-syntax',
|
|
1179
|
+
messageFallback: 'This line is not an attribute:',
|
|
1180
|
+
suggestionKey: 'com.labre.uml.validation.attribute-syntax.suggestion',
|
|
1181
|
+
suggestionFallback: 'An attribute is written "- balance : Money [0..1] = 0 {readOnly}", and everything after the name is optional. A line with parentheses is an operation and belongs in the compartment below.',
|
|
1182
|
+
version: 1,
|
|
1183
|
+
provenance: {
|
|
1184
|
+
source: 'standard',
|
|
1185
|
+
reference: 'OMG UML 2.5.1 §9.5.4 — [<visibility>] [/] <name> [: <prop-type>] [[<multiplicity>]] [= <default>] [{<modifiers>}]',
|
|
1186
|
+
},
|
|
1187
|
+
moment: 'on-demand',
|
|
1188
|
+
backgroundRole: UML_ROLE.diagram,
|
|
1189
|
+
labelSyntax: { parse: checkPropertyLine },
|
|
1190
|
+
};
|
|
1191
|
+
const operationSyntax = {
|
|
1192
|
+
id: 'uml.operation-syntax',
|
|
1193
|
+
framework: 'uml',
|
|
1194
|
+
family: 'label-syntax',
|
|
1195
|
+
severity: 'audit',
|
|
1196
|
+
appliesTo: UML_ROLE.operations,
|
|
1197
|
+
roles: UML_ROLES,
|
|
1198
|
+
messageKey: 'com.labre.uml.validation.operation-syntax',
|
|
1199
|
+
messageFallback: 'This line is not an operation:',
|
|
1200
|
+
suggestionKey: 'com.labre.uml.validation.operation-syntax.suggestion',
|
|
1201
|
+
suggestionFallback: 'An operation is written "+ place(order : Order) : Receipt", and the parentheses are what make it one — an operation with no parameters still has them.',
|
|
1202
|
+
version: 1,
|
|
1203
|
+
provenance: {
|
|
1204
|
+
source: 'standard',
|
|
1205
|
+
reference: 'OMG UML 2.5.1 §9.6.4 — [<visibility>] <name> ( [<parameter-list>] ) [: [<return-type>] [[<multiplicity>]] [{<oper-property>*}]]',
|
|
1206
|
+
},
|
|
1207
|
+
moment: 'on-demand',
|
|
1208
|
+
backgroundRole: UML_ROLE.diagram,
|
|
1209
|
+
labelSyntax: { parse: checkOperationLine },
|
|
1210
|
+
};
|
|
1211
|
+
/**
|
|
1212
|
+
* The transition's CENTRE label, read as ONE expression.
|
|
1213
|
+
*
|
|
1214
|
+
* `perLine: false`, unlike the two compartments above, and the clause is the
|
|
1215
|
+
* reason: §14.2.4.8's label is a single production — `trigger [guard] / effect` —
|
|
1216
|
+
* where §9.5.4's compartment is a list. An author who wraps a long label onto a
|
|
1217
|
+
* second line has written one label, and judging the halves separately would
|
|
1218
|
+
* report a guard with no trigger and a trigger with no guard for a label that is
|
|
1219
|
+
* neither.
|
|
1220
|
+
*/
|
|
1221
|
+
const transitionSyntax = {
|
|
1222
|
+
id: 'uml.transition-syntax',
|
|
1223
|
+
framework: 'uml',
|
|
1224
|
+
family: 'label-syntax',
|
|
1225
|
+
severity: 'audit',
|
|
1226
|
+
appliesTo: UML_ROLE.transition,
|
|
1227
|
+
roles: UML_ROLES,
|
|
1228
|
+
messageKey: 'com.labre.uml.validation.transition-syntax',
|
|
1229
|
+
messageFallback: 'This transition label does not parse:',
|
|
1230
|
+
suggestionKey: 'com.labre.uml.validation.transition-syntax.suggestion',
|
|
1231
|
+
suggestionFallback: 'A transition is labelled "trigger [guard] / effect", and every part of it is optional — a transition that fires when its source state finishes carries no label at all.',
|
|
1232
|
+
version: 1,
|
|
1233
|
+
provenance: {
|
|
1234
|
+
source: 'standard',
|
|
1235
|
+
reference: 'OMG UML 2.5.1 §14.2.4.8 — [<trigger> [, <trigger>]*] [[<guard>]] [/ <behavior-expression>]',
|
|
1236
|
+
},
|
|
1237
|
+
moment: 'on-demand',
|
|
1238
|
+
backgroundRole: UML_ROLE.diagram,
|
|
1239
|
+
labelSyntax: { parse: checkTransitionLabel, perLine: false },
|
|
1240
|
+
};
|
|
1241
|
+
/**
|
|
1242
|
+
* The multiplicity written at an association's ENDS — both of them, one rule.
|
|
1243
|
+
*
|
|
1244
|
+
* `target: 'end-labels'` reads the two end labels as two lines of one subject
|
|
1245
|
+
* (`validation.ts` {@link LabelTarget}), which is what lets one id cover a
|
|
1246
|
+
* grammar §11.5.4 places at both ends. Two rules would be two sentences and two
|
|
1247
|
+
* lines in every profile table for one requirement, and each could only ever
|
|
1248
|
+
* check half of every line drawn.
|
|
1249
|
+
*
|
|
1250
|
+
* `appliesTo: uml:association` reaches aggregation and composition for free —
|
|
1251
|
+
* they specialise it (`roles.ts`: §11.5.4's aggregation kind is a property of an
|
|
1252
|
+
* association END, not a different relationship).
|
|
1253
|
+
*
|
|
1254
|
+
* ## It is silent about the ROLE name, and that is the whole design
|
|
1255
|
+
*
|
|
1256
|
+
* An end label is a multiplicity, a role name, a visibility marker, or any
|
|
1257
|
+
* mixture of the three with no separator between them (§11.5.4). Only the range
|
|
1258
|
+
* has a syntax to be wrong about, so `checkEndLabelMultiplicity` fires on a
|
|
1259
|
+
* leading token that is unambiguously an attempt at one — `1..n`, `0...*` — and
|
|
1260
|
+
* leaves `items`, `- owner` and even `1st choice` alone. A checker stricter than
|
|
1261
|
+
* `parseEndLabel` would indict the ends the exporters read correctly.
|
|
1262
|
+
*/
|
|
1263
|
+
const multiplicitySyntax = {
|
|
1264
|
+
id: 'uml.multiplicity-syntax',
|
|
1265
|
+
framework: 'uml',
|
|
1266
|
+
family: 'label-syntax',
|
|
1267
|
+
severity: 'audit',
|
|
1268
|
+
appliesTo: UML_ROLE.association,
|
|
1269
|
+
roles: UML_ROLES,
|
|
1270
|
+
messageKey: 'com.labre.uml.validation.multiplicity-syntax',
|
|
1271
|
+
messageFallback: 'This end of the association is not a multiplicity:',
|
|
1272
|
+
suggestionKey: 'com.labre.uml.validation.multiplicity-syntax.suggestion',
|
|
1273
|
+
suggestionFallback: 'A range is written "1", "0..1", "0..*" or "*", with whole numbers at both bounds. A bound the file cannot hold — "1..n" — travels as part of the end’s name instead of as a range.',
|
|
1274
|
+
version: 1,
|
|
1275
|
+
provenance: {
|
|
1276
|
+
source: 'standard',
|
|
1277
|
+
reference: 'OMG UML 2.5.1 §7.5.4 — [<lower> ..] <upper>, read at an association end per §11.5.4',
|
|
1278
|
+
},
|
|
1279
|
+
moment: 'on-demand',
|
|
1280
|
+
backgroundRole: UML_ROLE.diagram,
|
|
1281
|
+
labelSyntax: {
|
|
1282
|
+
parse: checkEndLabelMultiplicity,
|
|
1283
|
+
target: 'end-labels',
|
|
1284
|
+
},
|
|
1285
|
+
};
|
|
1286
|
+
/* ── Grammar: what each line may run between ────────────────────────────── */
|
|
1287
|
+
/**
|
|
1288
|
+
* **U7** — a generalization whose two ends are not of the same kind.
|
|
1289
|
+
*
|
|
1290
|
+
* §9.9.7 is normative and short: the general classifier is of the same kind as
|
|
1291
|
+
* the specific one. A hollow triangle from a class to an interface is a
|
|
1292
|
+
* REALIZATION drawn with the wrong tool (`morph.ts` turns one into the other in a
|
|
1293
|
+
* click); one from a use case to an actor is two notations crossed; one pointing
|
|
1294
|
+
* at an instance is a generalization drawn onto a runtime value, which is why
|
|
1295
|
+
* `uml:object` is in this rule's alphabet and in none of its sentences.
|
|
1296
|
+
*
|
|
1297
|
+
* The four sanctioned sentences are the diagonal — class → class, interface →
|
|
1298
|
+
* interface, actor → actor, use case → use case — and everything else in the
|
|
1299
|
+
* alphabet is off it. See {@link UML_GENERALIZATION_MATRIX} for why the fifth
|
|
1300
|
+
* triplet carries a different edge role, and the file header for why that device
|
|
1301
|
+
* is what gives this rule anything to say at all.
|
|
1302
|
+
*
|
|
1303
|
+
* ## The self-loop is NOT here, and that is the point
|
|
1304
|
+
*
|
|
1305
|
+
* {@link generalizationSelfLoop} carries it, because the two clauses have
|
|
1306
|
+
* different AUTHORITY — the same split BPMN and C4 both make — and because the
|
|
1307
|
+
* family `continue`s on a loop before reaching any matrix, so exactly one of the
|
|
1308
|
+
* two can indict a given edge.
|
|
1309
|
+
*/
|
|
1310
|
+
const generalizationEndpoints = {
|
|
1311
|
+
id: 'uml.generalization-endpoints',
|
|
1312
|
+
framework: 'uml',
|
|
1313
|
+
family: 'relation-endpoints',
|
|
1314
|
+
severity: 'audit',
|
|
1315
|
+
// No `appliesTo`: the subject is a RELATION, and the role that names it is
|
|
1316
|
+
// declared where the family reads it.
|
|
1317
|
+
roles: UML_ROLES,
|
|
1318
|
+
messageKey: 'com.labre.uml.validation.generalization-endpoints',
|
|
1319
|
+
messageFallback: 'This generalization runs between two artefacts of different kinds.',
|
|
1320
|
+
suggestionKey: 'com.labre.uml.validation.generalization-endpoints.suggestion',
|
|
1321
|
+
suggestionFallback: 'A class specialises a class, an interface an interface, an actor an actor, a use case a use case. If what you mean is "this class implements that interface", draw a realization; if the far end is an instance, point at its classifier instead.',
|
|
1322
|
+
version: 1,
|
|
1323
|
+
provenance: {
|
|
1324
|
+
source: 'standard',
|
|
1325
|
+
reference: 'OMG UML 2.5.1 §9.9.7 — the general Classifier must be of the same kind as the specific Classifier',
|
|
1326
|
+
},
|
|
1327
|
+
backgroundRole: UML_ROLE.diagram,
|
|
1328
|
+
endpoints: {
|
|
1329
|
+
edgeRole: UML_ROLE.generalization,
|
|
1330
|
+
allowed: UML_GENERALIZATION_MATRIX,
|
|
1331
|
+
// No `forbidSelfLoop`: that clause is `uml.generalization-self-loop`, and its
|
|
1332
|
+
// absence here is what lets exactly one of the two indict a given loop.
|
|
1333
|
+
},
|
|
1334
|
+
};
|
|
1335
|
+
/**
|
|
1336
|
+
* **U8** — a generalization looped back onto the artefact it leaves.
|
|
1337
|
+
*
|
|
1338
|
+
* §9.9.7 again, and its other constraint: a generalization hierarchy is directed
|
|
1339
|
+
* and ACYCLIC, so a classifier cannot be a transitively general and a
|
|
1340
|
+
* transitively specific classifier of itself. The one-hop cycle is the case a
|
|
1341
|
+
* drawing can hold and a rule can see — the longer ones are a `reachability`
|
|
1342
|
+
* question this pack does not ask — and it is almost always a hand that dropped
|
|
1343
|
+
* the second end on the first.
|
|
1344
|
+
*
|
|
1345
|
+
* ## Why the two rules compose safely
|
|
1346
|
+
*
|
|
1347
|
+
* The ALPHABET GATE runs first, so a generalization looped onto a note, a package
|
|
1348
|
+
* or a frame is silence from both rules. Past the gate the family tests the
|
|
1349
|
+
* self-loop FIRST and `continue`s unconditionally — the `continue` sits outside
|
|
1350
|
+
* the `forbidSelfLoop` guard — so a loop never reaches a matrix at all. This rule
|
|
1351
|
+
* therefore declares the PERMISSIVE {@link UML_GENERALIZATION_ALPHABET} rather
|
|
1352
|
+
* than U7's grammar: carrying the grammar would report every wrong sentence a
|
|
1353
|
+
* second time on every non-loop generalization, which is exactly the trap C4
|
|
1354
|
+
* documents and its suite caught twice.
|
|
1355
|
+
*
|
|
1356
|
+
* It carries no `selfLoop` block: with the matrix unreachable here, the rule's
|
|
1357
|
+
* own message IS the self-loop message and the family falls back to it.
|
|
1358
|
+
*/
|
|
1359
|
+
const generalizationSelfLoop = {
|
|
1360
|
+
id: 'uml.generalization-self-loop',
|
|
1361
|
+
framework: 'uml',
|
|
1362
|
+
family: 'relation-endpoints',
|
|
1363
|
+
severity: 'audit',
|
|
1364
|
+
roles: UML_ROLES,
|
|
1365
|
+
messageKey: 'com.labre.uml.validation.generalization-self-loop',
|
|
1366
|
+
messageFallback: 'This generalization loops back onto its own classifier.',
|
|
1367
|
+
suggestionKey: 'com.labre.uml.validation.generalization-self-loop.suggestion',
|
|
1368
|
+
suggestionFallback: 'Nothing is a specialisation of itself: a generalization hierarchy is directed and acyclic. Point the triangle at the classifier this one really specialises, or delete the line.',
|
|
1369
|
+
version: 1,
|
|
1370
|
+
provenance: {
|
|
1371
|
+
source: 'standard',
|
|
1372
|
+
reference: 'OMG UML 2.5.1 §9.9.7 — Generalization hierarchies must be directed and acyclical',
|
|
1373
|
+
},
|
|
1374
|
+
backgroundRole: UML_ROLE.diagram,
|
|
1375
|
+
endpoints: {
|
|
1376
|
+
edgeRole: UML_ROLE.generalization,
|
|
1377
|
+
// The ALPHABET, never U7's grammar — see the header.
|
|
1378
|
+
allowed: UML_GENERALIZATION_ALPHABET,
|
|
1379
|
+
forbidSelfLoop: true,
|
|
1380
|
+
},
|
|
1381
|
+
};
|
|
1382
|
+
/**
|
|
1383
|
+
* **U9** — a realization that does not point at an interface.
|
|
1384
|
+
*
|
|
1385
|
+
* §10.4.3 types an InterfaceRealization's `contract` end as an Interface, and the
|
|
1386
|
+
* notation draws it as the dashed line with the hollow triangle. A realization
|
|
1387
|
+
* between two classes is a generalization drawn with the wrong tool — the two
|
|
1388
|
+
* differ by a dash, which is the single easiest mistake to make on a class
|
|
1389
|
+
* diagram and the single easiest to fix (`morph.ts`).
|
|
1390
|
+
*
|
|
1391
|
+
* One sentence, one alphabet of one role, and therefore exactly one thing this
|
|
1392
|
+
* rule can ever say. See {@link UML_REALIZATION_MATRIX} for what it declines to
|
|
1393
|
+
* have an opinion about.
|
|
1394
|
+
*/
|
|
1395
|
+
const realizationEndpoints = {
|
|
1396
|
+
id: 'uml.realization-endpoints',
|
|
1397
|
+
framework: 'uml',
|
|
1398
|
+
family: 'relation-endpoints',
|
|
1399
|
+
severity: 'audit',
|
|
1400
|
+
roles: UML_ROLES,
|
|
1401
|
+
messageKey: 'com.labre.uml.validation.realization-endpoints',
|
|
1402
|
+
messageFallback: 'This realization does not point at an interface.',
|
|
1403
|
+
suggestionKey: 'com.labre.uml.validation.realization-endpoints.suggestion',
|
|
1404
|
+
suggestionFallback: 'A realization says "this classifier implements that contract", and a contract is an interface. Point the dashed triangle at one — or, if what you mean is "is a", draw a generalization instead.',
|
|
1405
|
+
version: 1,
|
|
1406
|
+
provenance: {
|
|
1407
|
+
source: 'standard',
|
|
1408
|
+
reference: 'OMG UML 2.5.1 §10.4.3 — an InterfaceRealization’s contract is an Interface',
|
|
1409
|
+
},
|
|
1410
|
+
backgroundRole: UML_ROLE.diagram,
|
|
1411
|
+
endpoints: {
|
|
1412
|
+
edgeRole: UML_ROLE.realization,
|
|
1413
|
+
allowed: UML_REALIZATION_MATRIX,
|
|
1414
|
+
},
|
|
1415
|
+
};
|
|
1416
|
+
/**
|
|
1417
|
+
* **U10** — a dependency pointing at an instance.
|
|
1418
|
+
*
|
|
1419
|
+
* §9.8.3: an InstanceSpecification illustrates the model, it is not part of it.
|
|
1420
|
+
* A dashed arrow from a class to `o1 : Order` is a statement about a value where
|
|
1421
|
+
* the author meant a statement about a type, and the fix is one drag.
|
|
1422
|
+
*
|
|
1423
|
+
* The object is a legal SOURCE — a diagram may show what an instance needs — and
|
|
1424
|
+
* never a legal target, which is the whole of {@link UML_DEPENDENCY_MATRIX}.
|
|
1425
|
+
*
|
|
1426
|
+
* ## What the brief asked for that this rule no longer carries
|
|
1427
|
+
*
|
|
1428
|
+
* "dependency OR generalization to `uml:object`". `relation-endpoints` names one
|
|
1429
|
+
* edge role, so the generalization half cannot live here — and it does not have
|
|
1430
|
+
* to: {@link generalizationEndpoints} already indicts a generalization pointing at
|
|
1431
|
+
* an instance, because `uml:object` is in its alphabet and in none of its
|
|
1432
|
+
* sentences. One verdict per wrong edge, and each carries the sentence that fits
|
|
1433
|
+
* the line the author actually drew.
|
|
1434
|
+
*/
|
|
1435
|
+
const dependencyOnObject = {
|
|
1436
|
+
id: 'uml.dependency-on-object',
|
|
1437
|
+
framework: 'uml',
|
|
1438
|
+
family: 'relation-endpoints',
|
|
1439
|
+
severity: 'audit',
|
|
1440
|
+
roles: UML_ROLES,
|
|
1441
|
+
messageKey: 'com.labre.uml.validation.dependency-on-object',
|
|
1442
|
+
messageFallback: 'This dependency points at an instance.',
|
|
1443
|
+
suggestionKey: 'com.labre.uml.validation.dependency-on-object.suggestion',
|
|
1444
|
+
suggestionFallback: 'A dependency is a statement about the model, and an instance is an illustration of it. Point the arrow at the classifier the instance is of, or at the package that holds it.',
|
|
1445
|
+
version: 1,
|
|
1446
|
+
provenance: {
|
|
1447
|
+
source: 'recommendation',
|
|
1448
|
+
reference: 'OMG UML 2.5.1 §9.8.3 — an InstanceSpecification illustrates a Classifier; the model’s dependencies are stated between the named elements themselves',
|
|
1449
|
+
},
|
|
1450
|
+
backgroundRole: UML_ROLE.diagram,
|
|
1451
|
+
endpoints: {
|
|
1452
|
+
edgeRole: UML_ROLE.dependency,
|
|
1453
|
+
allowed: UML_DEPENDENCY_MATRIX,
|
|
1454
|
+
},
|
|
1455
|
+
};
|
|
1456
|
+
/**
|
|
1457
|
+
* **U11** — an include with an end that is not a use case.
|
|
1458
|
+
*
|
|
1459
|
+
* §18.1.3 types both ends of an Include as UseCases: an inclusion says "running
|
|
1460
|
+
* this behaviour always runs that one", which is a statement about two
|
|
1461
|
+
* behaviours. An include drawn from an ACTOR — the mistake this rule exists for
|
|
1462
|
+
* — is somebody reaching for the association tool's neighbour in the menu.
|
|
1463
|
+
*
|
|
1464
|
+
* Reads the use case grammar's `uml:include` slice; the two association triplets
|
|
1465
|
+
* in that table are the alphabet entries that make an actor end a finding rather
|
|
1466
|
+
* than silence (see {@link UML_USE_CASE_MATRIX}).
|
|
1467
|
+
*/
|
|
1468
|
+
const includeEndpoints = {
|
|
1469
|
+
id: 'uml.include-endpoints',
|
|
1470
|
+
framework: 'uml',
|
|
1471
|
+
family: 'relation-endpoints',
|
|
1472
|
+
severity: 'audit',
|
|
1473
|
+
roles: UML_ROLES,
|
|
1474
|
+
messageKey: 'com.labre.uml.validation.include-endpoints',
|
|
1475
|
+
messageFallback: 'This include runs between something other than two use cases.',
|
|
1476
|
+
suggestionKey: 'com.labre.uml.validation.include-endpoints.suggestion',
|
|
1477
|
+
suggestionFallback: 'An include says one behaviour always runs another, so both ends are use cases. If the end is an actor, what you want is an association — draw that instead.',
|
|
1478
|
+
version: 1,
|
|
1479
|
+
provenance: {
|
|
1480
|
+
source: 'standard',
|
|
1481
|
+
reference: 'OMG UML 2.5.1 §18.1.3 — an Include relates an including UseCase to the UseCase it includes',
|
|
1482
|
+
},
|
|
1483
|
+
backgroundRole: UML_ROLE.diagram,
|
|
1484
|
+
endpoints: {
|
|
1485
|
+
edgeRole: UML_ROLE.include,
|
|
1486
|
+
allowed: UML_USE_CASE_MATRIX,
|
|
1487
|
+
},
|
|
1488
|
+
};
|
|
1489
|
+
/**
|
|
1490
|
+
* **U12** — an extend with an end that is not a use case.
|
|
1491
|
+
*
|
|
1492
|
+
* {@link includeEndpoints}' twin, on §18.1.3's other relationship: an Extend
|
|
1493
|
+
* relates the extending UseCase to the extended one. The two are declared
|
|
1494
|
+
* separately because the roles are flat — an extend is not a kind of include, and
|
|
1495
|
+
* one rule policing both would be a rule nobody could read the verdict of.
|
|
1496
|
+
*/
|
|
1497
|
+
const extendEndpoints = {
|
|
1498
|
+
id: 'uml.extend-endpoints',
|
|
1499
|
+
framework: 'uml',
|
|
1500
|
+
family: 'relation-endpoints',
|
|
1501
|
+
severity: 'audit',
|
|
1502
|
+
roles: UML_ROLES,
|
|
1503
|
+
messageKey: 'com.labre.uml.validation.extend-endpoints',
|
|
1504
|
+
messageFallback: 'This extend runs between something other than two use cases.',
|
|
1505
|
+
suggestionKey: 'com.labre.uml.validation.extend-endpoints.suggestion',
|
|
1506
|
+
suggestionFallback: 'An extend says one behaviour may add itself to another at a point the other declares, so both ends are use cases. If the end is an actor, draw an association instead.',
|
|
1507
|
+
version: 1,
|
|
1508
|
+
provenance: {
|
|
1509
|
+
source: 'standard',
|
|
1510
|
+
reference: 'OMG UML 2.5.1 §18.1.3 — an Extend relates an extending UseCase to the extended UseCase',
|
|
1511
|
+
},
|
|
1512
|
+
backgroundRole: UML_ROLE.diagram,
|
|
1513
|
+
endpoints: {
|
|
1514
|
+
edgeRole: UML_ROLE.extend,
|
|
1515
|
+
allowed: UML_USE_CASE_MATRIX,
|
|
1516
|
+
},
|
|
1517
|
+
};
|
|
1518
|
+
/**
|
|
1519
|
+
* **U13** — an association drawn between two actors.
|
|
1520
|
+
*
|
|
1521
|
+
* §18.1.3 lists what an Actor may be associated with — UseCases, Components and
|
|
1522
|
+
* Classes — and two actors is not on it. Two stick figures joined by a solid line
|
|
1523
|
+
* is almost always an organisation chart drawn on a use case diagram: the
|
|
1524
|
+
* relationship between two roles is real and important and it is not what this
|
|
1525
|
+
* notation draws. (A GENERALIZATION between two actors is legal and this rule
|
|
1526
|
+
* says nothing about one — it reads `uml:association` alone.)
|
|
1527
|
+
*
|
|
1528
|
+
* ## The one removal, and the reason the table is written as one
|
|
1529
|
+
*
|
|
1530
|
+
* {@link UML_ASSOCIATION_MATRIX} is the alphabet minus a single line, exactly as
|
|
1531
|
+
* C4's relationship grammar is. That is what makes this a single-verdict rule:
|
|
1532
|
+
* everything else in the alphabet is sanctioned, so the only sentence it can
|
|
1533
|
+
* report is the one its message names. An association onto a package or a note
|
|
1534
|
+
* is outside the alphabet and stays outside the conversation.
|
|
1535
|
+
*
|
|
1536
|
+
* Also the rule that reaches the two DIAMONDS for free: `uml:aggregation` and
|
|
1537
|
+
* `uml:composition` specialise `uml:association` (§11.5.4), so `roleIsA` brings
|
|
1538
|
+
* them in and the same grammar judges all three.
|
|
1539
|
+
*/
|
|
1540
|
+
const actorActorAssociation = {
|
|
1541
|
+
id: 'uml.actor-actor-association',
|
|
1542
|
+
framework: 'uml',
|
|
1543
|
+
family: 'relation-endpoints',
|
|
1544
|
+
severity: 'audit',
|
|
1545
|
+
roles: UML_ROLES,
|
|
1546
|
+
messageKey: 'com.labre.uml.validation.actor-actor-association',
|
|
1547
|
+
messageFallback: 'This association runs between two actors.',
|
|
1548
|
+
suggestionKey: 'com.labre.uml.validation.actor-actor-association.suggestion',
|
|
1549
|
+
suggestionFallback: 'A use case diagram associates an actor with what the system does for them, never with another actor. Draw what each of them uses — or, if one role is a kind of the other, draw a generalization.',
|
|
1550
|
+
version: 1,
|
|
1551
|
+
provenance: {
|
|
1552
|
+
source: 'standard',
|
|
1553
|
+
reference: 'OMG UML 2.5.1 §18.1.3 — an Actor may have Associations with UseCases, Components and Classes, and with nothing else',
|
|
1554
|
+
},
|
|
1555
|
+
backgroundRole: UML_ROLE.diagram,
|
|
1556
|
+
endpoints: {
|
|
1557
|
+
edgeRole: UML_ROLE.association,
|
|
1558
|
+
allowed: UML_ASSOCIATION_MATRIX,
|
|
1559
|
+
},
|
|
1560
|
+
};
|
|
1561
|
+
/**
|
|
1562
|
+
* **U17** — a `«deploy»` arrow that does not put an artefact on a node.
|
|
1563
|
+
*
|
|
1564
|
+
* §19.2.3 types the two ends of a Deployment — a DeployedArtifact onto a
|
|
1565
|
+
* DeploymentTarget — and §19.4.4 draws it as the dashed arrow with the keyword.
|
|
1566
|
+
* The one drawing this rule is really for is the arrow from a COMPONENT to a
|
|
1567
|
+
* node: it is the sentence everybody means and the one §19 declines to draw,
|
|
1568
|
+
* because what reaches a machine is the artefact — the jar, the image, the
|
|
1569
|
+
* script — and the component is what that artefact MANIFESTS (§19.3.3). The fix
|
|
1570
|
+
* is one box and two arrows, and the suggestion says so.
|
|
1571
|
+
*
|
|
1572
|
+
* Reads {@link UML_DEPLOY_MATRIX}, whose second triplet is the alphabet entry
|
|
1573
|
+
* that makes the component end a finding rather than silence.
|
|
1574
|
+
*/
|
|
1575
|
+
const deployEndpoints = {
|
|
1576
|
+
id: 'uml.deploy-endpoints',
|
|
1577
|
+
framework: 'uml',
|
|
1578
|
+
family: 'relation-endpoints',
|
|
1579
|
+
severity: 'audit',
|
|
1580
|
+
roles: UML_ROLES,
|
|
1581
|
+
messageKey: 'com.labre.uml.validation.deploy-endpoints',
|
|
1582
|
+
messageFallback: 'This deployment does not put an artefact on a node.',
|
|
1583
|
+
suggestionKey: 'com.labre.uml.validation.deploy-endpoints.suggestion',
|
|
1584
|
+
suggestionFallback: 'A deployment runs from the artefact — the jar, the image, the script — to the node, device or execution environment it is installed on. If the far end of this arrow is a component, draw the artefact that manifests it and deploy that instead.',
|
|
1585
|
+
version: 1,
|
|
1586
|
+
provenance: {
|
|
1587
|
+
source: 'standard',
|
|
1588
|
+
reference: 'OMG UML 2.5.1 §19.2.3 — a Deployment relates a DeployedArtifact to the DeploymentTarget it is deployed on; §19.4.4 draws it as the «deploy» arrow',
|
|
1589
|
+
},
|
|
1590
|
+
backgroundRole: UML_ROLE.diagram,
|
|
1591
|
+
endpoints: {
|
|
1592
|
+
edgeRole: UML_ROLE.deploy,
|
|
1593
|
+
allowed: UML_DEPLOY_MATRIX,
|
|
1594
|
+
},
|
|
1595
|
+
};
|
|
1596
|
+
/**
|
|
1597
|
+
* **U18** — a `«manifest»` arrow that does not run from an artefact to a
|
|
1598
|
+
* component.
|
|
1599
|
+
*
|
|
1600
|
+
* §19.3.3: the Artifact owns the Manifestations, each representing the
|
|
1601
|
+
* utilization of some model element, and §19.3.5's figure draws the one this
|
|
1602
|
+
* notation is used for — an artefact and the component it embodies. An arrow
|
|
1603
|
+
* drawn the other way round, or one aimed at the NODE the artefact happens to
|
|
1604
|
+
* sit on, is the deployment story told with the wrong word.
|
|
1605
|
+
*
|
|
1606
|
+
* {@link deployEndpoints}' twin, and the two never double-report: an edge
|
|
1607
|
+
* carries one role, `uml:deploy` and `uml:manifest` are flat siblings, and each
|
|
1608
|
+
* rule reads its own.
|
|
1609
|
+
*/
|
|
1610
|
+
const manifestEndpoints = {
|
|
1611
|
+
id: 'uml.manifest-endpoints',
|
|
1612
|
+
framework: 'uml',
|
|
1613
|
+
family: 'relation-endpoints',
|
|
1614
|
+
severity: 'audit',
|
|
1615
|
+
roles: UML_ROLES,
|
|
1616
|
+
messageKey: 'com.labre.uml.validation.manifest-endpoints',
|
|
1617
|
+
messageFallback: 'This manifestation does not run from an artefact to a component.',
|
|
1618
|
+
suggestionKey: 'com.labre.uml.validation.manifest-endpoints.suggestion',
|
|
1619
|
+
suggestionFallback: 'A manifestation says "this file embodies that component", so it leaves the artefact and arrives on the component. If what you meant is where the file is installed, draw a deployment to the node instead.',
|
|
1620
|
+
version: 1,
|
|
1621
|
+
provenance: {
|
|
1622
|
+
source: 'standard',
|
|
1623
|
+
reference: 'OMG UML 2.5.1 §19.3.3 — an Artifact owns its Manifestations, each the utilization of a PackageableElement; §19.3.4 draws it as the «manifest» arrow',
|
|
1624
|
+
},
|
|
1625
|
+
backgroundRole: UML_ROLE.diagram,
|
|
1626
|
+
endpoints: {
|
|
1627
|
+
edgeRole: UML_ROLE.manifest,
|
|
1628
|
+
allowed: UML_MANIFEST_MATRIX,
|
|
1629
|
+
},
|
|
1630
|
+
};
|
|
1631
|
+
/**
|
|
1632
|
+
* **U19** — a communication path drawn to something that is not a node.
|
|
1633
|
+
*
|
|
1634
|
+
* §19.4.3, and one of the few clauses in the whole specification that types a
|
|
1635
|
+
* relationship in a single sentence: a CommunicationPath is an Association
|
|
1636
|
+
* between two DeploymentTargets. A network line drawn to a `.jar` or to a
|
|
1637
|
+
* component box is the confusion this rule is for — the SOFTWARE does not talk
|
|
1638
|
+
* to a machine, the machine it runs on does, and the drawing that says otherwise
|
|
1639
|
+
* has skipped the node.
|
|
1640
|
+
*
|
|
1641
|
+
* Both ends are `uml:node`, so a device connected to an execution environment is
|
|
1642
|
+
* sanctioned through the parent role and never mentioned.
|
|
1643
|
+
*/
|
|
1644
|
+
const communicationPathEndpoints = {
|
|
1645
|
+
id: 'uml.communication-path-endpoints',
|
|
1646
|
+
framework: 'uml',
|
|
1647
|
+
family: 'relation-endpoints',
|
|
1648
|
+
severity: 'audit',
|
|
1649
|
+
roles: UML_ROLES,
|
|
1650
|
+
messageKey: 'com.labre.uml.validation.communication-path-endpoints',
|
|
1651
|
+
messageFallback: 'This communication path has an end that is not a node.',
|
|
1652
|
+
suggestionKey: 'com.labre.uml.validation.communication-path-endpoints.suggestion',
|
|
1653
|
+
suggestionFallback: 'A communication path joins two machines — nodes, devices or execution environments — so that they can exchange messages. Draw it between the two cubes, and let the deployment arrows say which software sits on each.',
|
|
1654
|
+
version: 1,
|
|
1655
|
+
provenance: {
|
|
1656
|
+
source: 'standard',
|
|
1657
|
+
reference: 'OMG UML 2.5.1 §19.4.3 — a CommunicationPath is an Association between two DeploymentTargets, through which they may exchange Signals and Messages',
|
|
1658
|
+
},
|
|
1659
|
+
backgroundRole: UML_ROLE.diagram,
|
|
1660
|
+
endpoints: {
|
|
1661
|
+
edgeRole: UML_ROLE['communication-path'],
|
|
1662
|
+
allowed: UML_COMMUNICATION_PATH_MATRIX,
|
|
1663
|
+
},
|
|
1664
|
+
};
|
|
1665
|
+
/**
|
|
1666
|
+
* **U14** — a plain connector between two UML artefacts.
|
|
1667
|
+
*
|
|
1668
|
+
* The gap every other rule in this file falls through, and C4's `untyped-link`
|
|
1669
|
+
* and BPMN's `B4` one framework over. The UML relationship tools stamp a role;
|
|
1670
|
+
* quick-connect and auto-complete do not, so releasing the canvas' own link
|
|
1671
|
+
* gesture between two boxes produces a connector carrying NO role — which says
|
|
1672
|
+
* nothing to any grammar, nothing to the degree counts and nothing to the
|
|
1673
|
+
* exporters. A diagram joined that way LOOKS connected, exports as if nobody had
|
|
1674
|
+
* joined anything, and reads on screen as a finished drawing.
|
|
1675
|
+
*
|
|
1676
|
+
* So the rule is not really about the connector: it is about the verdicts and the
|
|
1677
|
+
* exports that go quiet behind it.
|
|
1678
|
+
*
|
|
1679
|
+
* ## Why it declares a matrix it never judges anything against
|
|
1680
|
+
*
|
|
1681
|
+
* `flagNeutral` reads the rule's own ALPHABET to decide which role-less links it
|
|
1682
|
+
* may presume were meant as relationships, so {@link UML_ELEMENT_MATRIX} is how
|
|
1683
|
+
* this rule says "between two UML artefacts" — and, by saying only that, how it
|
|
1684
|
+
* stays silent about everything else. A plain connector onto the frame, onto a
|
|
1685
|
+
* subject, onto a compartment, onto a sticky note, onto a shape somebody dropped
|
|
1686
|
+
* to think with is an annotation and none of UML's business.
|
|
1687
|
+
*
|
|
1688
|
+
* Nothing else in the declaration fires: the matrix sanctions all thirty-six
|
|
1689
|
+
* ordered pairs, so off-matrix is structurally unreachable here and the actor →
|
|
1690
|
+
* actor sentence stays {@link actorActorAssociation}'s finding alone.
|
|
1691
|
+
* `forbidSelfLoop` is absent for the same reason. This rule raises exactly one
|
|
1692
|
+
* kind of finding.
|
|
1693
|
+
*/
|
|
1694
|
+
const untypedEdge = {
|
|
1695
|
+
id: 'uml.untyped-edge',
|
|
1696
|
+
framework: 'uml',
|
|
1697
|
+
family: 'relation-endpoints',
|
|
1698
|
+
severity: 'audit',
|
|
1699
|
+
roles: UML_ROLES,
|
|
1700
|
+
// The rule's own words are never read: `flagNeutral` is the only verdict it can
|
|
1701
|
+
// reach, and it carries its own. Declared all the same, because the shape
|
|
1702
|
+
// requires them and a rule with no sentence is a rule nobody can review.
|
|
1703
|
+
messageKey: 'com.labre.uml.validation.untyped-edge',
|
|
1704
|
+
messageFallback: 'This link between two UML artefacts says nothing the model records.',
|
|
1705
|
+
suggestionKey: 'com.labre.uml.validation.untyped-edge.suggestion',
|
|
1706
|
+
suggestionFallback: 'Draw it again with one of the relationship tools. A plain connector is on the diagram and absent from the model, so nothing can be said about what it means, which way round it reads, or how to write it into a file.',
|
|
1707
|
+
version: 1,
|
|
1708
|
+
provenance: {
|
|
1709
|
+
source: 'labre-convention',
|
|
1710
|
+
reference: 'Labre convention — a role-less connector is a gesture of this canvas, not an artefact of the notation',
|
|
1711
|
+
},
|
|
1712
|
+
backgroundRole: UML_ROLE.diagram,
|
|
1713
|
+
endpoints: {
|
|
1714
|
+
// Any of the nine would do — the neutral pass reads no edge role — and the
|
|
1715
|
+
// association is the one a user reaching for a line most often means.
|
|
1716
|
+
edgeRole: UML_ROLE.association,
|
|
1717
|
+
allowed: UML_ELEMENT_MATRIX,
|
|
1718
|
+
flagNeutral: {
|
|
1719
|
+
messageKey: 'com.labre.uml.validation.untyped-edge.neutral',
|
|
1720
|
+
messageFallback: 'These two artefacts are joined by an untyped link.',
|
|
1721
|
+
suggestionKey: 'com.labre.uml.validation.untyped-edge.neutral.suggestion',
|
|
1722
|
+
suggestionFallback: 'The diagram shows a line and the model holds none — nothing associates, specialises or depends on anything here. Draw it again with an association, a generalization, a realization or a dependency so it can carry a direction and be exported.',
|
|
1723
|
+
},
|
|
1724
|
+
},
|
|
1725
|
+
};
|
|
1726
|
+
/* ── Degree: how many lines may reach one artefact? ─────────────────────── */
|
|
1727
|
+
/**
|
|
1728
|
+
* **U15** — a part claimed by two composites.
|
|
1729
|
+
*
|
|
1730
|
+
* §11.5.3, and one of the few genuinely arithmetic constraints the notation has:
|
|
1731
|
+
* composition is a whole/part relationship in which the part's lifetime is the
|
|
1732
|
+
* whole's, so an object may be a part of at most one composite at a time. Two
|
|
1733
|
+
* filled diamonds pointing at the same box is a drawing that cannot be built.
|
|
1734
|
+
*
|
|
1735
|
+
* ## The PART is the target end
|
|
1736
|
+
*
|
|
1737
|
+
* `roles.ts` is explicit: for an aggregation and a composition the source is the
|
|
1738
|
+
* WHOLE — the end that carries the diamond, which `actions.ts` arms as the
|
|
1739
|
+
* connector's front endpoint. So the part is where the line ARRIVES, and the
|
|
1740
|
+
* bound is `maxIn: 1`.
|
|
1741
|
+
*
|
|
1742
|
+
* Aggregation is deliberately not counted: §11.5.4's shared aggregation has no
|
|
1743
|
+
* such constraint — a part may be shared by many wholes, which is the whole
|
|
1744
|
+
* difference between the hollow diamond and the filled one — and `uml:composition`
|
|
1745
|
+
* is a role of its own precisely so a rule can say one and not the other.
|
|
1746
|
+
*
|
|
1747
|
+
* ## Written on the classifiers, like {@link elementOutsideFrame}
|
|
1748
|
+
*
|
|
1749
|
+
* `edge-degree` names one subject role. A composition pointing at an instance or
|
|
1750
|
+
* a package is a stranger drawing than this rule's, and one
|
|
1751
|
+
* {@link dependencyOnObject}'s neighbourhood already covers in spirit; the part
|
|
1752
|
+
* of a composite is a classifier in every drawing anybody makes.
|
|
1753
|
+
*/
|
|
1754
|
+
const compositionSingleOwner = {
|
|
1755
|
+
id: 'uml.composition-single-owner',
|
|
1756
|
+
framework: 'uml',
|
|
1757
|
+
family: 'edge-degree',
|
|
1758
|
+
severity: 'audit',
|
|
1759
|
+
appliesTo: UML_ROLE.classifier,
|
|
1760
|
+
roles: UML_ROLES,
|
|
1761
|
+
messageKey: 'com.labre.uml.validation.composition-single-owner',
|
|
1762
|
+
messageFallback: 'Two compositions claim this part.',
|
|
1763
|
+
suggestionKey: 'com.labre.uml.validation.composition-single-owner.suggestion',
|
|
1764
|
+
suggestionFallback: 'A composite owns its parts’ lifetime, so a part belongs to one whole at a time. Keep the composition that really owns it and make the other an aggregation — the hollow diamond, which is exactly the "shared part" the notation has.',
|
|
1765
|
+
version: 1,
|
|
1766
|
+
provenance: {
|
|
1767
|
+
source: 'standard',
|
|
1768
|
+
reference: 'OMG UML 2.5.1 §11.5.3 — a composite object is responsible for the existence of its parts; an object may be part of at most one composite at a time',
|
|
1769
|
+
},
|
|
1770
|
+
backgroundRole: UML_ROLE.diagram,
|
|
1771
|
+
degree: {
|
|
1772
|
+
edgeRole: UML_ROLE.composition,
|
|
1773
|
+
// The PART is where the diamond's line ARRIVES — see the header.
|
|
1774
|
+
maxIn: 1,
|
|
1775
|
+
},
|
|
1776
|
+
};
|
|
1777
|
+
/**
|
|
1778
|
+
* **U16** — a use case no actor is associated with.
|
|
1779
|
+
*
|
|
1780
|
+
* Not a constraint of the specification and it says so: §18.1 is perfectly happy
|
|
1781
|
+
* with a use case nobody has drawn an actor for, and there are real diagrams —
|
|
1782
|
+
* an included behaviour, a use case reached only through an extend — where that
|
|
1783
|
+
* is the right drawing.
|
|
1784
|
+
*
|
|
1785
|
+
* It is a USAGE remark, and the most useful one a finished use case diagram can
|
|
1786
|
+
* be given: a use case is a behaviour the system performs FOR somebody, so an
|
|
1787
|
+
* ellipse with no actor on either end is usually a use case whose actor has not
|
|
1788
|
+
* been drawn yet, or a system function that is not a use case at all.
|
|
1789
|
+
*
|
|
1790
|
+
* `eitherMin: 1` — at least one association on ONE side. The four ordinary bounds
|
|
1791
|
+
* cannot say it: `minIn: 1` indicts every use case an actor associates FROM,
|
|
1792
|
+
* `minOut: 1` every one drawn the other way round, and both together indict a
|
|
1793
|
+
* conformant diagram end to end. The direction of an association is not a fact
|
|
1794
|
+
* about the model (`roles.ts`: an association is UNDIRECTED), so the only honest
|
|
1795
|
+
* bound is the disjunctive one.
|
|
1796
|
+
*
|
|
1797
|
+
* `audit` at BOTH levels — `uml.strict` leaves it here — because the finding is
|
|
1798
|
+
* "this diagram is not finished", and not-finished is what a panel is for.
|
|
1799
|
+
*/
|
|
1800
|
+
const useCaseNoActor = {
|
|
1801
|
+
id: 'uml.use-case-no-actor',
|
|
1802
|
+
framework: 'uml',
|
|
1803
|
+
family: 'edge-degree',
|
|
1804
|
+
severity: 'audit',
|
|
1805
|
+
appliesTo: UML_ROLE['use-case'],
|
|
1806
|
+
roles: UML_ROLES,
|
|
1807
|
+
messageKey: 'com.labre.uml.validation.use-case-no-actor',
|
|
1808
|
+
messageFallback: 'No actor is associated with this use case.',
|
|
1809
|
+
suggestionKey: 'com.labre.uml.validation.use-case-no-actor.suggestion',
|
|
1810
|
+
suggestionFallback: 'A use case is something the system does for somebody. Draw the association to the actor it serves — or, if it is only ever reached from another use case, leave it: this is a remark, not a rule.',
|
|
1811
|
+
version: 1,
|
|
1812
|
+
provenance: {
|
|
1813
|
+
source: 'labre-convention',
|
|
1814
|
+
reference: 'Labre convention — UML states no such constraint; a use case with no actor is usually a diagram somebody has not finished',
|
|
1815
|
+
},
|
|
1816
|
+
backgroundRole: UML_ROLE.diagram,
|
|
1817
|
+
degree: {
|
|
1818
|
+
edgeRole: UML_ROLE.association,
|
|
1819
|
+
// One association on EITHER side — see the header on why no conjunction of
|
|
1820
|
+
// the per-direction bounds expresses this.
|
|
1821
|
+
eitherMin: 1,
|
|
1822
|
+
},
|
|
1823
|
+
};
|
|
1824
|
+
/* ── Attachment: is the glyph still on the box it was drawn on? ──────────── */
|
|
1825
|
+
/**
|
|
1826
|
+
* **U43** — a port that has drifted into the middle of its component.
|
|
1827
|
+
*
|
|
1828
|
+
* The only `border-proximity` rule in the library (ADR 0024), and the one this
|
|
1829
|
+
* pack recorded for a whole phase as a question the engine could not be asked.
|
|
1830
|
+
* §11.3.4 draws a Port as "a small square symbol… placed ON the boundary of the
|
|
1831
|
+
* rectangle" of the EncapsulatedClassifier that owns it, and the placement IS
|
|
1832
|
+
* the statement: a port on the edge says "traffic crosses here", the same square
|
|
1833
|
+
* a hundred units inside says nothing the notation has a reading for. It is not
|
|
1834
|
+
* decoration and it is not a matter of taste — `model.ts` resolves a port's
|
|
1835
|
+
* owner from exactly this geometry, so a port dragged off the border is also a
|
|
1836
|
+
* port the XMI and the PlantUML writers will file under nothing.
|
|
1837
|
+
*
|
|
1838
|
+
* ## The tolerance, and why it is a number of units
|
|
1839
|
+
*
|
|
1840
|
+
* {@link UML_PORT_BORDER_TOLERANCE} — the glyph's own 16-unit side, derived from
|
|
1841
|
+
* `UML_NODE_BOX.port` rather than restated. `consts.ts` works the three drawings
|
|
1842
|
+
* through: half in and half out (§11.3.4's own picture) is distance 0, tangent
|
|
1843
|
+
* inside is 8, and a square pushed a full glyph clear of the edge is 24 and the
|
|
1844
|
+
* finding falls. Absolute and not a ratio of the component, because what makes
|
|
1845
|
+
* "on the border" legible is the square against the line and not the size of the
|
|
1846
|
+
* box behind it.
|
|
1847
|
+
*
|
|
1848
|
+
* ## What it stays silent about
|
|
1849
|
+
*
|
|
1850
|
+
* A port touching NO component at all — a square dropped on blank canvas, or
|
|
1851
|
+
* parked beside the sheet while the diagram is being rearranged. That is the
|
|
1852
|
+
* family's own gate (`border-proximity` judges a glyph the author has already
|
|
1853
|
+
* put on a box), and it is the right silence: a port belonging to nothing is
|
|
1854
|
+
* somebody drawing, not somebody wrong (PRD principle 8). The same square on the
|
|
1855
|
+
* border of a CUBE is silence too — the rule's carrier is `uml:component`, and
|
|
1856
|
+
* `model.ts` already says a port drawn on a deployment node is a drawing this
|
|
1857
|
+
* pack keeps and writes nowhere.
|
|
1858
|
+
*
|
|
1859
|
+
* A port ON the border but outside its component (the half of the square that
|
|
1860
|
+
* sticks out being the larger half) is silence as well, at any drawing a hand
|
|
1861
|
+
* produces: the centre is at most half a glyph out, which is inside the
|
|
1862
|
+
* tolerance by construction.
|
|
1863
|
+
*/
|
|
1864
|
+
const portOnBorder = {
|
|
1865
|
+
id: 'uml.port-on-border',
|
|
1866
|
+
framework: 'uml',
|
|
1867
|
+
family: 'border-proximity',
|
|
1868
|
+
severity: 'audit',
|
|
1869
|
+
appliesTo: UML_ROLE.port,
|
|
1870
|
+
roles: UML_ROLES,
|
|
1871
|
+
messageKey: 'com.labre.uml.validation.port-on-border',
|
|
1872
|
+
messageFallback: 'This port sits inside its component, not on its border.',
|
|
1873
|
+
suggestionKey: 'com.labre.uml.validation.port-on-border.suggestion',
|
|
1874
|
+
suggestionFallback: 'A port is where something crosses the boundary of the component, so UML draws its square ON that boundary — straddling the edge, or just touching it from inside. Drag it back to the edge: the export reads which component owns a port from where the square sits, and one in the middle of the box belongs to nothing.',
|
|
1875
|
+
version: 1,
|
|
1876
|
+
provenance: {
|
|
1877
|
+
source: 'standard',
|
|
1878
|
+
reference: 'OMG UML 2.5.1 §11.3.4 — "A Port is shown as a small square symbol. The square symbol is placed on the boundary of the rectangle symbol for the owning EncapsulatedClassifier"',
|
|
1879
|
+
},
|
|
1880
|
+
backgroundRole: UML_ROLE.diagram,
|
|
1881
|
+
borderProximity: {
|
|
1882
|
+
carrierRole: UML_ROLE.component,
|
|
1883
|
+
tolerance: UML_PORT_BORDER_TOLERANCE,
|
|
1884
|
+
},
|
|
1885
|
+
};
|
|
1886
|
+
/* ── Behaviour: the alphabets and the grammars of the two flow sheets ────── */
|
|
1887
|
+
/**
|
|
1888
|
+
* The ACTIVITY vertices — what a flow may be drawn between on an `act` sheet.
|
|
1889
|
+
*
|
|
1890
|
+
* `uml:control-node` stands for its five children by declaration (`roles.ts`),
|
|
1891
|
+
* so the initial disc, the two finals, the decision diamond and the fork bar
|
|
1892
|
+
* arrive through `roleIsA` rather than as five entries. The object node is
|
|
1893
|
+
* deliberately NOT here: §15.7.9.4 keeps it off a control flow, which is the
|
|
1894
|
+
* whole content of {@link UML_CONTROL_FLOW_MATRIX}.
|
|
1895
|
+
*/
|
|
1896
|
+
const UML_ACTIVITY_VERTEX_ROLES = [
|
|
1897
|
+
UML_ROLE.action,
|
|
1898
|
+
UML_ROLE['control-node'],
|
|
1899
|
+
UML_ROLE['send-signal'],
|
|
1900
|
+
UML_ROLE['accept-event'],
|
|
1901
|
+
UML_ROLE['time-event'],
|
|
1902
|
+
];
|
|
1903
|
+
/**
|
|
1904
|
+
* The STATE MACHINE vertices — §14.5.12's "source Vertex" and "target Vertex".
|
|
1905
|
+
*
|
|
1906
|
+
* `uml:pseudostate` stands for the choice, the junction, the two histories, the
|
|
1907
|
+
* two connection points and the terminate cross. `uml:initial` is named beside
|
|
1908
|
+
* it rather than through it, because `roles.ts` files the initial disc under
|
|
1909
|
+
* `uml:control-node` — the same glyph means the same thing on both sheets, and
|
|
1910
|
+
* the vocabulary says so once instead of declaring it twice. `uml:fork` is here
|
|
1911
|
+
* for the same cross-family reason: §14.2.4 draws the fork and join bars in a
|
|
1912
|
+
* state machine and §15.3.4 draws them in an activity, and one role carries both.
|
|
1913
|
+
*/
|
|
1914
|
+
const UML_STATE_VERTEX_ROLES = [
|
|
1915
|
+
UML_ROLE.state,
|
|
1916
|
+
UML_ROLE['final-state'],
|
|
1917
|
+
UML_ROLE.pseudostate,
|
|
1918
|
+
UML_ROLE.initial,
|
|
1919
|
+
UML_ROLE.fork,
|
|
1920
|
+
];
|
|
1921
|
+
/**
|
|
1922
|
+
* ALPHABET entries for the state-machine vocabulary, carried by the two ACTIVITY
|
|
1923
|
+
* grammars.
|
|
1924
|
+
*
|
|
1925
|
+
* The device the file header explains, applied across the two behaviour sheets:
|
|
1926
|
+
* these are true sentences of §14.5.12 carrying the `uml:transition` role, so
|
|
1927
|
+
* `inMatrix` — which matches a triplet's edge with `roleIsA` — can never let one
|
|
1928
|
+
* sanction a control flow or an object flow. Their whole effect is to put
|
|
1929
|
+
* `uml:state`, `uml:final-state` and `uml:pseudostate` into the alphabet the two
|
|
1930
|
+
* activity rules speak, which is what makes "a control flow dragged onto a
|
|
1931
|
+
* state" a finding rather than silence.
|
|
1932
|
+
*/
|
|
1933
|
+
const UML_STATE_ALPHABET = [
|
|
1934
|
+
{
|
|
1935
|
+
source: UML_ROLE.state,
|
|
1936
|
+
edge: UML_ROLE.transition,
|
|
1937
|
+
target: UML_ROLE.state,
|
|
1938
|
+
},
|
|
1939
|
+
{
|
|
1940
|
+
source: UML_ROLE.state,
|
|
1941
|
+
edge: UML_ROLE.transition,
|
|
1942
|
+
target: UML_ROLE['final-state'],
|
|
1943
|
+
},
|
|
1944
|
+
{
|
|
1945
|
+
source: UML_ROLE.pseudostate,
|
|
1946
|
+
edge: UML_ROLE.transition,
|
|
1947
|
+
target: UML_ROLE.state,
|
|
1948
|
+
},
|
|
1949
|
+
];
|
|
1950
|
+
/**
|
|
1951
|
+
* ALPHABET entries for the activity vocabulary, carried by the TRANSITION
|
|
1952
|
+
* grammar — the mirror of {@link UML_STATE_ALPHABET}.
|
|
1953
|
+
*
|
|
1954
|
+
* Three foreign sentences putting `uml:action`, `uml:control-node` and
|
|
1955
|
+
* `uml:object-node` into the alphabet {@link transitionEndpoints} speaks. The
|
|
1956
|
+
* control-node entry is the one that earns its place twice over: it is what makes
|
|
1957
|
+
* a transition drawn onto a DECISION diamond a finding, because a decision is
|
|
1958
|
+
* §15.3.4's branch and `uml:choice` is §14.2.4's — two glyphs that look alike,
|
|
1959
|
+
* mean the same thing on two different sheets, and are the commonest confusion
|
|
1960
|
+
* this pair of diagrams produces.
|
|
1961
|
+
*/
|
|
1962
|
+
const UML_ACTIVITY_ALPHABET = [
|
|
1963
|
+
{
|
|
1964
|
+
source: UML_ROLE.action,
|
|
1965
|
+
edge: UML_ROLE['control-flow'],
|
|
1966
|
+
target: UML_ROLE['control-node'],
|
|
1967
|
+
},
|
|
1968
|
+
{
|
|
1969
|
+
source: UML_ROLE['object-node'],
|
|
1970
|
+
edge: UML_ROLE['object-flow'],
|
|
1971
|
+
target: UML_ROLE.action,
|
|
1972
|
+
},
|
|
1973
|
+
// …and the class-side vocabulary, so a behaviour edge dragged onto a class is
|
|
1974
|
+
// judged rather than ignored. `uml:classifier` reaches all three of them.
|
|
1975
|
+
{
|
|
1976
|
+
source: UML_ROLE.classifier,
|
|
1977
|
+
edge: UML_ROLE.association,
|
|
1978
|
+
target: UML_ROLE.classifier,
|
|
1979
|
+
},
|
|
1980
|
+
];
|
|
1981
|
+
/**
|
|
1982
|
+
* What a CONTROL FLOW may run between: the activity vertices, and never an
|
|
1983
|
+
* object node.
|
|
1984
|
+
*
|
|
1985
|
+
* §15.7.9.4 is one of the few constraints in Clause 15 that OCL can state in a
|
|
1986
|
+
* line — "ControlFlows may not have ObjectNodes at either end, except for
|
|
1987
|
+
* ObjectNodes with control type" — and the exception is the reason the rule that
|
|
1988
|
+
* holds this table stays a remark rather than a defect: `isControlType` is a
|
|
1989
|
+
* property of the metamodel with no notation at all (§15.4.4 draws the same
|
|
1990
|
+
* rectangle either way), so a canvas cannot say which of the two an author meant.
|
|
1991
|
+
* The table therefore reports the drawing the specification's own default
|
|
1992
|
+
* forbids, and the rule's suggestion names the exception.
|
|
1993
|
+
*
|
|
1994
|
+
* Twenty-five sentences over five roles, plus the four alphabet entries: the
|
|
1995
|
+
* grammar judges WHICH vertices, never how many edges reach one. The counts are
|
|
1996
|
+
* {@link initialNoIncomingFlow}, {@link activityFinalNoOutgoing},
|
|
1997
|
+
* {@link flowFinalNoOutgoing} and {@link decisionIncomingCount}, and keeping the
|
|
1998
|
+
* two apart is what stops one wrong arrow being reported twice.
|
|
1999
|
+
*/
|
|
2000
|
+
export const UML_CONTROL_FLOW_MATRIX = [
|
|
2001
|
+
...UML_ACTIVITY_VERTEX_ROLES.flatMap(source => UML_ACTIVITY_VERTEX_ROLES.map(target => ({
|
|
2002
|
+
source,
|
|
2003
|
+
edge: UML_ROLE['control-flow'],
|
|
2004
|
+
target,
|
|
2005
|
+
}))),
|
|
2006
|
+
// The ALPHABET entries — the state-machine vocabulary, and the class-side one.
|
|
2007
|
+
...UML_STATE_ALPHABET,
|
|
2008
|
+
{
|
|
2009
|
+
source: UML_ROLE.classifier,
|
|
2010
|
+
edge: UML_ROLE.association,
|
|
2011
|
+
target: UML_ROLE.classifier,
|
|
2012
|
+
},
|
|
2013
|
+
// …and the object node, named by a sentence of the OTHER flow so that it is in
|
|
2014
|
+
// the alphabet and outside the grammar. Without it a control flow drawn onto a
|
|
2015
|
+
// rectangle of data would be unjudged, which is the one drawing §15.7.9.4
|
|
2016
|
+
// actually names.
|
|
2017
|
+
{
|
|
2018
|
+
source: UML_ROLE['object-node'],
|
|
2019
|
+
edge: UML_ROLE['object-flow'],
|
|
2020
|
+
target: UML_ROLE.action,
|
|
2021
|
+
},
|
|
2022
|
+
];
|
|
2023
|
+
/**
|
|
2024
|
+
* What an OBJECT FLOW may run between: something, and an object node.
|
|
2025
|
+
*
|
|
2026
|
+
* §15.7.11.4's `same_upper_bounds` states the shape of it — "ObjectNodes
|
|
2027
|
+
* connected by an ObjectFlow, with optionally intervening ControlNodes" — so an
|
|
2028
|
+
* object flow is a line between DATA, routed through control nodes where it
|
|
2029
|
+
* forks or merges. One end being an object node is the loosest honest reading of
|
|
2030
|
+
* that, and it is the one drawn here: the strict reading (both ends) would indict
|
|
2031
|
+
* the object node that feeds a decision, which §15.7.11.4 explicitly allows.
|
|
2032
|
+
*
|
|
2033
|
+
* ## Why the rule that holds this stays a remark at every level
|
|
2034
|
+
*
|
|
2035
|
+
* §15.4.4 draws an action's PINS as small squares on its border and says in as
|
|
2036
|
+
* many words that they may be elided, in which case the object flow is drawn
|
|
2037
|
+
* from one action straight to the other. A pin IS an ObjectNode, so that drawing
|
|
2038
|
+
* is a legal elision of a conformant model and not a defect — and this canvas
|
|
2039
|
+
* has no pin. So the finding is "there is no data node on this flow", which is a
|
|
2040
|
+
* reading worth offering and never a conformance defect, and `uml.strict` leaves
|
|
2041
|
+
* it at `audit` for that reason alone (`profiles.ts`).
|
|
2042
|
+
*/
|
|
2043
|
+
const UML_OBJECT_FLOW_ENDS = [
|
|
2044
|
+
...UML_ACTIVITY_VERTEX_ROLES,
|
|
2045
|
+
UML_ROLE['object-node'],
|
|
2046
|
+
];
|
|
2047
|
+
export const UML_OBJECT_FLOW_MATRIX = [
|
|
2048
|
+
...UML_OBJECT_FLOW_ENDS.map(target => ({
|
|
2049
|
+
source: UML_ROLE['object-node'],
|
|
2050
|
+
edge: UML_ROLE['object-flow'],
|
|
2051
|
+
target,
|
|
2052
|
+
})),
|
|
2053
|
+
...UML_OBJECT_FLOW_ENDS.filter(source => source !== UML_ROLE['object-node']).map(source => ({
|
|
2054
|
+
source,
|
|
2055
|
+
edge: UML_ROLE['object-flow'],
|
|
2056
|
+
target: UML_ROLE['object-node'],
|
|
2057
|
+
})),
|
|
2058
|
+
// The ALPHABET entries — the state-machine vocabulary, and the class-side one.
|
|
2059
|
+
...UML_STATE_ALPHABET,
|
|
2060
|
+
{
|
|
2061
|
+
source: UML_ROLE.classifier,
|
|
2062
|
+
edge: UML_ROLE.association,
|
|
2063
|
+
target: UML_ROLE.classifier,
|
|
2064
|
+
},
|
|
2065
|
+
];
|
|
2066
|
+
/**
|
|
2067
|
+
* What a TRANSITION may run between: the vertices of a state machine, and
|
|
2068
|
+
* nothing else.
|
|
2069
|
+
*
|
|
2070
|
+
* §14.5.12 in one sentence — "A Transition is a single directed arc originating
|
|
2071
|
+
* from a single source Vertex and terminating on a single target Vertex" — and a
|
|
2072
|
+
* Vertex is a State, a FinalState or a Pseudostate (§14.5.14). So the table is
|
|
2073
|
+
* the full square over {@link UML_STATE_VERTEX_ROLES}: twenty-five sentences,
|
|
2074
|
+
* judging WHICH vertices and not how many arcs reach one.
|
|
2075
|
+
*
|
|
2076
|
+
* ## The square is deliberate, and it is what stops the double report
|
|
2077
|
+
*
|
|
2078
|
+
* A transition INTO an initial disc and a transition OUT OF a final state are
|
|
2079
|
+
* both wrong, and both are already said — by {@link initialNoIncomingTransition}
|
|
2080
|
+
* and {@link finalStateNoOutgoing}, each of which names the side the author has
|
|
2081
|
+
* to act on. Removing those two sentences from this table would make the same
|
|
2082
|
+
* arrow off-matrix as well, and the user would get two brackets and two
|
|
2083
|
+
* suggestions for one drag to fix. The DEGREE rules own the counts; this table
|
|
2084
|
+
* owns the vocabulary.
|
|
2085
|
+
*/
|
|
2086
|
+
export const UML_TRANSITION_MATRIX = [
|
|
2087
|
+
...UML_STATE_VERTEX_ROLES.flatMap(source => UML_STATE_VERTEX_ROLES.map(target => ({
|
|
2088
|
+
source,
|
|
2089
|
+
edge: UML_ROLE.transition,
|
|
2090
|
+
target,
|
|
2091
|
+
}))),
|
|
2092
|
+
// The ALPHABET entries — the activity vocabulary, and the class-side one.
|
|
2093
|
+
...UML_ACTIVITY_ALPHABET,
|
|
2094
|
+
];
|
|
2095
|
+
/* ── Behaviour: membership in a lane and in a composite state ───────────── */
|
|
2096
|
+
/**
|
|
2097
|
+
* **U20** — an action drawn outside every partition, while partitions exist.
|
|
2098
|
+
*
|
|
2099
|
+
* §15.6.4 draws an ActivityPartition as a swimlane: a band across the sheet with
|
|
2100
|
+
* a name at its head, holding the actions whoever the band names is responsible
|
|
2101
|
+
* for. The lanes are what the diagram is FOR once they are drawn — a reader
|
|
2102
|
+
* follows the flow across them to see where responsibility changes hands — and
|
|
2103
|
+
* an action floating between two of them belongs to nobody.
|
|
2104
|
+
*
|
|
2105
|
+
* ## The family is `element-in-background`, and the silence is the whole point
|
|
2106
|
+
*
|
|
2107
|
+
* {@link useCaseOutsideSubject}'s arrangement one notation over: a partition on
|
|
2108
|
+
* the board is the frame, an action contained by NONE of them is the finding,
|
|
2109
|
+
* and a board with no partition at all is total silence. Most activity diagrams
|
|
2110
|
+
* never draw a lane, and those are complete diagrams — the rule speaks only once
|
|
2111
|
+
* an author has said that responsibility is part of what this sheet shows.
|
|
2112
|
+
*
|
|
2113
|
+
* ## Written on the ACTION, and the limit that follows
|
|
2114
|
+
*
|
|
2115
|
+
* `element-in-background` names one subject role. The control nodes, the object
|
|
2116
|
+
* nodes and the three signal artefacts drawn between the lanes raise nothing —
|
|
2117
|
+
* and that is the right narrowing rather than a shortfall: §15.6.3 attributes
|
|
2118
|
+
* BEHAVIOUR to a partition, a decision diamond routing a flow between two lanes
|
|
2119
|
+
* is routinely drawn on the boundary on purpose, and a fork bar spanning three
|
|
2120
|
+
* lanes is §15.6.4's own figure. The action is the artefact a lane is about.
|
|
2121
|
+
*/
|
|
2122
|
+
const nodeInPartition = {
|
|
2123
|
+
id: 'uml.node-in-partition',
|
|
2124
|
+
framework: 'uml',
|
|
2125
|
+
family: 'element-in-background',
|
|
2126
|
+
severity: 'audit',
|
|
2127
|
+
appliesTo: UML_ROLE.action,
|
|
2128
|
+
roles: UML_ROLES,
|
|
2129
|
+
messageKey: 'com.labre.uml.validation.node-in-partition',
|
|
2130
|
+
messageFallback: 'This action sits outside every partition.',
|
|
2131
|
+
suggestionKey: 'com.labre.uml.validation.node-in-partition.suggestion',
|
|
2132
|
+
suggestionFallback: 'The swimlanes say who is responsible for each step. Move the action into the lane that owns it, or stretch the lane round it — a diagram that draws lanes is read by following the flow across them.',
|
|
2133
|
+
version: 1,
|
|
2134
|
+
provenance: {
|
|
2135
|
+
source: 'labre-convention',
|
|
2136
|
+
reference: 'Labre convention — OMG UML 2.5.1 §15.6.4 draws the swimlane; requiring an action to be inside one once lanes exist is membership on this canvas',
|
|
2137
|
+
},
|
|
2138
|
+
backgroundRole: UML_ROLE.partition,
|
|
2139
|
+
};
|
|
2140
|
+
/**
|
|
2141
|
+
* **U21 / U22** — a history pseudostate drawn outside every composite state.
|
|
2142
|
+
*
|
|
2143
|
+
* §14.2.3: a shallow history "represents the most recent active substate of its
|
|
2144
|
+
* containing Region" and a deep history the most recent configuration of the
|
|
2145
|
+
* whole sub-machine. Both are statements ABOUT a region, and a region on this
|
|
2146
|
+
* canvas is the `umlRegion` box the author drew (`roles.ts` records why the
|
|
2147
|
+
* composite state IS the frame). An `H` circle floating on the top-level sheet
|
|
2148
|
+
* therefore remembers the history of nothing.
|
|
2149
|
+
*
|
|
2150
|
+
* ## TWO rules for one sentence, and the reason is the family
|
|
2151
|
+
*
|
|
2152
|
+
* `element-in-background` names ONE subject role, and the vocabulary has no
|
|
2153
|
+
* parent meaning "a history": `uml:shallow-history` and `uml:deep-history` are
|
|
2154
|
+
* flat siblings under `uml:pseudostate`, which also covers the choice, the
|
|
2155
|
+
* junction, the two connection points and the terminate cross — none of which is
|
|
2156
|
+
* confined to a region. A rule written on the parent would indict every choice
|
|
2157
|
+
* diamond on a flat state machine, which is most of them.
|
|
2158
|
+
*
|
|
2159
|
+
* So the pack ships the sentence twice, and nothing is reported twice: an element
|
|
2160
|
+
* carries one role, so exactly one of the two can ever be about it. The same
|
|
2161
|
+
* split {@link initialNoIncomingFlow} and {@link initialNoIncomingTransition}
|
|
2162
|
+
* make for the other reason a family names one thing at a time.
|
|
2163
|
+
*
|
|
2164
|
+
* The provenance is `recommendation` rather than `standard` because the step from
|
|
2165
|
+
* §14.2.3's OWNERSHIP to this canvas's CONTAINMENT is ours: the metamodel says
|
|
2166
|
+
* the Pseudostate belongs to a Region, and reading a drawn box as that ownership
|
|
2167
|
+
* is the reading `roles.ts` chose, not a clause.
|
|
2168
|
+
*/
|
|
2169
|
+
const shallowHistoryOutsideRegion = {
|
|
2170
|
+
id: 'uml.shallow-history-outside-region',
|
|
2171
|
+
framework: 'uml',
|
|
2172
|
+
family: 'element-in-background',
|
|
2173
|
+
severity: 'audit',
|
|
2174
|
+
appliesTo: UML_ROLE['shallow-history'],
|
|
2175
|
+
roles: UML_ROLES,
|
|
2176
|
+
messageKey: 'com.labre.uml.validation.shallow-history-outside-region',
|
|
2177
|
+
messageFallback: 'This history is drawn outside every composite state.',
|
|
2178
|
+
suggestionKey: 'com.labre.uml.validation.shallow-history-outside-region.suggestion',
|
|
2179
|
+
suggestionFallback: 'A history remembers where a composite state was when it was last left, so it has to be drawn inside the one it remembers. Move the H into the composite state, or draw the composite state round it.',
|
|
2180
|
+
version: 1,
|
|
2181
|
+
provenance: {
|
|
2182
|
+
source: 'recommendation',
|
|
2183
|
+
reference: 'OMG UML 2.5.1 §14.2.3 — a shallowHistory Pseudostate represents the most recent active substate of its containing Region',
|
|
2184
|
+
},
|
|
2185
|
+
backgroundRole: UML_ROLE.region,
|
|
2186
|
+
};
|
|
2187
|
+
const deepHistoryOutsideRegion = {
|
|
2188
|
+
id: 'uml.deep-history-outside-region',
|
|
2189
|
+
framework: 'uml',
|
|
2190
|
+
family: 'element-in-background',
|
|
2191
|
+
severity: 'audit',
|
|
2192
|
+
appliesTo: UML_ROLE['deep-history'],
|
|
2193
|
+
roles: UML_ROLES,
|
|
2194
|
+
messageKey: 'com.labre.uml.validation.deep-history-outside-region',
|
|
2195
|
+
messageFallback: 'This deep history is drawn outside every composite state.',
|
|
2196
|
+
suggestionKey: 'com.labre.uml.validation.deep-history-outside-region.suggestion',
|
|
2197
|
+
suggestionFallback: 'A deep history restores the whole configuration a composite state was last in, so it has to be drawn inside the one it restores. Move the H* into the composite state, or draw the composite state round it.',
|
|
2198
|
+
version: 1,
|
|
2199
|
+
provenance: {
|
|
2200
|
+
source: 'recommendation',
|
|
2201
|
+
reference: 'OMG UML 2.5.1 §14.2.3 — a deepHistory Pseudostate represents the most recent active state configuration of its containing Region',
|
|
2202
|
+
},
|
|
2203
|
+
backgroundRole: UML_ROLE.region,
|
|
2204
|
+
};
|
|
2205
|
+
/* ── Behaviour: how many beginnings, and how many lines reach one ───────── */
|
|
2206
|
+
/**
|
|
2207
|
+
* **U23** — a second filled disc on one sheet.
|
|
2208
|
+
*
|
|
2209
|
+
* The one rule in this pack whose authority differs between the two sheets it
|
|
2210
|
+
* speaks on, and it says so rather than picking the flattering half:
|
|
2211
|
+
*
|
|
2212
|
+
* - on a **state machine**, §14.5.6.4 is normative and arithmetic — "A Region
|
|
2213
|
+
* can have at most one initial Vertex";
|
|
2214
|
+
* - on an **activity**, §15.3.3 says the opposite in as many words — "An
|
|
2215
|
+
* Activity may have more than one InitialNode", each one starting a concurrent
|
|
2216
|
+
* flow.
|
|
2217
|
+
*
|
|
2218
|
+
* So the provenance is `recommendation`: a second disc on an activity sheet is a
|
|
2219
|
+
* drawing the specification permits, and most of the ones anybody draws are a
|
|
2220
|
+
* disc the author forgot to delete rather than a deliberate concurrent start. The
|
|
2221
|
+
* rule offers the reading and never claims UML forbids it, which is the whole
|
|
2222
|
+
* point of the field.
|
|
2223
|
+
*
|
|
2224
|
+
* ## Counted per DIAGRAM, and the refinement that is not phase 2's
|
|
2225
|
+
*
|
|
2226
|
+
* §14.5.6.4's bound is per REGION, and a state machine with three composite
|
|
2227
|
+
* states legitimately holds four initial discs — one for the top level and one
|
|
2228
|
+
* inside each. `role-count` counts per instance of ONE frame, named by
|
|
2229
|
+
* `backgroundRole`, and this rule names the diagram: the per-region reading needs
|
|
2230
|
+
* the same bound counted against `uml:region` instances as well, which is a
|
|
2231
|
+
* second rule of the same family and a PHASE 3 refinement. Until it lands a
|
|
2232
|
+
* composite state with its own beginning is a finding here, which is the wrong
|
|
2233
|
+
* verdict on a right drawing — and the reason this rule is `audit` in the default
|
|
2234
|
+
* profile like everything else, rather than something a user has to argue with.
|
|
2235
|
+
*
|
|
2236
|
+
* The finding lands on the FRAME, because no disc is at fault: there are simply
|
|
2237
|
+
* two, and `role-count` raises on the instance so an arbitration made on one
|
|
2238
|
+
* sheet covers that sheet alone.
|
|
2239
|
+
*/
|
|
2240
|
+
const initialSingle = {
|
|
2241
|
+
id: 'uml.initial-single',
|
|
2242
|
+
framework: 'uml',
|
|
2243
|
+
family: 'role-count',
|
|
2244
|
+
severity: 'audit',
|
|
2245
|
+
// No `appliesTo`: `role-count` names its subject in `roleCount.subject`, and
|
|
2246
|
+
// the finding is about the frame rather than about any one element.
|
|
2247
|
+
roles: UML_ROLES,
|
|
2248
|
+
messageKey: 'com.labre.uml.validation.initial-single',
|
|
2249
|
+
messageFallback: 'This sheet draws more than one beginning.',
|
|
2250
|
+
suggestionKey: 'com.labre.uml.validation.initial-single.suggestion',
|
|
2251
|
+
suggestionFallback: 'A state machine region has at most one initial vertex, and a reader looks for one place the behaviour starts. Keep the disc the flow really begins at and delete the others — unless the sheet is an activity deliberately starting several concurrent flows, which UML allows.',
|
|
2252
|
+
version: 1,
|
|
2253
|
+
provenance: {
|
|
2254
|
+
source: 'recommendation',
|
|
2255
|
+
reference: 'OMG UML 2.5.1 §14.5.6.4 — "A Region can have at most one initial Vertex"; §15.3.3 allows an Activity more than one InitialNode, so on an activity sheet this is a reading rather than a clause',
|
|
2256
|
+
},
|
|
2257
|
+
backgroundRole: UML_ROLE.diagram,
|
|
2258
|
+
roleCount: {
|
|
2259
|
+
subject: UML_ROLE.initial,
|
|
2260
|
+
max: 1,
|
|
2261
|
+
},
|
|
2262
|
+
};
|
|
2263
|
+
/**
|
|
2264
|
+
* **U24** — a control flow pointing AT the disc the flow begins at.
|
|
2265
|
+
*
|
|
2266
|
+
* §15.3.3, verbatim: "An InitialNode shall not have any incoming ActivityEdges."
|
|
2267
|
+
* An arrow into the black disc is almost always an arrow drawn the wrong way
|
|
2268
|
+
* round, and the fix is one drag.
|
|
2269
|
+
*
|
|
2270
|
+
* ## Two rules, one sentence, and the family is the reason
|
|
2271
|
+
*
|
|
2272
|
+
* `edge-degree` counts ONE edge role. The same disc is §14.2.4's initial
|
|
2273
|
+
* Pseudostate on a state machine, where the arrow that must not exist is a
|
|
2274
|
+
* TRANSITION and not a control flow, so the requirement is two counts over two
|
|
2275
|
+
* vocabularies. They never double-report — an edge carries one role, and the two
|
|
2276
|
+
* roles are flat siblings — and they carry different authority, which is the
|
|
2277
|
+
* second reason to keep them apart: §15.3.3 states the activity half outright,
|
|
2278
|
+
* while the state machine half is a reading of §14.2.3 (see
|
|
2279
|
+
* {@link initialNoIncomingTransition}).
|
|
2280
|
+
*
|
|
2281
|
+
* ## The object flow is not counted, and that is deliberate
|
|
2282
|
+
*
|
|
2283
|
+
* §15.3.3's next sentence — "The outgoing ActivityEdges of an InitialNode must
|
|
2284
|
+
* all be ControlFlows" — would make an object flow at either end of a disc a
|
|
2285
|
+
* finding too. It is not asked here: a third rule would be a third bracket on the
|
|
2286
|
+
* same glyph, and an object flow drawn onto a control node is already
|
|
2287
|
+
* {@link objectFlowEndpoints}' business through its own matrix.
|
|
2288
|
+
*/
|
|
2289
|
+
const initialNoIncomingFlow = {
|
|
2290
|
+
id: 'uml.initial-no-incoming-flow',
|
|
2291
|
+
framework: 'uml',
|
|
2292
|
+
family: 'edge-degree',
|
|
2293
|
+
severity: 'audit',
|
|
2294
|
+
appliesTo: UML_ROLE.initial,
|
|
2295
|
+
roles: UML_ROLES,
|
|
2296
|
+
messageKey: 'com.labre.uml.validation.initial-no-incoming-flow',
|
|
2297
|
+
messageFallback: 'A control flow points at the node this activity begins at.',
|
|
2298
|
+
suggestionKey: 'com.labre.uml.validation.initial-no-incoming-flow.suggestion',
|
|
2299
|
+
suggestionFallback: 'The filled disc is where the flow starts, so nothing arrives at it. Turn the arrow round, or point it at the first action instead — and if the flow really does come back here, it comes back to that action.',
|
|
2300
|
+
version: 1,
|
|
2301
|
+
provenance: {
|
|
2302
|
+
source: 'standard',
|
|
2303
|
+
reference: 'OMG UML 2.5.1 §15.3.3 — "An InitialNode shall not have any incoming ActivityEdges"',
|
|
2304
|
+
},
|
|
2305
|
+
backgroundRole: UML_ROLE.diagram,
|
|
2306
|
+
degree: {
|
|
2307
|
+
edgeRole: UML_ROLE['control-flow'],
|
|
2308
|
+
maxIn: 0,
|
|
2309
|
+
},
|
|
2310
|
+
};
|
|
2311
|
+
/**
|
|
2312
|
+
* **U25** — a transition pointing AT the disc a state machine begins at.
|
|
2313
|
+
*
|
|
2314
|
+
* {@link initialNoIncomingFlow}'s twin over the state machine vocabulary. §14.2.3
|
|
2315
|
+
* describes the initial Pseudostate as "a starting point for a Region; that is,
|
|
2316
|
+
* it is the point from which execution begins", and §14.5.6.4 gives it at most
|
|
2317
|
+
* one OUTGOING transition — the clause constrains what leaves and says nothing
|
|
2318
|
+
* about what arrives, because a Vertex nothing can reach is not a case the
|
|
2319
|
+
* metamodel needed to exclude.
|
|
2320
|
+
*
|
|
2321
|
+
* So the provenance is `recommendation` where its activity twin is `standard`,
|
|
2322
|
+
* and the two rules are a small demonstration of why the field exists: the same
|
|
2323
|
+
* drawing, the same fix, and one of them is a conformance defect while the other
|
|
2324
|
+
* is the tool being helpful.
|
|
2325
|
+
*/
|
|
2326
|
+
const initialNoIncomingTransition = {
|
|
2327
|
+
id: 'uml.initial-no-incoming-transition',
|
|
2328
|
+
framework: 'uml',
|
|
2329
|
+
family: 'edge-degree',
|
|
2330
|
+
severity: 'audit',
|
|
2331
|
+
appliesTo: UML_ROLE.initial,
|
|
2332
|
+
roles: UML_ROLES,
|
|
2333
|
+
messageKey: 'com.labre.uml.validation.initial-no-incoming-transition',
|
|
2334
|
+
messageFallback: 'A transition points at the node this state machine begins at.',
|
|
2335
|
+
suggestionKey: 'com.labre.uml.validation.initial-no-incoming-transition.suggestion',
|
|
2336
|
+
suggestionFallback: 'The filled disc is where the machine starts, so nothing transitions into it. Point the arrow at the state the machine should return to instead.',
|
|
2337
|
+
version: 1,
|
|
2338
|
+
provenance: {
|
|
2339
|
+
source: 'recommendation',
|
|
2340
|
+
reference: 'OMG UML 2.5.1 §14.2.3 — an initial Pseudostate is the point from which execution begins; §14.5.6.4 constrains what leaves it and not what arrives',
|
|
2341
|
+
},
|
|
2342
|
+
backgroundRole: UML_ROLE.diagram,
|
|
2343
|
+
degree: {
|
|
2344
|
+
edgeRole: UML_ROLE.transition,
|
|
2345
|
+
maxIn: 0,
|
|
2346
|
+
},
|
|
2347
|
+
};
|
|
2348
|
+
/**
|
|
2349
|
+
* **U26 / U27** — a flow leaving a node that ends one.
|
|
2350
|
+
*
|
|
2351
|
+
* §15.7.19.4, verbatim and for both of them: "A FinalNode has no outgoing
|
|
2352
|
+
* ActivityEdges." An activity final stops every flow in the activity and a flow
|
|
2353
|
+
* final destroys the one token that reached it — in neither case is there
|
|
2354
|
+
* anything left to leave.
|
|
2355
|
+
*
|
|
2356
|
+
* TWO rules again, and this time because the vocabulary is flat where the
|
|
2357
|
+
* metamodel is not: §15.7.19 makes ActivityFinalNode and FlowFinalNode two
|
|
2358
|
+
* FinalNodes, and `roles.ts` files both directly under `uml:control-node`
|
|
2359
|
+
* alongside the initial disc, the decision and the fork — so there is no
|
|
2360
|
+
* ancestor meaning "a final" that does not also reach the four glyphs a flow
|
|
2361
|
+
* legitimately leaves. Naming the two is the only reading that is right about
|
|
2362
|
+
* both.
|
|
2363
|
+
*
|
|
2364
|
+
* Their sentences differ, and deliberately: what a reader has to understand is
|
|
2365
|
+
* different. An arrow off an activity final says the author thinks the activity
|
|
2366
|
+
* continues; an arrow off a flow final says they think one branch does.
|
|
2367
|
+
*/
|
|
2368
|
+
const activityFinalNoOutgoing = {
|
|
2369
|
+
id: 'uml.activity-final-no-outgoing',
|
|
2370
|
+
framework: 'uml',
|
|
2371
|
+
family: 'edge-degree',
|
|
2372
|
+
severity: 'audit',
|
|
2373
|
+
appliesTo: UML_ROLE['activity-final'],
|
|
2374
|
+
roles: UML_ROLES,
|
|
2375
|
+
messageKey: 'com.labre.uml.validation.activity-final-no-outgoing',
|
|
2376
|
+
messageFallback: 'A control flow leaves the node this activity ends at.',
|
|
2377
|
+
suggestionKey: 'com.labre.uml.validation.activity-final-no-outgoing.suggestion',
|
|
2378
|
+
suggestionFallback: 'The bullseye stops every flow in the activity, so nothing continues past it. Draw the arrow from the last action instead — or, if only this branch ends here, use the flow final (the circle with the cross).',
|
|
2379
|
+
version: 1,
|
|
2380
|
+
provenance: {
|
|
2381
|
+
source: 'standard',
|
|
2382
|
+
reference: 'OMG UML 2.5.1 §15.7.19.4 — "A FinalNode has no outgoing ActivityEdges"',
|
|
2383
|
+
},
|
|
2384
|
+
backgroundRole: UML_ROLE.diagram,
|
|
2385
|
+
degree: {
|
|
2386
|
+
edgeRole: UML_ROLE['control-flow'],
|
|
2387
|
+
maxOut: 0,
|
|
2388
|
+
},
|
|
2389
|
+
};
|
|
2390
|
+
const flowFinalNoOutgoing = {
|
|
2391
|
+
id: 'uml.flow-final-no-outgoing',
|
|
2392
|
+
framework: 'uml',
|
|
2393
|
+
family: 'edge-degree',
|
|
2394
|
+
severity: 'audit',
|
|
2395
|
+
appliesTo: UML_ROLE['flow-final'],
|
|
2396
|
+
roles: UML_ROLES,
|
|
2397
|
+
messageKey: 'com.labre.uml.validation.flow-final-no-outgoing',
|
|
2398
|
+
messageFallback: 'A control flow leaves the node this branch ends at.',
|
|
2399
|
+
suggestionKey: 'com.labre.uml.validation.flow-final-no-outgoing.suggestion',
|
|
2400
|
+
suggestionFallback: 'The circle with the cross destroys the token that reaches it, so this branch stops there. Draw the arrow from the action before it, or delete the flow final if the branch does carry on.',
|
|
2401
|
+
version: 1,
|
|
2402
|
+
provenance: {
|
|
2403
|
+
source: 'standard',
|
|
2404
|
+
reference: 'OMG UML 2.5.1 §15.7.19.4 — "A FinalNode has no outgoing ActivityEdges"',
|
|
2405
|
+
},
|
|
2406
|
+
backgroundRole: UML_ROLE.diagram,
|
|
2407
|
+
degree: {
|
|
2408
|
+
edgeRole: UML_ROLE['control-flow'],
|
|
2409
|
+
maxOut: 0,
|
|
2410
|
+
},
|
|
2411
|
+
};
|
|
2412
|
+
/**
|
|
2413
|
+
* **U28** — a transition leaving a final state.
|
|
2414
|
+
*
|
|
2415
|
+
* §14.5.11.4, verbatim: "A FinalState cannot have any outgoing Transitions." The
|
|
2416
|
+
* same shape as the two above, one notation over, and the same one-drag fix.
|
|
2417
|
+
*/
|
|
2418
|
+
const finalStateNoOutgoing = {
|
|
2419
|
+
id: 'uml.final-state-no-outgoing',
|
|
2420
|
+
framework: 'uml',
|
|
2421
|
+
family: 'edge-degree',
|
|
2422
|
+
severity: 'audit',
|
|
2423
|
+
appliesTo: UML_ROLE['final-state'],
|
|
2424
|
+
roles: UML_ROLES,
|
|
2425
|
+
messageKey: 'com.labre.uml.validation.final-state-no-outgoing',
|
|
2426
|
+
messageFallback: 'A transition leaves this final state.',
|
|
2427
|
+
suggestionKey: 'com.labre.uml.validation.final-state-no-outgoing.suggestion',
|
|
2428
|
+
suggestionFallback: 'A final state is where the machine stops, so nothing transitions out of it. Draw the transition from the state before it — and if the machine really does carry on, this is not the end.',
|
|
2429
|
+
version: 1,
|
|
2430
|
+
provenance: {
|
|
2431
|
+
source: 'standard',
|
|
2432
|
+
reference: 'OMG UML 2.5.1 §14.5.11.4 — "A FinalState cannot have any outgoing Transitions"',
|
|
2433
|
+
},
|
|
2434
|
+
backgroundRole: UML_ROLE.diagram,
|
|
2435
|
+
degree: {
|
|
2436
|
+
edgeRole: UML_ROLE.transition,
|
|
2437
|
+
maxOut: 0,
|
|
2438
|
+
},
|
|
2439
|
+
};
|
|
2440
|
+
/**
|
|
2441
|
+
* **U29** — a decision diamond fed by three arrows.
|
|
2442
|
+
*
|
|
2443
|
+
* §15.7.11.4, and it is arithmetic: "A DecisionNode has one or two incoming
|
|
2444
|
+
* ActivityEdges and at least one outgoing ActivityEdge." TWO rather than one,
|
|
2445
|
+
* because the second incoming edge has a job — §15.3.4's `decisionInputFlow`
|
|
2446
|
+
* carries the value the guards are evaluated against — and a diamond with three
|
|
2447
|
+
* arrows into it is a merge somebody drew as a decision.
|
|
2448
|
+
*
|
|
2449
|
+
* ## `maxIn: 2`, and not the `maxIn: 1` a reader might expect
|
|
2450
|
+
*
|
|
2451
|
+
* Worth stating because the id would let you assume otherwise and because the
|
|
2452
|
+
* brief this rule was written from asked for one. The clause says two, the second
|
|
2453
|
+
* one is a real and named feature of the notation, and data that contradicts the
|
|
2454
|
+
* clause it cites is the one thing this pack cannot ship. The id names the
|
|
2455
|
+
* QUESTION — how many arrive — rather than the bound.
|
|
2456
|
+
*
|
|
2457
|
+
* ## The floor is not asked for
|
|
2458
|
+
*
|
|
2459
|
+
* "At least one incoming" would fire on every diamond for the whole of the time
|
|
2460
|
+
* between dropping it and joining it up, which is the definition of a rule that
|
|
2461
|
+
* argues with a croquis. The ceiling is the half that reports a mistake rather
|
|
2462
|
+
* than an unfinished drawing.
|
|
2463
|
+
*/
|
|
2464
|
+
const decisionIncomingCount = {
|
|
2465
|
+
id: 'uml.decision-incoming-count',
|
|
2466
|
+
framework: 'uml',
|
|
2467
|
+
family: 'edge-degree',
|
|
2468
|
+
severity: 'audit',
|
|
2469
|
+
appliesTo: UML_ROLE.decision,
|
|
2470
|
+
roles: UML_ROLES,
|
|
2471
|
+
messageKey: 'com.labre.uml.validation.decision-incoming-count',
|
|
2472
|
+
messageFallback: 'More than two control flows arrive at this decision.',
|
|
2473
|
+
suggestionKey: 'com.labre.uml.validation.decision-incoming-count.suggestion',
|
|
2474
|
+
suggestionFallback: 'A decision takes the flow it branches and, at most, a second one carrying the value its guards test. If these arrows are branches coming back together, draw a merge — the same diamond, with the arrows leaving it joined into one.',
|
|
2475
|
+
version: 1,
|
|
2476
|
+
provenance: {
|
|
2477
|
+
source: 'standard',
|
|
2478
|
+
reference: 'OMG UML 2.5.1 §15.7.11.4 — "A DecisionNode has one or two incoming ActivityEdges and at least one outgoing ActivityEdge"',
|
|
2479
|
+
},
|
|
2480
|
+
backgroundRole: UML_ROLE.diagram,
|
|
2481
|
+
degree: {
|
|
2482
|
+
edgeRole: UML_ROLE['control-flow'],
|
|
2483
|
+
// TWO, which is what the clause says — see the header on why the id does not
|
|
2484
|
+
// name the bound.
|
|
2485
|
+
maxIn: 2,
|
|
2486
|
+
},
|
|
2487
|
+
};
|
|
2488
|
+
/* ── Behaviour: what each flow may run between ──────────────────────────── */
|
|
2489
|
+
/**
|
|
2490
|
+
* **U30** — a control flow with an object node, a state or a class at one end.
|
|
2491
|
+
*
|
|
2492
|
+
* §15.7.9.4's `object_nodes` constraint, and the alphabet entries that let this
|
|
2493
|
+
* rule reach the two confusions a mixed board actually produces: a control flow
|
|
2494
|
+
* dragged onto a STATE (the state machine sheet's vocabulary, one tool away in
|
|
2495
|
+
* the same menu) and a control flow dragged onto a CLASS.
|
|
2496
|
+
*
|
|
2497
|
+
* The object node is the clause's own case and the one with an exception: an
|
|
2498
|
+
* ObjectNode whose `isControlType` is true may sit on a control flow, and
|
|
2499
|
+
* §15.4.4 gives that property no notation at all, so a drawing cannot state it.
|
|
2500
|
+
* The suggestion names the exception rather than hiding it, which is what keeps
|
|
2501
|
+
* the finding honest at `audit` — and what makes this the endpoint rule a reader
|
|
2502
|
+
* may reasonably overrule.
|
|
2503
|
+
*/
|
|
2504
|
+
const controlFlowEndpoints = {
|
|
2505
|
+
id: 'uml.control-flow-endpoints',
|
|
2506
|
+
framework: 'uml',
|
|
2507
|
+
family: 'relation-endpoints',
|
|
2508
|
+
severity: 'audit',
|
|
2509
|
+
roles: UML_ROLES,
|
|
2510
|
+
messageKey: 'com.labre.uml.validation.control-flow-endpoints',
|
|
2511
|
+
messageFallback: 'This control flow runs between artefacts an activity does not route control through.',
|
|
2512
|
+
suggestionKey: 'com.labre.uml.validation.control-flow-endpoints.suggestion',
|
|
2513
|
+
suggestionFallback: 'A control flow joins actions, control nodes and the signal artefacts. Draw an object flow if what travels is data, a transition if these are states — and if this really is a control-typed object node, the notation has no way of saying so and the remark can be waived.',
|
|
2514
|
+
version: 1,
|
|
2515
|
+
provenance: {
|
|
2516
|
+
source: 'standard',
|
|
2517
|
+
reference: 'OMG UML 2.5.1 §15.7.9.4 — "ControlFlows may not have ObjectNodes at either end, except for ObjectNodes with control type"',
|
|
2518
|
+
},
|
|
2519
|
+
backgroundRole: UML_ROLE.diagram,
|
|
2520
|
+
endpoints: {
|
|
2521
|
+
edgeRole: UML_ROLE['control-flow'],
|
|
2522
|
+
allowed: UML_CONTROL_FLOW_MATRIX,
|
|
2523
|
+
},
|
|
2524
|
+
};
|
|
2525
|
+
/**
|
|
2526
|
+
* **U31** — an object flow that touches no data.
|
|
2527
|
+
*
|
|
2528
|
+
* §15.7.11.4's `same_upper_bounds` describes an object flow as a line between
|
|
2529
|
+
* ObjectNodes "with optionally intervening ControlNodes", so at least one end of
|
|
2530
|
+
* one is data. See {@link UML_OBJECT_FLOW_MATRIX} for why the reading is the
|
|
2531
|
+
* loose one, why the rule is a `recommendation` and why `uml.strict` is the one
|
|
2532
|
+
* endpoint rule it leaves alone: §15.4.4's elided PINS make the action-to-action
|
|
2533
|
+
* drawing a legal shorthand, and this canvas has no pin to draw.
|
|
2534
|
+
*/
|
|
2535
|
+
const objectFlowEndpoints = {
|
|
2536
|
+
id: 'uml.object-flow-endpoints',
|
|
2537
|
+
framework: 'uml',
|
|
2538
|
+
family: 'relation-endpoints',
|
|
2539
|
+
severity: 'audit',
|
|
2540
|
+
roles: UML_ROLES,
|
|
2541
|
+
messageKey: 'com.labre.uml.validation.object-flow-endpoints',
|
|
2542
|
+
messageFallback: 'Neither end of this object flow is a data node.',
|
|
2543
|
+
suggestionKey: 'com.labre.uml.validation.object-flow-endpoints.suggestion',
|
|
2544
|
+
suggestionFallback: 'An object flow carries something: draw the object node it carries between the two actions, and name what travels. If what you meant is "this happens, then that happens", draw a control flow instead.',
|
|
2545
|
+
version: 1,
|
|
2546
|
+
provenance: {
|
|
2547
|
+
source: 'recommendation',
|
|
2548
|
+
reference: 'OMG UML 2.5.1 §15.7.11.4 — ObjectNodes connected by an ObjectFlow, with optionally intervening ControlNodes; §15.4.4 elides the Pins that would be those nodes, so the action-to-action drawing is a shorthand rather than a defect',
|
|
2549
|
+
},
|
|
2550
|
+
backgroundRole: UML_ROLE.diagram,
|
|
2551
|
+
endpoints: {
|
|
2552
|
+
edgeRole: UML_ROLE['object-flow'],
|
|
2553
|
+
allowed: UML_OBJECT_FLOW_MATRIX,
|
|
2554
|
+
},
|
|
2555
|
+
};
|
|
2556
|
+
/**
|
|
2557
|
+
* **U32** — a transition drawn onto something that is not a vertex.
|
|
2558
|
+
*
|
|
2559
|
+
* §14.5.12: a Transition runs from one Vertex to another, and a Vertex is a
|
|
2560
|
+
* State, a FinalState or a Pseudostate. The finding worth having is the one the
|
|
2561
|
+
* alphabet entries buy — a transition dragged onto a DECISION diamond, which is
|
|
2562
|
+
* §15.3.4's branch where §14.2.4's is the `<<choice>>`, and the commonest
|
|
2563
|
+
* confusion between these two sheets by a distance.
|
|
2564
|
+
*
|
|
2565
|
+
* See {@link UML_TRANSITION_MATRIX} for why the table over the vertices is the
|
|
2566
|
+
* full square rather than a grammar: the counts are owned by the degree rules,
|
|
2567
|
+
* and one wrong arrow gets one bracket.
|
|
2568
|
+
*/
|
|
2569
|
+
const transitionEndpoints = {
|
|
2570
|
+
id: 'uml.transition-endpoints',
|
|
2571
|
+
framework: 'uml',
|
|
2572
|
+
family: 'relation-endpoints',
|
|
2573
|
+
severity: 'audit',
|
|
2574
|
+
roles: UML_ROLES,
|
|
2575
|
+
messageKey: 'com.labre.uml.validation.transition-endpoints',
|
|
2576
|
+
messageFallback: 'This transition runs between artefacts a state machine has no transitions between.',
|
|
2577
|
+
suggestionKey: 'com.labre.uml.validation.transition-endpoints.suggestion',
|
|
2578
|
+
suggestionFallback: 'A transition joins states, final states and the pseudostates that route between them. A diamond on a state machine is the choice pseudostate, not the activity decision — swap it, or draw a control flow if this sheet is really an activity.',
|
|
2579
|
+
version: 1,
|
|
2580
|
+
provenance: {
|
|
2581
|
+
source: 'standard',
|
|
2582
|
+
reference: 'OMG UML 2.5.1 §14.5.12 — "A Transition is a single directed arc originating from a single source Vertex and terminating on a single target Vertex"',
|
|
2583
|
+
},
|
|
2584
|
+
backgroundRole: UML_ROLE.diagram,
|
|
2585
|
+
endpoints: {
|
|
2586
|
+
edgeRole: UML_ROLE.transition,
|
|
2587
|
+
allowed: UML_TRANSITION_MATRIX,
|
|
2588
|
+
},
|
|
2589
|
+
};
|
|
2590
|
+
/* ── Behaviour: can the flow get there at all? ──────────────────────────── */
|
|
2591
|
+
/**
|
|
2592
|
+
* **U33 / U34** — an action, or a state, no walk from the beginning ever
|
|
2593
|
+
* reaches.
|
|
2594
|
+
*
|
|
2595
|
+
* The orphan question, and the first one this pack asks that no amount of
|
|
2596
|
+
* looking at an element or at one line can answer: an action with a perfectly
|
|
2597
|
+
* good control flow on either side is still unreachable if the chain it belongs
|
|
2598
|
+
* to never starts anywhere. BPMN's `unreachable-step` is the same rule one
|
|
2599
|
+
* notation over, and the reasoning it records applies here whole.
|
|
2600
|
+
*
|
|
2601
|
+
* `implicitRoots` is on, and it is what makes the rules shippable: a node nothing
|
|
2602
|
+
* points at IS a beginning whether or not anybody drew the disc, so a second
|
|
2603
|
+
* branch drawn beside the first — and every activity in the thirty seconds before
|
|
2604
|
+
* its initial node goes down — is left alone. What survives is the real defect: a
|
|
2605
|
+
* RING entered from nowhere, where every step is pointed at and no walk reaches
|
|
2606
|
+
* any of them.
|
|
2607
|
+
*
|
|
2608
|
+
* ## Two rules, for the two vocabularies
|
|
2609
|
+
*
|
|
2610
|
+
* `reachability` follows ONE edge role, so the activity walk (control flow) and
|
|
2611
|
+
* the state machine walk (transition) are two declarations. Both start from
|
|
2612
|
+
* `uml:initial`, which is the one role the two sheets genuinely share, and both
|
|
2613
|
+
* are silent on a board carrying none of either kind of root.
|
|
2614
|
+
*
|
|
2615
|
+
* ## The subjects are the ACTION and the STATE, and nothing else
|
|
2616
|
+
*
|
|
2617
|
+
* `reachability` names one subject role. The object nodes, the signal artefacts
|
|
2618
|
+
* and the pseudostates are not walked for — a narrower rule that is right is
|
|
2619
|
+
* worth more than a wider one that guesses, and the artefact a reader asks "how
|
|
2620
|
+
* do we get here?" about is the step and the state.
|
|
2621
|
+
*
|
|
2622
|
+
* ## `on-demand`, for BPMN's reason exactly
|
|
2623
|
+
*
|
|
2624
|
+
* A graph sweep is O(V + E), rebuilds its adjacency every evaluation and cannot
|
|
2625
|
+
* be made incremental even in principle — reachability is global, so re-pointing
|
|
2626
|
+
* one arrow can orphan or rescue an arbitrary number of steps nowhere near it. It
|
|
2627
|
+
* stays out of the drawing budget and runs when somebody asks whether the diagram
|
|
2628
|
+
* is finished, which is also the only moment the answer is worth anything.
|
|
2629
|
+
*/
|
|
2630
|
+
const unreachableAction = {
|
|
2631
|
+
id: 'uml.unreachable-action',
|
|
2632
|
+
framework: 'uml',
|
|
2633
|
+
family: 'reachability',
|
|
2634
|
+
severity: 'audit',
|
|
2635
|
+
roles: UML_ROLES,
|
|
2636
|
+
messageKey: 'com.labre.uml.validation.unreachable-action',
|
|
2637
|
+
messageFallback: 'No flow from the beginning of this activity reaches here.',
|
|
2638
|
+
suggestionKey: 'com.labre.uml.validation.unreachable-action.suggestion',
|
|
2639
|
+
suggestionFallback: 'Follow the control flows back from this action: somewhere the chain stops, or an arrow points the wrong way. Join it to the flow, or delete it if it no longer happens.',
|
|
2640
|
+
version: 1,
|
|
2641
|
+
provenance: {
|
|
2642
|
+
source: 'recommendation',
|
|
2643
|
+
reference: 'Best practice — OMG UML 2.5.1 §15.3.3 seeds the flow at the InitialNodes and requires the reachability of nobody',
|
|
2644
|
+
},
|
|
2645
|
+
moment: 'on-demand',
|
|
2646
|
+
backgroundRole: UML_ROLE.diagram,
|
|
2647
|
+
reachability: {
|
|
2648
|
+
rootRole: UML_ROLE.initial,
|
|
2649
|
+
subjectRole: UML_ROLE.action,
|
|
2650
|
+
edgeRole: UML_ROLE['control-flow'],
|
|
2651
|
+
// An action nothing points at IS a beginning, whether or not anybody drew
|
|
2652
|
+
// the disc — see the header.
|
|
2653
|
+
implicitRoots: true,
|
|
2654
|
+
},
|
|
2655
|
+
};
|
|
2656
|
+
const unreachableState = {
|
|
2657
|
+
id: 'uml.unreachable-state',
|
|
2658
|
+
framework: 'uml',
|
|
2659
|
+
family: 'reachability',
|
|
2660
|
+
severity: 'audit',
|
|
2661
|
+
roles: UML_ROLES,
|
|
2662
|
+
messageKey: 'com.labre.uml.validation.unreachable-state',
|
|
2663
|
+
messageFallback: 'No transition from the beginning of this machine reaches here.',
|
|
2664
|
+
suggestionKey: 'com.labre.uml.validation.unreachable-state.suggestion',
|
|
2665
|
+
suggestionFallback: 'Follow the transitions back from this state: somewhere the chain stops, or an arrow points the wrong way. Join it to the machine, or delete it if the machine can no longer be in it.',
|
|
2666
|
+
version: 1,
|
|
2667
|
+
provenance: {
|
|
2668
|
+
source: 'recommendation',
|
|
2669
|
+
reference: 'Best practice — OMG UML 2.5.1 §14.2.3 seeds execution at the initial Pseudostate and requires the reachability of nobody',
|
|
2670
|
+
},
|
|
2671
|
+
moment: 'on-demand',
|
|
2672
|
+
backgroundRole: UML_ROLE.diagram,
|
|
2673
|
+
reachability: {
|
|
2674
|
+
rootRole: UML_ROLE.initial,
|
|
2675
|
+
subjectRole: UML_ROLE.state,
|
|
2676
|
+
edgeRole: UML_ROLE.transition,
|
|
2677
|
+
implicitRoots: true,
|
|
2678
|
+
},
|
|
2679
|
+
};
|
|
2680
|
+
/* ── The interaction: what a message may run between ────────────────────── */
|
|
2681
|
+
/**
|
|
2682
|
+
* The two artefacts a message may start from and land on — §17.4.4's sender and
|
|
2683
|
+
* receiver MessageEnds, as this canvas draws them.
|
|
2684
|
+
*
|
|
2685
|
+
* A LIFELINE is the participant itself, and an EXECUTION is the bar drawn on its
|
|
2686
|
+
* spine while it is doing something (§17.3.4: "apply a thin gray or white
|
|
2687
|
+
* rectangle that covers the Lifeline line"). Both are legal ends of the same
|
|
2688
|
+
* arrow, and which of the two an author aims at is a matter of how much detail
|
|
2689
|
+
* the drawing is carrying: the same call is drawn lifeline-to-lifeline on a
|
|
2690
|
+
* sketch and execution-to-execution once the activations are in. A grammar that
|
|
2691
|
+
* admitted one and not the other would indict half the sequence diagrams ever
|
|
2692
|
+
* drawn for being drawn at the wrong level of detail.
|
|
2693
|
+
*
|
|
2694
|
+
* The DESTRUCTION is deliberately not here: it is not an end a message may
|
|
2695
|
+
* generally reach, it is the end a DELETE message must reach, which is a
|
|
2696
|
+
* sentence of its own below.
|
|
2697
|
+
*/
|
|
2698
|
+
const UML_MESSAGE_ENDS = [
|
|
2699
|
+
UML_ROLE.lifeline,
|
|
2700
|
+
UML_ROLE.execution,
|
|
2701
|
+
];
|
|
2702
|
+
/**
|
|
2703
|
+
* The three message sorts whose ends §17.4.4 constrains no further than "a
|
|
2704
|
+
* sender and a receiver": a synchronous call, an asynchronous one and a reply.
|
|
2705
|
+
*/
|
|
2706
|
+
const UML_PLAIN_MESSAGE_ROLES = [
|
|
2707
|
+
UML_ROLE['message-sync'],
|
|
2708
|
+
UML_ROLE['message-async'],
|
|
2709
|
+
UML_ROLE['message-reply'],
|
|
2710
|
+
];
|
|
2711
|
+
/**
|
|
2712
|
+
* ALPHABET entries for the message grammar — the class-side and use-case
|
|
2713
|
+
* vocabularies, carried as true sentences of THEIR OWN edges.
|
|
2714
|
+
*
|
|
2715
|
+
* The device {@link UML_STATE_ALPHABET} explains, one sheet further on: these
|
|
2716
|
+
* triplets carry `uml:association`, so `inMatrix` — which matches a triplet's
|
|
2717
|
+
* edge with `roleIsA` — can never let one sanction a message. Their whole effect
|
|
2718
|
+
* is to put `uml:classifier`, `uml:actor` and `uml:use-case` into the alphabet
|
|
2719
|
+
* the message rule speaks, which is what makes "a message dragged onto a class"
|
|
2720
|
+
* and "a message dragged onto an actor" findings rather than silence.
|
|
2721
|
+
*
|
|
2722
|
+
* The ACTOR is the one that earns its place. Every drawing tool in the world
|
|
2723
|
+
* puts a stick figure at the left of a sequence diagram, and §17.3.4 does give a
|
|
2724
|
+
* lifeline head "a shape that is based on the classifier for the part" — so an
|
|
2725
|
+
* author reaching for the actor tool on an `sd` sheet is making a reasonable
|
|
2726
|
+
* mistake about THIS canvas, where a participant is a lifeline whatever it
|
|
2727
|
+
* represents. Being in the alphabet is what lets the finding say so.
|
|
2728
|
+
*/
|
|
2729
|
+
const UML_INTERACTION_ALPHABET = [
|
|
2730
|
+
{
|
|
2731
|
+
source: UML_ROLE.actor,
|
|
2732
|
+
edge: UML_ROLE.association,
|
|
2733
|
+
target: UML_ROLE['use-case'],
|
|
2734
|
+
},
|
|
2735
|
+
{
|
|
2736
|
+
source: UML_ROLE.classifier,
|
|
2737
|
+
edge: UML_ROLE.association,
|
|
2738
|
+
target: UML_ROLE.classifier,
|
|
2739
|
+
},
|
|
2740
|
+
];
|
|
2741
|
+
/**
|
|
2742
|
+
* What a MESSAGE may run between, sort by sort — §17.4.4, which is the one place
|
|
2743
|
+
* in Clause 17 where the notation constrains an ARROW's ends rather than its
|
|
2744
|
+
* shape.
|
|
2745
|
+
*
|
|
2746
|
+
* Three of the five sorts are the full square over {@link UML_MESSAGE_ENDS}: a
|
|
2747
|
+
* call, a signal and a reply run from a participant to a participant, and the
|
|
2748
|
+
* clause says nothing more. The other two each carry a sentence the
|
|
2749
|
+
* specification states outright:
|
|
2750
|
+
*
|
|
2751
|
+
* - a **create** message "has a dashed line with an open arrow head" and is
|
|
2752
|
+
* drawn to the HEAD of the lifeline it brings into existence (§17.4.4,
|
|
2753
|
+
* Figure 17.13), so its target is a LIFELINE and never an execution — there
|
|
2754
|
+
* is nothing to be executing on a participant that does not exist yet;
|
|
2755
|
+
* - a **delete** message "must end in a DestructionOccurrenceSpecification"
|
|
2756
|
+
* (§17.4.4, quoted in the rule's provenance), which is the X this canvas
|
|
2757
|
+
* draws as `uml:destruction`. The LIFELINE is admitted beside it, and that is
|
|
2758
|
+
* a deliberate tolerance rather than a reading of the clause: an author draws
|
|
2759
|
+
* the arrow first and drops the cross on its end afterwards, and a grammar
|
|
2760
|
+
* that indicted the intermediate state would be indicting the gesture.
|
|
2761
|
+
* `uml.strict` promotes this rule, so the tolerance is the same at both
|
|
2762
|
+
* levels — the finding that is worth having is the delete drawn onto an
|
|
2763
|
+
* EXECUTION, which is a participant carrying on after it has been destroyed.
|
|
2764
|
+
*
|
|
2765
|
+
* Self-loops are NOT forbidden, and §17.4.4 is explicit about why: "The send and
|
|
2766
|
+
* receive events may both be on the same lifeline." A participant calling itself
|
|
2767
|
+
* is the notation's own figure for a nested activation.
|
|
2768
|
+
*/
|
|
2769
|
+
export const UML_MESSAGE_MATRIX = [
|
|
2770
|
+
...UML_PLAIN_MESSAGE_ROLES.flatMap(edge => UML_MESSAGE_ENDS.flatMap(source => UML_MESSAGE_ENDS.map(target => ({ source, edge, target })))),
|
|
2771
|
+
...UML_MESSAGE_ENDS.map(source => ({
|
|
2772
|
+
source,
|
|
2773
|
+
edge: UML_ROLE['message-create'],
|
|
2774
|
+
target: UML_ROLE.lifeline,
|
|
2775
|
+
})),
|
|
2776
|
+
...UML_MESSAGE_ENDS.flatMap(source => [UML_ROLE.destruction, UML_ROLE.lifeline].map(target => ({
|
|
2777
|
+
source,
|
|
2778
|
+
edge: UML_ROLE['message-delete'],
|
|
2779
|
+
target,
|
|
2780
|
+
}))),
|
|
2781
|
+
...UML_INTERACTION_ALPHABET,
|
|
2782
|
+
];
|
|
2783
|
+
/**
|
|
2784
|
+
* **U39** — a message drawn between artefacts an interaction does not exchange
|
|
2785
|
+
* messages between.
|
|
2786
|
+
*
|
|
2787
|
+
* `edgeRole: uml:message` — the PARENT of the five sorts — so one rule covers a
|
|
2788
|
+
* call, a signal, a reply, a create and a delete, and the matrix above is what
|
|
2789
|
+
* tells them apart: each triplet names a specialised edge role, and `inMatrix`
|
|
2790
|
+
* matches it with `roleIsA`, so a create's sentence can never sanction a delete.
|
|
2791
|
+
*
|
|
2792
|
+
* One id rather than five, for the reason every other endpoint rule in this file
|
|
2793
|
+
* is one id: the five sorts are one grammar with one gesture to fix it — re-point
|
|
2794
|
+
* an end — and five rules would be five lines in both profile tables and five
|
|
2795
|
+
* sentences saying the same thing about the same drag.
|
|
2796
|
+
*
|
|
2797
|
+
* ## What it stays silent about
|
|
2798
|
+
*
|
|
2799
|
+
* A message with a free end, a message onto a NOTE, onto a sticky, onto a
|
|
2800
|
+
* rectangle somebody thought with: the alphabet is the participants plus the two
|
|
2801
|
+
* foreign vocabularies {@link UML_INTERACTION_ALPHABET} names, and everything
|
|
2802
|
+
* outside it is a sketch (PRD principle 8). And a message from a participant to
|
|
2803
|
+
* ITSELF, which §17.4.4 draws on purpose.
|
|
2804
|
+
*/
|
|
2805
|
+
const messageEndpoints = {
|
|
2806
|
+
id: 'uml.message-endpoints',
|
|
2807
|
+
framework: 'uml',
|
|
2808
|
+
family: 'relation-endpoints',
|
|
2809
|
+
severity: 'audit',
|
|
2810
|
+
roles: UML_ROLES,
|
|
2811
|
+
messageKey: 'com.labre.uml.validation.message-endpoints',
|
|
2812
|
+
messageFallback: 'This message runs between artefacts an interaction does not exchange messages between.',
|
|
2813
|
+
suggestionKey: 'com.labre.uml.validation.message-endpoints.suggestion',
|
|
2814
|
+
suggestionFallback: 'A message joins two participants — a lifeline, or the execution bar drawn on one. A create message lands on the head of the lifeline it brings into existence, and a delete message ends on the cross that destroys it. If this participant is an actor, draw it as a lifeline: a sequence sheet has one kind of participant.',
|
|
2815
|
+
version: 1,
|
|
2816
|
+
provenance: {
|
|
2817
|
+
source: 'standard',
|
|
2818
|
+
reference: 'OMG UML 2.5.1 §17.4.4 — "An object deletion Message (messageSort equals deleteMessage) must end in a DestructionOccurrenceSpecification", and §17.3.4 on what a Lifeline and an ExecutionSpecification are',
|
|
2819
|
+
},
|
|
2820
|
+
backgroundRole: UML_ROLE.diagram,
|
|
2821
|
+
endpoints: {
|
|
2822
|
+
edgeRole: UML_ROLE.message,
|
|
2823
|
+
allowed: UML_MESSAGE_MATRIX,
|
|
2824
|
+
},
|
|
2825
|
+
};
|
|
2826
|
+
/**
|
|
2827
|
+
* **U40** — a message label that does not parse.
|
|
2828
|
+
*
|
|
2829
|
+
* The fifth `label-syntax` rule (ADR 0021), and the first written on a CONNECTOR's
|
|
2830
|
+
* centre label since `uml.transition-syntax`. `appliesTo: uml:message` reaches
|
|
2831
|
+
* all five sorts for free — they specialise it — which is exactly right here:
|
|
2832
|
+
* §17.4.4 prints ONE label grammar for the request sorts and one for the reply,
|
|
2833
|
+
* the second being the first plus an assignment target and a return value, and
|
|
2834
|
+
* `checkMessageLabel` reads both because an arrow carries one text and nothing
|
|
2835
|
+
* on the canvas tells a reader which of the two productions to use.
|
|
2836
|
+
*
|
|
2837
|
+
* `perLine: false`, like the transition label and for the same reason: §17.4.4's
|
|
2838
|
+
* label is a single production, not a list, and an author who wraps
|
|
2839
|
+
* `r = place(order, now) : Receipt` onto a second line has written one label.
|
|
2840
|
+
*
|
|
2841
|
+
* ## What it stays silent about
|
|
2842
|
+
*
|
|
2843
|
+
* An UNLABELLED arrow — §17.4.4 makes the label optional and a sequence diagram
|
|
2844
|
+
* is drawn arrows-first — and `*`, which is a spelling the clause prints. See
|
|
2845
|
+
* `grammar.ts` for the four things it does report, each of them a case where the
|
|
2846
|
+
* lenient parse the exporters read LOST something the author typed.
|
|
2847
|
+
*/
|
|
2848
|
+
const messageSyntax = {
|
|
2849
|
+
id: 'uml.message-syntax',
|
|
2850
|
+
framework: 'uml',
|
|
2851
|
+
family: 'label-syntax',
|
|
2852
|
+
severity: 'audit',
|
|
2853
|
+
appliesTo: UML_ROLE.message,
|
|
2854
|
+
roles: UML_ROLES,
|
|
2855
|
+
messageKey: 'com.labre.uml.validation.message-syntax',
|
|
2856
|
+
messageFallback: 'This message label does not parse:',
|
|
2857
|
+
suggestionKey: 'com.labre.uml.validation.message-syntax.suggestion',
|
|
2858
|
+
suggestionFallback: 'A message is labelled "place(order)", and a reply "r = place(order) : Receipt" — the assignment, the arguments and the returned value are each optional, and an arrow with no label at all is legal.',
|
|
2859
|
+
version: 1,
|
|
2860
|
+
provenance: {
|
|
2861
|
+
source: 'standard',
|
|
2862
|
+
reference: 'OMG UML 2.5.1 §17.4.4 — <request-message-label> ::= <message-name> [( [<input-argument-list>] )], <reply-message-label> ::= [<assignment-target> =] <message-name> [( [<output-argument-list>] )] [: <value-specification>]',
|
|
2863
|
+
},
|
|
2864
|
+
moment: 'on-demand',
|
|
2865
|
+
backgroundRole: UML_ROLE.diagram,
|
|
2866
|
+
labelSyntax: { parse: checkMessageLabel, perLine: false },
|
|
2867
|
+
};
|
|
2868
|
+
/**
|
|
2869
|
+
* **U41** — a lifeline whose head has been emptied.
|
|
2870
|
+
*
|
|
2871
|
+
* The THIRD `label-presence` rule, and the one that made the tier vocabulary
|
|
2872
|
+
* grow: until phase 3 a lifeline's head carried `uml:label`, the actor's and the
|
|
2873
|
+
* use case's tier, and a rule written on that tier could not tell the three
|
|
2874
|
+
* apart. Which made both halves of the naming question wrong at once — an
|
|
2875
|
+
* emptied head was reported as "this actor or use case has no name", and an
|
|
2876
|
+
* author of a sequence diagram was told to write "Customer" in a box that wants
|
|
2877
|
+
* `order : Order`. `uml:lifeline-ident` is that tier split off (`roles.ts`), and
|
|
2878
|
+
* this is one of the two rules the split exists for.
|
|
2879
|
+
*
|
|
2880
|
+
* ONE rule, one subject role, and no overlap with {@link unnamedActorOrUseCase}
|
|
2881
|
+
* by construction: the new role is FLAT, so `roleIsA` never walks from one to
|
|
2882
|
+
* the other and an emptied head is reported here and nowhere else.
|
|
2883
|
+
*
|
|
2884
|
+
* ## What it stays silent about
|
|
2885
|
+
*
|
|
2886
|
+
* A head somebody deleted outright rather than emptied — the same known limit
|
|
2887
|
+
* {@link unnamedClassifier} documents, and for the same reason: there is no text
|
|
2888
|
+
* on the sheet for a `label-presence` rule to be about.
|
|
2889
|
+
*/
|
|
2890
|
+
const unnamedLifeline = {
|
|
2891
|
+
id: 'uml.unnamed-lifeline',
|
|
2892
|
+
framework: 'uml',
|
|
2893
|
+
family: 'label-presence',
|
|
2894
|
+
severity: 'audit',
|
|
2895
|
+
appliesTo: UML_ROLE['lifeline-ident'],
|
|
2896
|
+
roles: UML_ROLES,
|
|
2897
|
+
messageKey: 'com.labre.uml.validation.unnamed-lifeline',
|
|
2898
|
+
messageFallback: 'This lifeline has no participant in its head.',
|
|
2899
|
+
suggestionKey: 'com.labre.uml.validation.unnamed-lifeline.suggestion',
|
|
2900
|
+
suggestionFallback: 'Write who is taking part — "order : Order", "customer", or just ": Order" for an anonymous instance. Every arrow on this sheet is read as a sentence about the head it leaves and the head it reaches, and an empty one leaves both halves unsaid.',
|
|
2901
|
+
version: 1,
|
|
2902
|
+
provenance: {
|
|
2903
|
+
source: 'recommendation',
|
|
2904
|
+
reference: 'OMG UML 2.5.1 §17.3.4 — a Lifeline is drawn as a named rectangle with a dashed line descending from it',
|
|
2905
|
+
},
|
|
2906
|
+
// On-demand for the reason the other two naming rules are: a head is what a
|
|
2907
|
+
// user changes by TYPING, and real-time would re-evaluate on every keystroke.
|
|
2908
|
+
moment: 'on-demand',
|
|
2909
|
+
backgroundRole: UML_ROLE.diagram,
|
|
2910
|
+
label: { present: true },
|
|
2911
|
+
};
|
|
2912
|
+
/**
|
|
2913
|
+
* **U42** — a lifeline head that does not parse.
|
|
2914
|
+
*
|
|
2915
|
+
* The SIXTH `label-syntax` rule (ADR 0021), and the second half of the tier
|
|
2916
|
+
* split. §17.3.4 prints a BNF — `<lifelineident> ::= ([<connectable-element-name>
|
|
2917
|
+
* [\[<selector>\]]] [: <class-name>] | 'self')` — which is exactly the kind of
|
|
2918
|
+
* claim this family exists to make, and `checkLifelineIdent` is the checker the
|
|
2919
|
+
* IMPORTERS and the two exporters already read the head with. So a finding here
|
|
2920
|
+
* is the line that will leave the sheet less structured than it was drawn: a
|
|
2921
|
+
* head the grammar cannot split is a lifeline whose `name` and `type` go into
|
|
2922
|
+
* the XMI and the PlantUML as one opaque string.
|
|
2923
|
+
*
|
|
2924
|
+
* `perLine: false`, like the transition and the message labels: §17.3.4's
|
|
2925
|
+
* production is one line, not a list, and a head wrapped onto two is one head.
|
|
2926
|
+
*
|
|
2927
|
+
* ## Why it could not be written on `uml:label`
|
|
2928
|
+
*
|
|
2929
|
+
* `Place an order (fast)` is a perfectly good use case name and a broken
|
|
2930
|
+
* lifeline head — parentheses in a head read as a message label, which is what
|
|
2931
|
+
* `checkLifelineIdent` says about them. A rule on the shared tier would have
|
|
2932
|
+
* indicted every parenthesised ellipse on every use case diagram in the library
|
|
2933
|
+
* (`roles.ts`).
|
|
2934
|
+
*
|
|
2935
|
+
* ## What it stays silent about
|
|
2936
|
+
*
|
|
2937
|
+
* An EMPTY head, which is {@link unnamedLifeline}'s question and carries its own
|
|
2938
|
+
* sentence — two rules on one word to fix is the thing this pack refuses
|
|
2939
|
+
* everywhere — and `self`, which §17.3.4 prints as an alternative of the
|
|
2940
|
+
* production itself.
|
|
2941
|
+
*/
|
|
2942
|
+
const lifelineIdentSyntax = {
|
|
2943
|
+
id: 'uml.lifeline-ident-syntax',
|
|
2944
|
+
framework: 'uml',
|
|
2945
|
+
family: 'label-syntax',
|
|
2946
|
+
severity: 'audit',
|
|
2947
|
+
appliesTo: UML_ROLE['lifeline-ident'],
|
|
2948
|
+
roles: UML_ROLES,
|
|
2949
|
+
messageKey: 'com.labre.uml.validation.lifeline-ident-syntax',
|
|
2950
|
+
messageFallback: 'This lifeline head does not parse:',
|
|
2951
|
+
suggestionKey: 'com.labre.uml.validation.lifeline-ident-syntax.suggestion',
|
|
2952
|
+
suggestionFallback: 'A head is written "name : Class" — "order : Order", "customer", ": Order" for an anonymous participant, "customers[i] : Customer" for one of many, or "self". The name and the class are each optional; the colon between them is not decoration.',
|
|
2953
|
+
version: 1,
|
|
2954
|
+
provenance: {
|
|
2955
|
+
source: 'standard',
|
|
2956
|
+
reference: "OMG UML 2.5.1 §17.3.4 — <lifelineident> ::= ([<connectable-element-name> ['[' <selector> ']']] [: <class-name>] | 'self')",
|
|
2957
|
+
},
|
|
2958
|
+
moment: 'on-demand',
|
|
2959
|
+
backgroundRole: UML_ROLE.diagram,
|
|
2960
|
+
labelSyntax: { parse: checkLifelineIdent, perLine: false },
|
|
2961
|
+
};
|
|
2962
|
+
/**
|
|
2963
|
+
* The pack, whole: forty-three rules over ten families.
|
|
2964
|
+
*
|
|
2965
|
+
* Sixteen in phase 1, and not that brief's seventeen ids because the actor's
|
|
2966
|
+
* name and the use case's name are ONE rule: they are the same tier role, and
|
|
2967
|
+
* two rules on one role report one emptied word twice (see
|
|
2968
|
+
* {@link unnamedActorOrUseCase}). The structural sheets appended three — the
|
|
2969
|
+
* grammars of the three edges they draw — and skipped two the engine could not
|
|
2970
|
+
* be asked; one of those two came back as the forty-third rule below, once the
|
|
2971
|
+
* engine grew the family it needed. The BEHAVIOUR sheets appended fifteen, which is the largest single
|
|
2972
|
+
* addition this library has taken: an activity and a state machine are the two
|
|
2973
|
+
* diagrams UML constrains arithmetically, so where a class diagram's pack is
|
|
2974
|
+
* mostly a vocabulary these are counts, degrees and a graph walk.
|
|
2975
|
+
*
|
|
2976
|
+
* The SEQUENCE sheet appended four, and all four are families this pack already
|
|
2977
|
+
* had — which says something about the notation rather than about the effort:
|
|
2978
|
+
* an interaction's whole grammar is "who may exchange a message with whom",
|
|
2979
|
+
* "what a message label says", and the two questions any tier is asked about
|
|
2980
|
+
* the words in a lifeline's head. Those last two are the ones that cost a role:
|
|
2981
|
+
* `uml:lifeline-ident` exists because §17.3.4 prints a BNF for a head and
|
|
2982
|
+
* §18.1.4 prints none for an actor's word, and one tier carrying both would have
|
|
2983
|
+
* had to ask them the same question (`roles.ts`, {@link unnamedLifeline}). The
|
|
2984
|
+
* four requirements Clause 17 states that this library CANNOT state — a message
|
|
2985
|
+
* never traversed upwards (§17.4.4), an execution bar sat on a lifeline's spine
|
|
2986
|
+
* (§17.3.4), a create message landing on a head, an operand's guard written in
|
|
2987
|
+
* brackets (§17.6.4) — are each a geometry no family expresses, and they are
|
|
2988
|
+
* recorded in `docs/adr/0022` rather than approximated here. A rule that fires
|
|
2989
|
+
* on the wrong thing costs more than a requirement nobody checks.
|
|
2990
|
+
*
|
|
2991
|
+
* The PO recette of 2026-09-16 appended the forty-third, and it is the one rule
|
|
2992
|
+
* in the pack that was written down as impossible before it was written: a port
|
|
2993
|
+
* dragged into the middle of its component (§11.3.4). See {@link portOnBorder},
|
|
2994
|
+
* and the paragraph above the six remaining gaps on how it left that list.
|
|
2995
|
+
*
|
|
2996
|
+
* ## TWO of the ten families are new to the ENGINE, and they are the only ones
|
|
2997
|
+
*
|
|
2998
|
+
* `label-syntax` (`docs/adr/0021`) arrived with four spelling rules and carries
|
|
2999
|
+
* six, and it was the first family this library added for a reason no table
|
|
3000
|
+
* could meet: a notation's GRAMMAR is a parser, and the clauses these rules cite
|
|
3001
|
+
* are already written as one in `grammar.ts` for the exporters.
|
|
3002
|
+
* `border-proximity` (`docs/adr/0024`) is the second, and its reason is the
|
|
3003
|
+
* opposite one — pure geometry that three existing families each get NEARLY
|
|
3004
|
+
* right and none of them states: a distance from a glyph's centre to another
|
|
3005
|
+
* artefact's outline. In both cases the family owns the walk and the pack owns
|
|
3006
|
+
* the reading, and in both cases the alternative on the table was approximating
|
|
3007
|
+
* the requirement with a neighbouring family. Everything else the thirty-odd
|
|
3008
|
+
* roles of phases 2 and 3 needed, the engine already had.
|
|
3009
|
+
*
|
|
3010
|
+
* ## The other eight are the engine's, and two of them are BPMN's
|
|
3011
|
+
*
|
|
3012
|
+
* `role-count` and `reachability` are BPMN's, registered here with UML's own
|
|
3013
|
+
* roles and nothing else: a state machine with two beginnings and a ring of
|
|
3014
|
+
* states nothing enters are the same two questions a pool with two start events
|
|
3015
|
+
* and an unreachable task ask. So the claim `docs/add-a-framework` makes about
|
|
3016
|
+
* the seam survives the largest notation in the library twice over — the
|
|
3017
|
+
* fifteen edge roles are fifteen readings of `relation-endpoints`, the four
|
|
3018
|
+
* frames are the membership families C4 already uses, and the sheet's own
|
|
3019
|
+
* declaration is the `view-admissibility` C4 opened.
|
|
3020
|
+
*
|
|
3021
|
+
* ## Several rules say one sentence twice, and the reason is always the same
|
|
3022
|
+
*
|
|
3023
|
+
* Five pairs — the two histories, the two initial-node counts, the two finals,
|
|
3024
|
+
* the two reachability walks — are one requirement declared twice because the
|
|
3025
|
+
* family that answers it names ONE role: one subject role, or one edge role.
|
|
3026
|
+
* They never double-report (an element carries one role, an edge carries one
|
|
3027
|
+
* role, and each pair's two halves are flat siblings), and each half carries the
|
|
3028
|
+
* sentence and the authority its own half of the notation has. Where the pair
|
|
3029
|
+
* could have been collapsed by inventing an ancestor role, it was not: this
|
|
3030
|
+
* vocabulary's whole authority is that every role in it is the specification's.
|
|
3031
|
+
*/
|
|
3032
|
+
export const UML_RULES = [
|
|
3033
|
+
// Membership: is the drawing on the sheet it claims to be on?
|
|
3034
|
+
elementOutsideFrame,
|
|
3035
|
+
notAdmissibleOnKind,
|
|
3036
|
+
useCaseOutsideSubject,
|
|
3037
|
+
actorInsideSubject,
|
|
3038
|
+
// Naming: does the drawing say anything at all?
|
|
3039
|
+
unnamedClassifier,
|
|
3040
|
+
unnamedActorOrUseCase,
|
|
3041
|
+
// Spelling: and is what it says written the way the clause writes it?
|
|
3042
|
+
attributeSyntax,
|
|
3043
|
+
operationSyntax,
|
|
3044
|
+
transitionSyntax,
|
|
3045
|
+
multiplicitySyntax,
|
|
3046
|
+
// Grammar: what each line may run between.
|
|
3047
|
+
generalizationEndpoints,
|
|
3048
|
+
generalizationSelfLoop,
|
|
3049
|
+
realizationEndpoints,
|
|
3050
|
+
dependencyOnObject,
|
|
3051
|
+
includeEndpoints,
|
|
3052
|
+
extendEndpoints,
|
|
3053
|
+
actorActorAssociation,
|
|
3054
|
+
deployEndpoints,
|
|
3055
|
+
manifestEndpoints,
|
|
3056
|
+
communicationPathEndpoints,
|
|
3057
|
+
untypedEdge,
|
|
3058
|
+
// Degree: how many lines may reach one artefact?
|
|
3059
|
+
compositionSingleOwner,
|
|
3060
|
+
useCaseNoActor,
|
|
3061
|
+
// Attachment: is the glyph still on the box it was drawn on?
|
|
3062
|
+
portOnBorder,
|
|
3063
|
+
// Behaviour — membership: is the drawing in the lane, in the region?
|
|
3064
|
+
nodeInPartition,
|
|
3065
|
+
shallowHistoryOutsideRegion,
|
|
3066
|
+
deepHistoryOutsideRegion,
|
|
3067
|
+
// Behaviour — counts: how many beginnings, how many lines reach one glyph?
|
|
3068
|
+
initialSingle,
|
|
3069
|
+
initialNoIncomingFlow,
|
|
3070
|
+
initialNoIncomingTransition,
|
|
3071
|
+
activityFinalNoOutgoing,
|
|
3072
|
+
flowFinalNoOutgoing,
|
|
3073
|
+
finalStateNoOutgoing,
|
|
3074
|
+
decisionIncomingCount,
|
|
3075
|
+
// Behaviour — grammar: what each flow may run between.
|
|
3076
|
+
controlFlowEndpoints,
|
|
3077
|
+
objectFlowEndpoints,
|
|
3078
|
+
transitionEndpoints,
|
|
3079
|
+
// Behaviour — the graph: can the flow get there at all?
|
|
3080
|
+
unreachableAction,
|
|
3081
|
+
unreachableState,
|
|
3082
|
+
// The interaction: what a message runs between, and what its label says…
|
|
3083
|
+
messageEndpoints,
|
|
3084
|
+
messageSyntax,
|
|
3085
|
+
// …and the two questions a lifeline's own head is asked, which are the two
|
|
3086
|
+
// questions every tier in this pack is asked: does it say anything, and does
|
|
3087
|
+
// what it says parse.
|
|
3088
|
+
unnamedLifeline,
|
|
3089
|
+
lifelineIdentSyntax,
|
|
3090
|
+
];
|