@cardenelabs/dragon 0.30.0 → 0.33.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/README.md +314 -12
- package/dist/index.cjs +6960 -1434
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2151 -115
- package/dist/index.d.ts +2151 -115
- package/dist/index.js +6944 -1436
- package/dist/index.js.map +1 -1
- package/package.json +12 -13
- package/src/compile/actor-ids.ts +6 -15
- package/src/compile/actor-option-notice.ts +379 -0
- package/src/compile/actors.ts +32 -3
- package/src/compile/c4.ts +0 -3
- package/src/compile/chain.ts +43 -4
- package/src/compile/chart-fields.ts +16 -1
- package/src/compile/direction.ts +50 -11
- package/src/compile/edge-option-notice.ts +112 -0
- package/src/compile/flowchart.ts +344 -0
- package/src/compile/funnel.ts +14 -2
- package/src/compile/gantt.ts +141 -36
- package/src/compile/generic.ts +473 -61
- package/src/compile/journey.ts +14 -5
- package/src/compile/kinds.ts +5 -13
- package/src/compile/lanes.ts +95 -13
- package/src/compile/metro.ts +271 -0
- package/src/compile/mind.ts +55 -5
- package/src/compile/notice.ts +51 -4
- package/src/compile/order.ts +62 -0
- package/src/compile/parts.ts +107 -33
- package/src/compile/placement-apply.ts +27 -11
- package/src/compile/quadrant.ts +171 -16
- package/src/compile/record.ts +75 -0
- package/src/compile/row-marks.ts +128 -0
- package/src/compile/sequence.ts +105 -26
- package/src/compile/slug.ts +9 -1
- package/src/compile/stages.ts +247 -0
- package/src/compile/subtitle.ts +15 -2
- package/src/compile/swimlane-marks.ts +87 -0
- package/src/compile/swimlane.ts +13 -14
- package/src/compile/timeline.ts +213 -0
- package/src/compile/topology.ts +3 -9
- package/src/compile/tree.ts +13 -4
- package/src/compile/value-chart.ts +179 -25
- package/src/compile.ts +1547 -370
- package/src/focus.ts +2 -2
- package/src/index.ts +34 -2
- package/src/json-parser.ts +1407 -48
- package/src/keywords.ts +255 -17
- package/src/notation-lint.ts +41 -64
- package/src/schemas/diagram.json +643 -20
- package/src/types.ts +336 -35
- package/src/v05/parser.ts +2451 -271
- package/src/compile/class.ts +0 -100
- package/src/compile/er.ts +0 -44
- package/src/compile/solidity.ts +0 -39
- package/src/compile/state.ts +0 -71
package/src/compile.ts
CHANGED
|
@@ -12,29 +12,48 @@
|
|
|
12
12
|
import type { DslDocument, DslLane, DslStep, DslEventBinding, PresetType } from "./types";
|
|
13
13
|
// 図種ごとの組み立てを分けた先 (#2030)。 共有の小道具から順に出している。
|
|
14
14
|
// どれも他の file を取り込まない葉なので、図種ごとの file と両方から呼んでも輪にならない
|
|
15
|
-
import {
|
|
15
|
+
import { 向きを選べる図種, 縦列より向きが勝つ図種, 既定の向き } from "./compile/direction";
|
|
16
|
+
import { 並び順が効くか, 並び順を選べる図種 } from "./compile/order";
|
|
16
17
|
import { actorRefTable, canonicalizeFlowActors } from "./compile/actors";
|
|
17
18
|
import { compileC4 } from "./compile/c4";
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
19
|
+
import { 鎖でつなぐ形か, 鎖に並べる登場人物, 鎖にしない書き方の案内 } from "./compile/chain";
|
|
20
|
+
import { compileRecord } from "./compile/record";
|
|
21
|
+
import { 行頭の印にする, 行頭の印を読むか, 鍵の行を上にまとめる } from "./compile/row-marks";
|
|
21
22
|
import { compileFlow } from "./compile/flow";
|
|
22
23
|
import { compileFunnel } from "./compile/funnel";
|
|
24
|
+
import { 共通の組み立てへ回す } from "./compile/generic";
|
|
23
25
|
import { compileGantt } from "./compile/gantt";
|
|
24
26
|
|
|
25
27
|
import { compileJourney } from "./compile/journey";
|
|
26
28
|
import { compileMind } from "./compile/mind";
|
|
27
29
|
import { compileQuadrant } from "./compile/quadrant";
|
|
28
30
|
import { compileSequence } from "./compile/sequence";
|
|
29
|
-
import {
|
|
30
|
-
import { compileState } from "./compile/state";
|
|
31
|
+
import { compileFlowchart } from "./compile/flowchart";
|
|
31
32
|
import { compileSwimlane } from "./compile/swimlane";
|
|
32
33
|
import { compileTopology } from "./compile/topology";
|
|
33
34
|
import { compileTree } from "./compile/tree";
|
|
34
35
|
import { compileValueChart } from "./compile/value-chart";
|
|
35
36
|
import { deepRewriteStrings } from "./compile/deep-rewrite";
|
|
37
|
+
import {
|
|
38
|
+
効かない箱の欄を並べる,
|
|
39
|
+
板が伝えない箱の欄,
|
|
40
|
+
木の図が伝えない箱の欄,
|
|
41
|
+
値として読む図種,
|
|
42
|
+
値として読む図が伝えない箱の欄,
|
|
43
|
+
値として読む図が主役を読む,
|
|
44
|
+
骨組みの図種,
|
|
45
|
+
骨組みの図が伝えない箱の欄,
|
|
46
|
+
} from "./compile/actor-option-notice";
|
|
47
|
+
import {
|
|
48
|
+
効かない矢印の欄を並べる,
|
|
49
|
+
向きだけを使う図が伝えない矢印の欄,
|
|
50
|
+
} from "./compile/edge-option-notice";
|
|
36
51
|
import { recordDerivedSourceLine } from "./compile/derived-source";
|
|
37
|
-
import {
|
|
52
|
+
import {
|
|
53
|
+
mergePartsFromActors,
|
|
54
|
+
reportPartNodeNotHonored,
|
|
55
|
+
測るために配置する,
|
|
56
|
+
} from "./compile/parts";
|
|
38
57
|
// 見本の取り込みが外へ出していた 13 個を、この file から出していた形のまま渡す (#2034)。
|
|
39
58
|
// `index.ts` と検査が `./compile` から取り込んでいるので、窓口をここに残す
|
|
40
59
|
export {
|
|
@@ -51,6 +70,7 @@ export {
|
|
|
51
70
|
partsBaseBottom,
|
|
52
71
|
partsGridCenters,
|
|
53
72
|
部品に上書きを当てる,
|
|
73
|
+
測るために受け取った知らせの数,
|
|
54
74
|
} from "./compile/parts";
|
|
55
75
|
import { SINGLE_BOX_KINDS, 図種の作り } from "./compile/kinds";
|
|
56
76
|
import { collectRenamedTargets, disambiguateActorIds, restoreActorNames } from "./compile/actor-ids";
|
|
@@ -65,7 +85,7 @@ import {
|
|
|
65
85
|
// 箱を測る物差しを、この file から出していた形のまま外へ渡す (#2036)。
|
|
66
86
|
// `index.ts` が画面側の測りに使う
|
|
67
87
|
export { measureActorBoxes } from "./compile/placement-apply";
|
|
68
|
-
import { 縦列を選べる図種,
|
|
88
|
+
import { 縦列を選べる図種, 書いた縦列に置く, 縦列の判定に数える箱 } from "./compile/lanes";
|
|
69
89
|
import type { CompileNotice } from "./compile/notice";
|
|
70
90
|
// 分けた先の型を、この file から出していた形のまま外へ渡す (#2030)。
|
|
71
91
|
// `index.ts` と 47 個の検査が `./compile` から取り込んでいるので、窓口をここに残す
|
|
@@ -73,11 +93,20 @@ export type { CompileNotice } from "./compile/notice";
|
|
|
73
93
|
import { 段を読み取る } from "./compile/rows";
|
|
74
94
|
import { truncateForMessage } from "./compile/subtitle";
|
|
75
95
|
import { slugify } from "./compile/slug";
|
|
96
|
+
import { 省く泳法図の印を外す, 泳法図で印をどう描く } from "./compile/swimlane-marks";
|
|
76
97
|
import { 語の状態を図の語へ直す } from "./compile/word-state";
|
|
77
|
-
import type { CdlDiagram, CdlEdge,
|
|
78
|
-
import {
|
|
98
|
+
import type { CdlDiagram, CdlEdge, CdlLegendItem, CdlNode } from "@cardenelabs/cdl";
|
|
99
|
+
import { parseFormula, extractIdentifiers, inputDefaultValue } from "@cardenelabs/cdl";
|
|
79
100
|
import { parseFocusEntry } from "./focus";
|
|
80
|
-
import { DRAW_TARGETS } from "./v05/parser";
|
|
101
|
+
import { DRAW_TARGETS, 描く語がその図を指すか } from "./v05/parser";
|
|
102
|
+
import {
|
|
103
|
+
DIAGRAM_SHAPES,
|
|
104
|
+
resolveShape,
|
|
105
|
+
shapesForDiagramType,
|
|
106
|
+
棒を描く節か,
|
|
107
|
+
意匠の棒の角,
|
|
108
|
+
type DslShape,
|
|
109
|
+
} from "./keywords";
|
|
81
110
|
import { pointsOutside, stripExternalPaint } from "./color";
|
|
82
111
|
import { countDocElements, describeOversize } from "./input-size";
|
|
83
112
|
export interface CompileToCdlOpts {
|
|
@@ -131,6 +160,7 @@ export function compileToCdl(doc: DslDocument, opts?: CompileToCdlOpts): CdlDiag
|
|
|
131
160
|
// 端にしか出てこない名前で id を分けても、 その箱は作られない
|
|
132
161
|
const 分けた = disambiguateActorIds(doc, opts?.onNotice);
|
|
133
162
|
doc = 分けた.doc;
|
|
163
|
+
doc = 省く泳法図の印を外す(doc);
|
|
134
164
|
|
|
135
165
|
// 図種の組み立てが知らせた行を控える (#2107 / #2111)。 同じ行に多重度の知らせを重ねない。
|
|
136
166
|
// 知らせる図種も知らせの種類も手で並べず、実際に出た知らせで決める
|
|
@@ -151,53 +181,29 @@ export function compileToCdl(doc: DslDocument, opts?: CompileToCdlOpts): CdlDiag
|
|
|
151
181
|
case "flow":
|
|
152
182
|
diagram = compileFlow(doc, opts?.partsCatalog);
|
|
153
183
|
break;
|
|
184
|
+
case "flowchart":
|
|
185
|
+
diagram = compileFlowchart(doc);
|
|
186
|
+
break;
|
|
154
187
|
case "swimlane":
|
|
155
188
|
diagram = compileSwimlane(doc);
|
|
156
189
|
break;
|
|
157
|
-
case "
|
|
158
|
-
diagram =
|
|
159
|
-
break;
|
|
160
|
-
case "state":
|
|
161
|
-
diagram = compileState(doc);
|
|
190
|
+
case "record":
|
|
191
|
+
diagram = compileRecord(doc);
|
|
162
192
|
break;
|
|
163
193
|
case "topology":
|
|
164
194
|
diagram = compileTopology(doc);
|
|
165
195
|
break;
|
|
166
|
-
case "solidity":
|
|
167
|
-
diagram = compileSolidity(doc);
|
|
168
|
-
break;
|
|
169
196
|
case "gantt":
|
|
170
197
|
diagram = compileGantt(doc, 図種の知らせ);
|
|
171
198
|
break;
|
|
172
|
-
case "
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
break;
|
|
181
|
-
case "line":
|
|
182
|
-
diagram = compileValueChart(doc, "line", "chart-line", 図種の知らせ);
|
|
183
|
-
break;
|
|
184
|
-
case "gauge":
|
|
185
|
-
diagram = compileValueChart(doc, "gauge", "chart-gauge", 図種の知らせ);
|
|
186
|
-
break;
|
|
187
|
-
case "radial":
|
|
188
|
-
diagram = compileValueChart(doc, "radial", "chart-radial", 図種の知らせ);
|
|
189
|
-
break;
|
|
190
|
-
case "stat":
|
|
191
|
-
diagram = compileValueChart(doc, "stat", "chart-stat", 図種の知らせ);
|
|
192
|
-
break;
|
|
193
|
-
case "waffle":
|
|
194
|
-
diagram = compileValueChart(doc, "waffle", "chart-waffle", 図種の知らせ);
|
|
195
|
-
break;
|
|
196
|
-
case "stacked":
|
|
197
|
-
diagram = compileValueChart(doc, "stacked", "chart-stacked-bar", 図種の知らせ);
|
|
198
|
-
break;
|
|
199
|
-
case "slope":
|
|
200
|
-
diagram = compileValueChart(doc, "slope", "chart-slope", 図種の知らせ);
|
|
199
|
+
case "chart":
|
|
200
|
+
// 形は `shape:` が決める (#2657)。 書かなければ棒 = 数を並べる図で最も素直な形で、
|
|
201
|
+
// 形を書かずに数だけ書いた記法が図にならない状態を作らない
|
|
202
|
+
diagram = compileValueChart(
|
|
203
|
+
doc,
|
|
204
|
+
doc.shape === undefined ? "bar" : (resolveShape(doc.shape) ?? "bar"),
|
|
205
|
+
図種の知らせ,
|
|
206
|
+
);
|
|
201
207
|
break;
|
|
202
208
|
case "funnel":
|
|
203
209
|
diagram = compileFunnel(doc, 図種の知らせ);
|
|
@@ -234,10 +240,10 @@ export function compileToCdl(doc: DslDocument, opts?: CompileToCdlOpts): CdlDiag
|
|
|
234
240
|
const 矢印の行 = new Map<string, DslStep>();
|
|
235
241
|
applyEdgeInlineOptions(diagram, doc, edgeSourceLines, 矢印の行, opts?.partsCatalog);
|
|
236
242
|
const 作った組の枠 = applyGroupContainers(diagram, doc);
|
|
237
|
-
applyNodeTones(diagram, doc);
|
|
238
243
|
// 光らせる相手が実在するかを確かめる。 id への解決は図種ごとに違うが、 名前が居るか
|
|
239
|
-
// 居ないかは記述だけで決まるので 1
|
|
240
|
-
|
|
244
|
+
// 居ないかは記述だけで決まるので 1 か所で見る。
|
|
245
|
+
// 記法にはあるのに図がその相手を持たない形 (#2398) は、組み上がった図と突き合わせる
|
|
246
|
+
reportMissingFocusTargets(書いたまま, opts?.onNotice, diagram);
|
|
241
247
|
// 矢印が指す名前が actors に居るかを確かめる。 図種ごとの解決より前に、 記述だけで決まる
|
|
242
248
|
reportMissingFlowActors(書いたまま, opts?.onNotice);
|
|
243
249
|
// 両端が同じ矢印を伝える (#1227)。 落とす前の `flow` を見る
|
|
@@ -245,12 +251,39 @@ export function compileToCdl(doc: DslDocument, opts?: CompileToCdlOpts): CdlDiag
|
|
|
245
251
|
// 自分へ戻る形と居ない名前を指す形は既に落ちた後の `doc` を見る = 上の 2 件と重ねない
|
|
246
252
|
reportFlowEndpointNotHonored(doc, 分けた.元の名前, opts?.onNotice, opts?.partsCatalog);
|
|
247
253
|
reportLaneNotHonored(書いたまま, opts?.onNotice);
|
|
248
|
-
|
|
254
|
+
/*
|
|
255
|
+
* 箱の知らせが出た行を控える (#2368)。 片方だけの位置の知らせ (#2362) を同じ行に重ねない。
|
|
256
|
+
*
|
|
257
|
+
* 知らせる図種も知らせの種類も手で並べず、実際に出た知らせで決める (`図種の知らせ` と同じ形)。
|
|
258
|
+
*/
|
|
259
|
+
const 箱に知らせた行 = new Set<number>();
|
|
260
|
+
const 箱の知らせ = 受け取り口
|
|
261
|
+
? (n: CompileNotice): void => {
|
|
262
|
+
箱に知らせた行.add(n.line);
|
|
263
|
+
受け取り口(n);
|
|
264
|
+
}
|
|
265
|
+
: undefined;
|
|
266
|
+
reportActorKindNotHonored(書いたまま, 箱の知らせ);
|
|
267
|
+
reportTreeActorOptionNotHonored(書いたまま, 箱の知らせ);
|
|
268
|
+
reportValueChartActorOptionNotHonored(書いたまま, 箱の知らせ);
|
|
269
|
+
reportSkeletonActorOptionNotHonored(書いたまま, 箱の知らせ);
|
|
270
|
+
reportHalfWrittenPosition(書いたまま, 箱に知らせた行, opts?.onNotice);
|
|
271
|
+
// 出した行を `図種が知らせた行` に控える = 多重度の知らせ (#2107) を同じ行に重ねない
|
|
272
|
+
reportSkeletonEdgeOptionNotHonored(書いたまま, 図種が知らせた行, 図種の知らせ);
|
|
249
273
|
reportMessageOptionNotHonored(書いたまま, opts?.onNotice);
|
|
274
|
+
reportBandProblems(書いたまま, diagram, opts?.onNotice);
|
|
250
275
|
reportCardinalityNotHonored(書いたまま, 図種が知らせた行, opts?.onNotice);
|
|
251
276
|
reportFlowOffsetNotHonored(書いたまま, opts?.onNotice);
|
|
252
277
|
reportDocEyebrowNotHonored(書いたまま, opts?.onNotice);
|
|
253
278
|
reportDirectionNotHonored(書いたまま, opts?.onNotice);
|
|
279
|
+
reportOrderNotHonored(書いたまま, opts?.onNotice);
|
|
280
|
+
reportShapeNotHonored(書いたまま, opts?.onNotice);
|
|
281
|
+
reportFormNotHonored(書いたまま, opts?.onNotice);
|
|
282
|
+
reportTicksNotHonored(書いたまま, opts?.onNotice);
|
|
283
|
+
reportLaneSubtitleNotHonored(書いたまま, opts?.onNotice);
|
|
284
|
+
reportStageFromLane(書いたまま, opts?.onNotice);
|
|
285
|
+
reportTrackFromName(書いたまま, opts?.onNotice);
|
|
286
|
+
reportThemeAlsoPalette(書いたまま, opts?.onNotice);
|
|
254
287
|
reportDrawNotHonored(書いたまま, opts?.onNotice);
|
|
255
288
|
reportChartFieldsNotHonored(書いたまま, opts?.onNotice);
|
|
256
289
|
reportAxesNotHonored(書いたまま, opts?.onNotice);
|
|
@@ -262,6 +295,19 @@ export function compileToCdl(doc: DslDocument, opts?: CompileToCdlOpts): CdlDiag
|
|
|
262
295
|
// CAR-1657 = parts kind actor を merge (opts.partsCatalog 経由)、 applyV05Extensions 後段で実行
|
|
263
296
|
const 追加した縦列: DslLane[] = [];
|
|
264
297
|
const extended = applyV05Extensions(diagram, placed, 追加した縦列);
|
|
298
|
+
/*
|
|
299
|
+
* 書いた色を箱に載せる (#2333)。 **題を写した後に呼ぶ**。
|
|
300
|
+
*
|
|
301
|
+
* 照合は箱に出る題で行う (理由は `applyNodeTones` の説明)。 その題が箱に入る時点が
|
|
302
|
+
* 図種で 2 つに割れており、15 図種は組み立て器が `箱の題` で入れる一方、
|
|
303
|
+
* 順序図 / 帯図 / c4 は登場人物の名前で箱を作り、`applyV05Extensions` が
|
|
304
|
+
* 題へ書き換える。 書き換えより前に照合すると、後者の 4 図種で題と名前が食い違う。
|
|
305
|
+
*
|
|
306
|
+
* 実測 = ここより前で呼んでいた間、題を書いた箱の色が 5 図種 (フロー / 構成図 /
|
|
307
|
+
* 状態遷移 / クラス図 / ER 図) で消え、名前で照合していたため帯図と c4 だけが通っていた。
|
|
308
|
+
* 呼ぶ位置を変えずに照合だけを題へ寄せると、今度は帯図と c4 が落ちる (実測)。
|
|
309
|
+
*/
|
|
310
|
+
applyNodeTones(extended, placed);
|
|
265
311
|
// 値の知らせは、本文なら値を書いた行、見本なら見本を置いた行を指す。 `derived` 自体には
|
|
266
312
|
// source position が無いため、見本を重ねる間だけ別表で宣言元を持ち回る (#1180)。
|
|
267
313
|
const inheritedDerivedSourceLines = opts?.onNotice ? new Map<string, number[]>() : undefined;
|
|
@@ -298,8 +344,8 @@ export function compileToCdl(doc: DslDocument, opts?: CompileToCdlOpts): CdlDiag
|
|
|
298
344
|
// 動かない図に段を 1 つ入れる (#1086)。
|
|
299
345
|
//
|
|
300
346
|
// 描画側は「段が 1 件以上」 を要求するが、 段を作るかどうかは種類ごとにばらけている。
|
|
301
|
-
// 実測 = `animation:`
|
|
302
|
-
// state / topology
|
|
347
|
+
// 実測 = `animation:` を書かない同じ記法を旧 12 種に与えると、6 種 (sequence / flow / er /
|
|
348
|
+
// state / topology) は描かれ、6 種 (swimlane / gantt / class / pie / c4 / mind)
|
|
303
349
|
// は「phase が 0 件です」 で弾かれた。 書く人から見ると区別する手がかりが無い。
|
|
304
350
|
//
|
|
305
351
|
// **出口で 1 度だけ見る**。 種類ごとに塞ぐと 12 経路のどれかを見落とす。 図は必ずここを
|
|
@@ -329,8 +375,12 @@ export function compileToCdl(doc: DslDocument, opts?: CompileToCdlOpts): CdlDiag
|
|
|
329
375
|
if (doc.readouts && doc.readouts.length > 0) {
|
|
330
376
|
// 出口の paint 検査は diagram を直接書き換える。 doc の object を共有すると、検査が
|
|
331
377
|
// compileToCdl の入力まで書き換えて入力不変性を壊すため、nested field も含めて写す。
|
|
332
|
-
|
|
333
|
-
|
|
378
|
+
//
|
|
379
|
+
// **書いた行は図へ載せない** (#2405)。 行は読む元の名前を突き合わせた知らせのために持つ。
|
|
380
|
+
// 残すと、行を足しただけで組み上がる図が変わる。
|
|
381
|
+
type 図の読み取り値 = NonNullable<CdlDiagram["readouts"]>[number];
|
|
382
|
+
const ownReadouts = doc.readouts.map(({ pos: _書いた行, ...readout }) =>
|
|
383
|
+
deepRewriteStrings(readout, (value) => value) as 図の読み取り値,
|
|
334
384
|
);
|
|
335
385
|
merged.readouts = [...(merged.readouts ?? []), ...ownReadouts];
|
|
336
386
|
}
|
|
@@ -427,6 +477,12 @@ export function compileToCdl(doc: DslDocument, opts?: CompileToCdlOpts): CdlDiag
|
|
|
427
477
|
if (載せる式.length > 0) merged.formulas = 載せる式;
|
|
428
478
|
else delete merged.formulas;
|
|
429
479
|
}
|
|
480
|
+
// **3 つの節を全て載せ終えてから呼ぶ** = 読む元は `states` / `inputs` / `formulas` に散っており、
|
|
481
|
+
// 途中で呼ぶと、後から載る節の名前を「無い」 と知らせる
|
|
482
|
+
// **つまみを載せ終えてから呼ぶ** = 記法は `states:` を `inputs:` より前に書けるため、
|
|
483
|
+
// 途中で呼ぶと後から載るつまみを見落とす
|
|
484
|
+
reportStateInputMismatch(doc, merged, opts?.onNotice);
|
|
485
|
+
reportReadoutSourceMissing(doc, merged, opts?.onNotice);
|
|
430
486
|
/*
|
|
431
487
|
* 押下などの出来事で動く仕掛けを図に載せる (#1393)。
|
|
432
488
|
*
|
|
@@ -441,13 +497,34 @@ export function compileToCdl(doc: DslDocument, opts?: CompileToCdlOpts): CdlDiag
|
|
|
441
497
|
for (const e of doc.events) {
|
|
442
498
|
const 相手 = 出来事の相手を解く(merged, doc, e);
|
|
443
499
|
if (相手 === undefined) {
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
500
|
+
/*
|
|
501
|
+
* **原因を 2 つに分ける** (#2336)。 書いた名前が本文に在るかで、直し方が正反対になる。
|
|
502
|
+
*
|
|
503
|
+
* 無い = 綴り違いなので「名前で書く」 の案内が効く。
|
|
504
|
+
* 在る = その図種が登場人物を箱 / 矢印にしないので、同じ案内に従っても永久に直らない。
|
|
505
|
+
* 1 つの知らせにまとめていた間、後者は書き直しの繰り返しに入る形だった
|
|
506
|
+
* (実測 = 24 図種のうち箱を指せるのは 7 図種)。
|
|
507
|
+
*/
|
|
508
|
+
const 呼び名 = 出来事の相手の呼び名[e.target.kind];
|
|
509
|
+
opts?.onNotice?.(
|
|
510
|
+
出来事の相手が本文に在る(doc, e)
|
|
511
|
+
? {
|
|
512
|
+
kind: "event-target-not-honored",
|
|
513
|
+
actor: e.handlerId,
|
|
514
|
+
line: e.pos.line,
|
|
515
|
+
message:
|
|
516
|
+
`type: ${doc.type} は書いた名前を${呼び名}にしないため、` +
|
|
517
|
+
` 出来事 "${e.handlerId}" を付けられません`,
|
|
518
|
+
hint: `名前の書き方の問題ではありません。 図そのもの (\`diagram: true\`) を指すか、${呼び名}を作る図種に変えてください`,
|
|
519
|
+
}
|
|
520
|
+
: {
|
|
521
|
+
kind: "event-target-missing",
|
|
522
|
+
actor: e.handlerId,
|
|
523
|
+
line: e.pos.line,
|
|
524
|
+
message: `出来事 "${e.handlerId}" が指す相手が見つかりません`,
|
|
525
|
+
hint: "box は箱の名前、 lane は縦列の名前、 arrow は `A -> B` で書く",
|
|
526
|
+
},
|
|
527
|
+
);
|
|
451
528
|
continue;
|
|
452
529
|
}
|
|
453
530
|
載せる.push({
|
|
@@ -484,9 +561,31 @@ export function compileToCdl(doc: DslDocument, opts?: CompileToCdlOpts): CdlDiag
|
|
|
484
561
|
// 作り替えた名前を表示だけ戻す (#1220)。 **図への追加を全て終えた後**に戻す = 途中で戻すと、
|
|
485
562
|
// 後続の処理が名前で引く時に作り替え前と後が混ざる
|
|
486
563
|
restoreActorNames(merged, 分けた.元の名前, 作り替えた対象);
|
|
487
|
-
//
|
|
488
|
-
// 20 か所以上あり、そのどれに足しても残りが取り残される
|
|
564
|
+
// 字幅・配色・行の縞は **図への追加を全て終えた後**に当てる (#1553 / #2918)。
|
|
565
|
+
// 図種ごとの組み立ては 20 か所以上あり、そのどれに足しても残りが取り残される
|
|
489
566
|
配色と縞を当てる(merged, doc);
|
|
567
|
+
if (doc.legend && doc.legend.length > 0) {
|
|
568
|
+
// cdl 0.129.0 の凡例は印付き項目と注記の union。両方の欄を落とさず、組み立て API と
|
|
569
|
+
// 同じ順 (mark/text/tone/lineStyle または text/align/lead) で写す。
|
|
570
|
+
merged.legend = doc.legend.map((item) =>
|
|
571
|
+
(item.mark === undefined
|
|
572
|
+
? {
|
|
573
|
+
text: item.text,
|
|
574
|
+
...(item.align === undefined ? {} : { align: item.align }),
|
|
575
|
+
...(item.lead === undefined ? {} : { lead: item.lead }),
|
|
576
|
+
}
|
|
577
|
+
: {
|
|
578
|
+
mark: item.mark,
|
|
579
|
+
text: item.text,
|
|
580
|
+
...(item.tone === undefined ? {} : { tone: item.tone }),
|
|
581
|
+
...(item.lineStyle === undefined ? {} : { lineStyle: item.lineStyle }),
|
|
582
|
+
}) as CdlLegendItem,
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
if (doc.legendFontSize !== undefined) merged.legendFontSize = doc.legendFontSize;
|
|
586
|
+
if (doc.legendOffset !== undefined) merged.legendOffset = doc.legendOffset;
|
|
587
|
+
if (doc.edgeEndGap !== undefined) merged.edgeEndGap = doc.edgeEndGap;
|
|
588
|
+
if (doc.stageLayout !== undefined) merged.stageLayout = doc.stageLayout;
|
|
490
589
|
静止した図の焦点を外す(merged, doc);
|
|
491
590
|
// 位置のずらしは **配置に効く欄を全て載せた後** に当てる (#1971)。 縦列の幅や視点の間隔を
|
|
492
591
|
// 足す前に測ると、後から足された分だけ狙いがずれる
|
|
@@ -494,9 +593,44 @@ export function compileToCdl(doc: DslDocument, opts?: CompileToCdlOpts): CdlDiag
|
|
|
494
593
|
// 組の枠は **縦列の位置が全て決まった後** に置く (#1972)。 位置のずらしより前に置くと、
|
|
495
594
|
// ずらした縦列から枠が離れる
|
|
496
595
|
applyGroupFrames(merged, doc, 作った組の枠, opts?.onNotice);
|
|
596
|
+
cdlの未指定欄を外す(merged);
|
|
497
597
|
return merged;
|
|
498
598
|
}
|
|
499
599
|
|
|
600
|
+
/** cdl が object に足す未指定欄を、記法で値を書かなかった時の省略形へ戻す。 */
|
|
601
|
+
function cdlの未指定欄を外す(diagram: CdlDiagram): void {
|
|
602
|
+
for (const node of diagram.nodes) {
|
|
603
|
+
if (node.terminalDecoration === undefined) delete node.terminalDecoration;
|
|
604
|
+
if (node.cornerForm === undefined) delete node.cornerForm;
|
|
605
|
+
if (node.figureKindLabel === undefined) delete node.figureKindLabel;
|
|
606
|
+
if (node.figureInset === undefined) delete node.figureInset;
|
|
607
|
+
if (node.chartBarSize === undefined) delete node.chartBarSize;
|
|
608
|
+
if (node.chartBarShape === undefined) delete node.chartBarShape;
|
|
609
|
+
if (node.barCornerRadii === undefined) delete node.barCornerRadii;
|
|
610
|
+
if (node.chartLineSize === undefined) delete node.chartLineSize;
|
|
611
|
+
if (node.chartPieSize === undefined) delete node.chartPieSize;
|
|
612
|
+
if (node.chartWaffleSize === undefined) delete node.chartWaffleSize;
|
|
613
|
+
if (node.chartSlopeLabelTemplate === undefined) delete node.chartSlopeLabelTemplate;
|
|
614
|
+
if (node.chartSlopeSize === undefined) delete node.chartSlopeSize;
|
|
615
|
+
if (node.chartStackedLayout === undefined) delete node.chartStackedLayout;
|
|
616
|
+
if (node.chartGaugeSize === undefined) delete node.chartGaugeSize;
|
|
617
|
+
if (node.ganttSize === undefined) delete node.ganttSize;
|
|
618
|
+
if (node.journeySize === undefined) delete node.journeySize;
|
|
619
|
+
if (node.quadrantLabelPosition === undefined) delete node.quadrantLabelPosition;
|
|
620
|
+
if (node.quadrantSize === undefined) delete node.quadrantSize;
|
|
621
|
+
}
|
|
622
|
+
for (const edge of diagram.edges) {
|
|
623
|
+
if (edge.posX1 === undefined) delete edge.posX1;
|
|
624
|
+
if (edge.posY1 === undefined) delete edge.posY1;
|
|
625
|
+
if (edge.posX2 === undefined) delete edge.posX2;
|
|
626
|
+
if (edge.posY2 === undefined) delete edge.posY2;
|
|
627
|
+
if (edge.waypoints === undefined) delete edge.waypoints;
|
|
628
|
+
if (edge.labelCollision === undefined) delete edge.labelCollision;
|
|
629
|
+
if (edge.labelSize === undefined) delete edge.labelSize;
|
|
630
|
+
}
|
|
631
|
+
if (diagram.stageLayout === undefined) delete diagram.stageLayout;
|
|
632
|
+
}
|
|
633
|
+
|
|
500
634
|
/**
|
|
501
635
|
* 段を書かない図では、どの箱も「いま」 にしない (#1557)。
|
|
502
636
|
*
|
|
@@ -507,7 +641,7 @@ export function compileToCdl(doc: DslDocument, opts?: CompileToCdlOpts): CdlDiag
|
|
|
507
641
|
* dragon が 1 段を足す経路 (`injectStaticPhase`)。 どちらも **全ての箱と矢印を段に載せる**。
|
|
508
642
|
*
|
|
509
643
|
* 描画側は段に載った箱を「いま」 として描く (枠を主役色にして太くする)。 その結果、静止した図は
|
|
510
|
-
* 全ての箱が「いま」 になり、止まっている箱の枠が 1 度も出ない (実測 = ER 図 3 箱 /
|
|
644
|
+
* 全ての箱が「いま」 になり、止まっている箱の枠が 1 度も出ない (実測 = ER 図 3 箱 / フロー 2 箱)。
|
|
511
645
|
*
|
|
512
646
|
* 全部を強調するのと何も強調しないのは、どちらも「区別が無い」 状態。 後者の方が落ち着いて読める。
|
|
513
647
|
*
|
|
@@ -529,39 +663,57 @@ function 静止した図の焦点を外す(diagram: CdlDiagram, doc: DslDocument
|
|
|
529
663
|
}
|
|
530
664
|
}
|
|
531
665
|
|
|
532
|
-
|
|
666
|
+
/**
|
|
667
|
+
* 行を書いた箱か (#2782)。 縞を敷く相手を決める。
|
|
668
|
+
*
|
|
669
|
+
* 縞の目的は行を横に追うことなので、行が無い箱に敷いても追う対象が無い。
|
|
670
|
+
*/
|
|
671
|
+
function 行を書いた箱か(node: CdlNode): boolean {
|
|
672
|
+
return (node.rows?.length ?? 0) > 0;
|
|
673
|
+
}
|
|
533
674
|
|
|
534
675
|
/**
|
|
535
|
-
*
|
|
676
|
+
* 図の字幅・配色と、行を書いた箱の縞を当てる (#1553 / #2782 / #2918)。
|
|
536
677
|
*
|
|
537
678
|
* ## 配色
|
|
538
679
|
*
|
|
539
680
|
* cdl は色を持たない。 名前だけを `data-cdl-palette` として markup に出し、消費側
|
|
540
681
|
* (`cdl-theme.css`) が名前を見て 7 つの口 (台 / 行の面 / 縞 / 枠 / 字 / 型名 / 線) に色を当てる。
|
|
682
|
+
* 字の測り方は配色の有無にかかわらず、この出口で比例・等幅の広い方へ揃える。端末だけ
|
|
683
|
+
* 等幅へ替えると配色で骨組みが変わり、比例だけでは端末の字が箱から出るためである。
|
|
684
|
+
* この出口は後段の配置計算より前なので、箱・札・縦列の隙間も同じ幅で測られる (#2818)。
|
|
541
685
|
*
|
|
542
|
-
*
|
|
543
|
-
*
|
|
544
|
-
*
|
|
545
|
-
*
|
|
546
|
-
*
|
|
686
|
+
* **`record` は書かなければ配色を持たない** (#2782)。 畳む前は `type: er` が書かなくても
|
|
687
|
+
* `kinari` (生成りに茶) になったが、畳んだ先には移り変わりを書いた図も入る。
|
|
688
|
+
* 表の図と移り変わりの図は行の有無でも行の中身でも分かれない = 同じ `kind: storage` に
|
|
689
|
+
* 同じ形の行 (`督促を送る: 7 日ごと`) を書く。 分けられない材料で推し量ると、
|
|
690
|
+
* どちらかの図が書いていない色みを名乗る。
|
|
547
691
|
*
|
|
548
|
-
*
|
|
692
|
+
* これは #2782 で落とした 3 つの既定 (始まりと終わりの札 / 矢印の先端 / 箱の種類) と
|
|
693
|
+
* 同じ形で、**書かないものを図が名乗らない** 側に倒している。
|
|
694
|
+
* 表の図には見本の側で `palette: kinari` を書く。
|
|
549
695
|
*
|
|
550
696
|
* ## 行の縞
|
|
551
697
|
*
|
|
552
|
-
*
|
|
553
|
-
*
|
|
554
|
-
* しないよう、出口で揃える。
|
|
698
|
+
* 縞の色は配色からしか来ない。 配色が無い図に敷くと箱の面と同じ色に落ちて 1 本も出ないので、
|
|
699
|
+
* **配色が決まった図の、行を書いた表の箱** にだけ敷く。
|
|
555
700
|
*
|
|
556
|
-
*
|
|
557
|
-
*
|
|
701
|
+
* 畳む前は組み立て器 `er()` が縞を敷く経路と、箱を直に組む経路の 2 つがあり、同じ図が
|
|
702
|
+
* 動きの有無で縞を持ったり持たなかったりしていた。 経路が 1 本になった後も出口で揃える。
|
|
558
703
|
*/
|
|
559
704
|
function 配色と縞を当てる(diagram: CdlDiagram, doc: DslDocument): void {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
if (
|
|
705
|
+
diagram.textMetrics = "widest";
|
|
706
|
+
const 配色 = doc.theme;
|
|
707
|
+
if (配色 === undefined) return;
|
|
708
|
+
diagram.palette = 配色;
|
|
563
709
|
for (const node of diagram.nodes) {
|
|
564
|
-
if (node
|
|
710
|
+
if (棒を描く節か(node)) {
|
|
711
|
+
const 棒の角 = 意匠の棒の角(配色, node);
|
|
712
|
+
if (棒の角 !== undefined) node.barCornerRadii = 棒の角;
|
|
713
|
+
}
|
|
714
|
+
if (node.kind === "storage" && 行を書いた箱か(node) && node.rowStripe === undefined) {
|
|
715
|
+
node.rowStripe = true;
|
|
716
|
+
}
|
|
565
717
|
}
|
|
566
718
|
}
|
|
567
719
|
|
|
@@ -708,15 +860,20 @@ function reportDrawNotHonored(doc: DslDocument, onNotice?: (n: CompileNotice) =>
|
|
|
708
860
|
});
|
|
709
861
|
continue;
|
|
710
862
|
}
|
|
711
|
-
//
|
|
712
|
-
const
|
|
713
|
-
|
|
863
|
+
// 語が別の図を指している形 (#1314)。 図は描けるので誤りにはしない
|
|
864
|
+
const 相手 = DRAW_TARGETS.get(phase.draw);
|
|
865
|
+
const 数の図の形 = doc.shape === undefined ? undefined : (resolveShape(doc.shape) ?? undefined);
|
|
866
|
+
if (相手 !== undefined && !描く語がその図を指すか(phase.draw, doc.type, 数の図の形)) {
|
|
867
|
+
// 数を描く図は 1 つの型に 9 つの形が入るので、相手も この図も形で名乗る (#2657)
|
|
868
|
+
const 名乗り = (t: PresetType, sh: DslShape | undefined): string =>
|
|
869
|
+
t === "chart" && sh !== undefined ? `type: chart と shape: ${sh}` : `type: ${t}`;
|
|
870
|
+
const この図 = 名乗り(doc.type, 数の図の形);
|
|
714
871
|
onNotice({
|
|
715
872
|
kind: "draw-target-mismatch",
|
|
716
873
|
actor: phase.name,
|
|
717
874
|
line,
|
|
718
|
-
message: `段 "${phase.name}" の draw: ${phase.draw} は
|
|
719
|
-
hint: `この図では draw: ${doc.type} と書いてください`,
|
|
875
|
+
message: `段 "${phase.name}" の draw: ${phase.draw} は ${この図} では効きません (${phase.draw} は ${名乗り(相手.type, 相手.shape)} の図に書きます)`,
|
|
876
|
+
hint: `この図では draw: ${doc.type === "chart" ? (doc.shape ?? "bar") : doc.type} と書いてください`,
|
|
720
877
|
});
|
|
721
878
|
}
|
|
722
879
|
}
|
|
@@ -728,7 +885,9 @@ function reportDrawNotHonored(doc: DslDocument, onNotice?: (n: CompileNotice) =>
|
|
|
728
885
|
* **語と図種の対応表から導く** (`DRAW_TARGETS`)。 一覧を写すと、語を足した時に片方だけ
|
|
729
886
|
* 古いまま残る。 描画側 (`cdl` の `DRAW_KINDS`) が対応する種別と一致する。
|
|
730
887
|
*/
|
|
731
|
-
const DRAWABLE_DOC_TYPES: ReadonlySet<PresetType> = new Set<PresetType>(
|
|
888
|
+
const DRAWABLE_DOC_TYPES: ReadonlySet<PresetType> = new Set<PresetType>(
|
|
889
|
+
[...DRAW_TARGETS.values()].map((v) => v.type),
|
|
890
|
+
);
|
|
732
891
|
|
|
733
892
|
/**
|
|
734
893
|
* 縦列を選べる図種で、一部の箱だけが縦列を書いた時に伝える (#1263)。
|
|
@@ -737,9 +896,12 @@ const DRAWABLE_DOC_TYPES: ReadonlySet<PresetType> = new Set<PresetType>(DRAW_TAR
|
|
|
737
896
|
* 書いた人の意図と一致する保証が無い。 **全部書くか 1 つも書かないか** を求める。
|
|
738
897
|
*
|
|
739
898
|
* 1 つも書いていない形は従来どおりの並びになるだけなので知らせない。
|
|
899
|
+
*
|
|
900
|
+
* 数える箱は置く側と同じ `縦列の判定に数える箱` から採る (#2372)。 別々に数えると、
|
|
901
|
+
* 見本だけの図で「一部だけ書いた」 形が知らせずに落ちる。
|
|
740
902
|
*/
|
|
741
903
|
function reportLaneMixed(doc: DslDocument, onNotice: (n: CompileNotice) => void): void {
|
|
742
|
-
const 対象 = doc
|
|
904
|
+
const 対象 = 縦列の判定に数える箱(doc);
|
|
743
905
|
const 書いた = 対象.filter((a) => a.lane !== undefined);
|
|
744
906
|
if (書いた.length === 0 || 書いた.length === 対象.length) return;
|
|
745
907
|
const 書いていない = 対象.filter((a) => a.lane === undefined);
|
|
@@ -810,11 +972,33 @@ function reportFlowEndpointNotHonored(
|
|
|
810
972
|
actor: 書いた名前(s.from),
|
|
811
973
|
line: s.pos.line,
|
|
812
974
|
message: `"${truncateForMessage(書いた名前(s.from))} -> ${truncateForMessage(書いた名前(s.to))}" の端は使われません (type: flow は登場人物を書いた順に繋ぎます)`,
|
|
813
|
-
hint:
|
|
975
|
+
hint: `書いた端どおりに繋ぐには次のどれかを書いてください: ${鎖にしない書き方の案内()} (どれも書いた端がそのまま矢印になります)`,
|
|
814
976
|
});
|
|
815
977
|
}
|
|
816
978
|
}
|
|
817
979
|
|
|
980
|
+
/**
|
|
981
|
+
* 板の言づてで、効かないと伝えない矢印の欄 (#2356)。
|
|
982
|
+
*
|
|
983
|
+
* **除外側を書く**。 効かない欄を並べる形にすると、矢印に欄を足した日にその欄だけが
|
|
984
|
+
* 一覧から漏れ、書いた人には「書いたのに何も起きない」 としか見えない。
|
|
985
|
+
*
|
|
986
|
+
* | 区分 | 欄 |
|
|
987
|
+
* |---|---|
|
|
988
|
+
* | 板が描く | 出どころ / 行き先 / 語 / 言づての種類 と、行番号と書いた場所 |
|
|
989
|
+
* | 別の知らせが受け持つ | 部品の端 (`part-node-ignored` が 1 件ずつ伝える) |
|
|
990
|
+
*/
|
|
991
|
+
const 板が伝えない矢印の欄: ReadonlySet<string> = new Set([
|
|
992
|
+
"no",
|
|
993
|
+
"from",
|
|
994
|
+
"to",
|
|
995
|
+
"label",
|
|
996
|
+
"msgKind",
|
|
997
|
+
"pos",
|
|
998
|
+
"fromPartNode",
|
|
999
|
+
"toPartNode",
|
|
1000
|
+
]);
|
|
1001
|
+
|
|
818
1002
|
/**
|
|
819
1003
|
* 順序図の言づてに書いた飾りが使われないことを伝える (#1466)。
|
|
820
1004
|
*
|
|
@@ -822,29 +1006,20 @@ function reportFlowEndpointNotHonored(
|
|
|
822
1006
|
* 色味 (`tone`) / 添え字 (`sub`) / 寄せ (`side`) が矢印に付いていたが、板では行になった。
|
|
823
1007
|
* 黙って落とすと、書いた側は効いていると思い込む。
|
|
824
1008
|
*
|
|
825
|
-
*
|
|
826
|
-
*
|
|
1009
|
+
* **伝える欄を並べない** (#2356)。 以前は色味 / 添え字 / 寄せ / 名前のずらし / 多重度 を手で並べて
|
|
1010
|
+
* いたため、後から足した項目がどこにも入らなかった (実測 = 矢印に書ける 21 項目のうち 13 件が、
|
|
1011
|
+
* 図も変わらず知らせも出ないまま落ちていた)。 板が描く欄を **除いた残り全部** を伝える。
|
|
827
1012
|
*
|
|
828
1013
|
* `actors` に無い名前を指す言づては見ない (#2111)。 その言づては組み立ての前に落ちて板に載らず、
|
|
829
1014
|
* 同じ行を居ない名前の知らせ (`reportMissingFlowActors`) が指している。
|
|
830
1015
|
*/
|
|
831
1016
|
function reportMessageOptionNotHonored(doc: DslDocument, onNotice?: (n: CompileNotice) => void): void {
|
|
832
1017
|
if (!onNotice) return;
|
|
833
|
-
if (doc.type !== "sequence"
|
|
1018
|
+
if (doc.type !== "sequence") return;
|
|
834
1019
|
const 名前の表 = actorRefTable(doc);
|
|
835
1020
|
for (const s of doc.flow) {
|
|
836
1021
|
if (!名前の表.has(s.from) || !名前の表.has(s.to)) continue;
|
|
837
|
-
const 効かない =
|
|
838
|
-
s.tone !== undefined ? "色味" : "",
|
|
839
|
-
s.sub !== undefined ? "添え字" : "",
|
|
840
|
-
s.side !== undefined ? "寄せ" : "",
|
|
841
|
-
// 名前のずらしも板には載せる先が無い (#1971)。 位置のずらし (`pos`) は名前のずらしに足す欄なので同じ扱い
|
|
842
|
-
s.layoutPos !== undefined || s.labelOffsetX !== undefined || s.labelOffsetY !== undefined
|
|
843
|
-
? "名前のずらし"
|
|
844
|
-
: "",
|
|
845
|
-
// 多重度も板には描く先が無い (#2107)。 他の図種は `reportCardinalityNotHonored` が知らせる
|
|
846
|
-
書いた多重度を読む(s.cardinality) !== undefined ? "多重度" : "",
|
|
847
|
-
].filter((x) => x !== "");
|
|
1022
|
+
const 効かない = 効かない矢印の欄を並べる(s, 板が伝えない矢印の欄);
|
|
848
1023
|
if (効かない.length === 0) continue;
|
|
849
1024
|
onNotice({
|
|
850
1025
|
kind: "message-option-not-honored",
|
|
@@ -856,10 +1031,268 @@ function reportMessageOptionNotHonored(doc: DslDocument, onNotice?: (n: CompileN
|
|
|
856
1031
|
}
|
|
857
1032
|
}
|
|
858
1033
|
|
|
1034
|
+
/**
|
|
1035
|
+
* 名前を指す欄かどうかを、 綴りで決める (#2405)。
|
|
1036
|
+
*
|
|
1037
|
+
* 部品の表 (`readout-table.generated.ts`) の 107 種が持つ欄のうち、 別の所に書いた値を名前で
|
|
1038
|
+
* 指すのは `source` / `〜Source` / `sourceA` の 3 つの形。 表そのものは「文字列」 としか
|
|
1039
|
+
* 言わないため、 どの文字列が名前かは綴りからしか決められない。
|
|
1040
|
+
*
|
|
1041
|
+
* **規則から外れた綴りで名前の欄が足されると、 その欄だけが黙る**。 規則に当たらない欄の
|
|
1042
|
+
* 一覧を検査で固定し、 増えた日に人が 1 件ずつ見分ける形にする
|
|
1043
|
+
* (`test/readout-source-2405.test.ts`)。
|
|
1044
|
+
*/
|
|
1045
|
+
function 名前を指す欄(欄名: string): boolean {
|
|
1046
|
+
return 欄名 === "source" || 欄名.endsWith("Source") || /^source[A-Z]$/.test(欄名);
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
/**
|
|
1050
|
+
* 値を見せる部品 (`readouts:`) が読む元の名前を突き合わせて知らせる (#2405)。
|
|
1051
|
+
*
|
|
1052
|
+
* ## 直す前に起きていたこと
|
|
1053
|
+
*
|
|
1054
|
+
* 読み取り値は `source: done` の形で、 別の所に書いた値を名前で指す。 **その名前が在るかを
|
|
1055
|
+
* 誰も見ていなかった** = 状態を指す図も、 どこにも無い名前を指す図も、 大文字小文字を
|
|
1056
|
+
* 間違えた図も、 すべて知らせ 0 件で同じ形のまま描く側へ渡っていた (実測 6 通り)。
|
|
1057
|
+
* 描く側は知らない名前を読むと何も出さないので、 書いた人には「部品を置いたのに数字が
|
|
1058
|
+
* 出ない」 だけが残る。
|
|
1059
|
+
*
|
|
1060
|
+
* ## 読む元は 3 つ
|
|
1061
|
+
*
|
|
1062
|
+
* `states` / `inputs` / `formulas`。 式の名前の突き合わせ (`formula-unresolved`) は
|
|
1063
|
+
* つまみと先に書いた式の 2 つしか見ないが、 読み取り値は状態も読めるので 3 つを合わせる。
|
|
1064
|
+
*
|
|
1065
|
+
* ## 3 つの節を全て載せ終えてから呼ぶ
|
|
1066
|
+
*
|
|
1067
|
+
* 記法は `readouts:` を 3 つの節より前に書ける。 途中で呼ぶと、 後から載る節の名前を
|
|
1068
|
+
* 「無い」 と知らせる。
|
|
1069
|
+
*
|
|
1070
|
+
* ## 見る欄を `source` だけにしない
|
|
1071
|
+
*
|
|
1072
|
+
* `stacked-bar` は `source` を持たず `sourceA` / `sourceB` だけを持つ。 `source` だけを
|
|
1073
|
+
* 見る形にすると、 その種類が丸ごと黙ったまま残る (表の 107 種が持つ名前の欄は 24 種類)。
|
|
1074
|
+
*
|
|
1075
|
+
* ## 1 つの部品で 2 つ外れたら 2 件とも出す
|
|
1076
|
+
*
|
|
1077
|
+
* 片方で打ち切ると、 直した次の回にもう片方が初めて出る。
|
|
1078
|
+
*/
|
|
1079
|
+
function reportReadoutSourceMissing(
|
|
1080
|
+
doc: DslDocument,
|
|
1081
|
+
merged: CdlDiagram,
|
|
1082
|
+
onNotice?: (n: CompileNotice) => void,
|
|
1083
|
+
): void {
|
|
1084
|
+
if (!onNotice) return;
|
|
1085
|
+
const 読み取り値 = doc.readouts ?? [];
|
|
1086
|
+
if (読み取り値.length === 0) return;
|
|
1087
|
+
const 書かれた名前 = new Set<string>([
|
|
1088
|
+
...(merged.states ?? []).map((x) => x.id),
|
|
1089
|
+
...(merged.inputs ?? []).map((x) => x.id),
|
|
1090
|
+
...(merged.formulas ?? []).map((x) => x.id),
|
|
1091
|
+
]);
|
|
1092
|
+
const hint = 書ける値の案内(書かれた名前);
|
|
1093
|
+
for (const r of 読み取り値) {
|
|
1094
|
+
for (const [欄, 値] of Object.entries(r)) {
|
|
1095
|
+
if (!名前を指す欄(欄) || typeof 値 !== "string") continue;
|
|
1096
|
+
if (書かれた名前.has(値)) continue;
|
|
1097
|
+
onNotice({
|
|
1098
|
+
kind: "readout-source-missing",
|
|
1099
|
+
actor: 値,
|
|
1100
|
+
line: r.pos?.line ?? 0,
|
|
1101
|
+
message: `部品 "${truncateForMessage(r.id)}" の ${欄} が、 状態でもつまみでも式でもない名前 "${truncateForMessage(値)}" を読んでいます`,
|
|
1102
|
+
hint,
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
/**
|
|
1109
|
+
* 状態 (`states:`) とつまみ (`inputs:`) に同じ名前を書いて、 値がずれたことを知らせる (#2413)。
|
|
1110
|
+
*
|
|
1111
|
+
* ## 両方に書くこと自体は意図された書き方
|
|
1112
|
+
*
|
|
1113
|
+
* 状態が静止した図の値を決め、 つまみが読み手に値を変えさせる。 カタログの見本 105 箇所が
|
|
1114
|
+
* この形で書かれているので、 重なりそのものは誤りにしない。
|
|
1115
|
+
*
|
|
1116
|
+
* ## ずれると状態の初期値は図のどこにも出ない
|
|
1117
|
+
*
|
|
1118
|
+
* 描く側は `mergeStateValues(base, overrides)` でつまみの既定値を状態の上に塗り、
|
|
1119
|
+
* 静止した最初の一枚にもつまみの既定値が入る。 見本 77 組は 1 組もずれておらず、
|
|
1120
|
+
* 書き手が全部手で揃えている決まりなのに、 守れているかを見る仕組みが無かった。
|
|
1121
|
+
*
|
|
1122
|
+
* ## 効く既定値を自前で導かない
|
|
1123
|
+
*
|
|
1124
|
+
* 種類によって値の作り方が違い、 欄の名前からは導けない (`timeline` は `defaultSpeedIdx` を
|
|
1125
|
+
* 持つが効く既定値は 0)。 描く側が書き出している `inputDefaultValue` をそのまま呼ぶ。
|
|
1126
|
+
*
|
|
1127
|
+
* ## 突き合わせは文字列で行う
|
|
1128
|
+
*
|
|
1129
|
+
* 描く側が `merged[key] = String(v)` で文字列にしてから塗るため、 `42` と `"42"` は
|
|
1130
|
+
* 同じ値として扱う。
|
|
1131
|
+
*
|
|
1132
|
+
* ## 式と巻き上げは対象にしない
|
|
1133
|
+
*
|
|
1134
|
+
* 静止した最初の一枚 (`serverSnapshot`) に入るのはつまみの既定値だけで、 式や巻き上げが
|
|
1135
|
+
* 値を作るまでは状態の初期値が見える。 置き値として役に立つので、 ずれていても誤りにならない。
|
|
1136
|
+
*/
|
|
1137
|
+
function reportStateInputMismatch(
|
|
1138
|
+
doc: DslDocument,
|
|
1139
|
+
merged: CdlDiagram,
|
|
1140
|
+
onNotice?: (n: CompileNotice) => void,
|
|
1141
|
+
): void {
|
|
1142
|
+
if (!onNotice) return;
|
|
1143
|
+
const つまみ = merged.inputs ?? [];
|
|
1144
|
+
if (つまみ.length === 0) return;
|
|
1145
|
+
const 状態 = new Map((doc.animate?.states ?? []).map((s) => [s.name, s]));
|
|
1146
|
+
if (状態.size === 0) return;
|
|
1147
|
+
for (const input of つまみ) {
|
|
1148
|
+
const s = 状態.get(input.id);
|
|
1149
|
+
if (s === undefined) continue;
|
|
1150
|
+
const 既定 = inputDefaultValue(input);
|
|
1151
|
+
if (既定 === undefined) continue;
|
|
1152
|
+
if (String(s.initial) === String(既定)) continue;
|
|
1153
|
+
onNotice({
|
|
1154
|
+
kind: "state-shadowed-by-input",
|
|
1155
|
+
actor: input.id,
|
|
1156
|
+
line: s.pos.line,
|
|
1157
|
+
message: `"${truncateForMessage(input.id)}" を states と inputs の両方に書いていますが、 値が違います (states の ${truncateForMessage(String(s.initial))} は出ません。 つまみの ${truncateForMessage(String(既定))} が使われます)`,
|
|
1158
|
+
hint: `states の初期値をつまみの既定値 ${truncateForMessage(String(既定))} に揃えるか、 どちらか一方だけに書く`,
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
/**
|
|
1164
|
+
* 「states / inputs / formulas に書いた名前で指す」 の直し方を 1 本作る (#2405)。
|
|
1165
|
+
*
|
|
1166
|
+
* 並べる件数と 1 件ずつの長さを切るのは `書ける面の案内` と同じ理由 = 名前を上限まで書いた
|
|
1167
|
+
* 図で知らせが数百 MB になる。
|
|
1168
|
+
*/
|
|
1169
|
+
function 書ける値の案内(書かれた名前: ReadonlySet<string>): string {
|
|
1170
|
+
const 見せる数 = 8;
|
|
1171
|
+
const 全部 = [...書かれた名前];
|
|
1172
|
+
if (全部.length === 0) return "states / inputs / formulas のどれかに値を書く";
|
|
1173
|
+
const 一覧 = 全部.slice(0, 見せる数).map(truncateForMessage).join(" / ");
|
|
1174
|
+
const 残り = 全部.length - 見せる数;
|
|
1175
|
+
return `states / inputs / formulas に書いた名前で指す (${一覧}${残り > 0 ? ` ほか ${残り} 件` : ""})`;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
/**
|
|
1179
|
+
* 順序図の帯 (`bands:`) に書いた面の名前と言づての番号を突き合わせて知らせる (#2404)。
|
|
1180
|
+
*
|
|
1181
|
+
* ## なぜ誤りにせず知らせにするか
|
|
1182
|
+
*
|
|
1183
|
+
* 帯が 1 本ずれても図は出る。 「別の所に書いた名前を指す」 欄は 4 つとも知らせで扱っており
|
|
1184
|
+
* (箱の `lane` / 組の `lanes` / 出来事の `box` / 矢印の端)、 帯だけを誤りにする理由が無い。
|
|
1185
|
+
*
|
|
1186
|
+
* ## 帯を描かない図種は、 それを先に知らせて打ち切る
|
|
1187
|
+
*
|
|
1188
|
+
* 帯は板の縦線に重ねる四角なので、 板を作らない図種には載せる先が無い
|
|
1189
|
+
* (実測 = 24 図種のうち 22 図種が `bands:` を読んだうえで黙って捨てていた)。
|
|
1190
|
+
* その図で名前や番号を直しても帯は出ないため、 直しても直らない案内を返さない。
|
|
1191
|
+
*
|
|
1192
|
+
* **図種を手で並べない**。 組み上がった図が板を持つかで決める = 図種を足した日にずれない
|
|
1193
|
+
* (`rules/quality.md` の導出可能記述)。
|
|
1194
|
+
*
|
|
1195
|
+
* ## なぜ読む側ではなくここで確かめるか
|
|
1196
|
+
*
|
|
1197
|
+
* 記法は `bands:` を `actors:` より前に書ける = 読む途中では面の一覧が揃っていない。
|
|
1198
|
+
* 板に載る言づての数も `flow:` を読み終えるまで決まらない。 組み立ての入口は両方を持っている。
|
|
1199
|
+
*
|
|
1200
|
+
* ## 突き合わせ先は「書いた `actors`」
|
|
1201
|
+
*
|
|
1202
|
+
* 板の図種は箱が 1 つしか無く、 面は箱の中の行になる = 組み上がった図と突き合わせる相手が居ない
|
|
1203
|
+
* (段の注目先 #2398 と同じ)。 名前の受け方は矢印の端と同じ表 (`actorRefTable`) を使うので、
|
|
1204
|
+
* id の形 (`api-gateway`) で書いた指定も通る。
|
|
1205
|
+
*
|
|
1206
|
+
* ## 番号が指すのは言づての行で、 0 から数える
|
|
1207
|
+
*
|
|
1208
|
+
* 記法の `- DB: 1..2` は段 (`animation` の `step`) ではなく **板に載る言づての行**を指す
|
|
1209
|
+
* (実測 = 描く側は `rowY(from)` と `rowY(to)` で四角の上下を決め、 `rowY(i)` は i 通目の
|
|
1210
|
+
* 言づての高さを返す)。 見本もその読み方で書かれている (面 4 つ ・ 言づて 7 通の図で
|
|
1211
|
+
* `ブラウザ: 0..5` / `DB: 6..6`)。
|
|
1212
|
+
*
|
|
1213
|
+
* 段と言づては数も起点も違う = 段で数えると、 正しく書いた見本を範囲外として知らせてしまう。
|
|
1214
|
+
*
|
|
1215
|
+
* ## 板に載らない言づてを数に入れない
|
|
1216
|
+
*
|
|
1217
|
+
* `actors` に無い名前を指す言づては組み立ての前に落ちて板に載らない (実測 = 3 通のうち 1 通が
|
|
1218
|
+
* 落ちて 2 通になる)。 書いた行数で数えると、 落ちた分だけ上限が広がって範囲外を見逃す。
|
|
1219
|
+
*
|
|
1220
|
+
* ## 言づてが 1 通も無い図でも黙らない
|
|
1221
|
+
*
|
|
1222
|
+
* 比べる相手が無いからと飛ばすと、 帯を書いたのに何も描かれない図が黙る経路になる。
|
|
1223
|
+
* 範囲を示せないので、 理由を「言づてが 1 通も無い」 に変えて同じ知らせを出す。
|
|
1224
|
+
*
|
|
1225
|
+
* ## 負の番号はここで見ない
|
|
1226
|
+
*
|
|
1227
|
+
* 記法は数字だけを受ける形 (`\d+`) で読み、 JSON は「0 以上の整数」 を要求する = 2 つの入口が
|
|
1228
|
+
* どちらも先に断っている。 ここに枝を置いても 1 度も通らない。 入口の側が緩んだら気付けるよう、
|
|
1229
|
+
* 断ることそのものを検査で固定する。
|
|
1230
|
+
*
|
|
1231
|
+
* ## 名前と番号で打ち切らない
|
|
1232
|
+
*
|
|
1233
|
+
* 両方外れた帯には 2 件とも出す。 片方で止めると、 名前を直した次の回に番号の誤りが初めて出る。
|
|
1234
|
+
*/
|
|
1235
|
+
function reportBandProblems(
|
|
1236
|
+
doc: DslDocument,
|
|
1237
|
+
diagram: CdlDiagram,
|
|
1238
|
+
onNotice?: (n: CompileNotice) => void,
|
|
1239
|
+
): void {
|
|
1240
|
+
if (!onNotice) return;
|
|
1241
|
+
const 帯 = doc.bands ?? [];
|
|
1242
|
+
if (帯.length === 0) return;
|
|
1243
|
+
const 板がある = diagram.nodes.some((n) => n.sequenceData !== undefined);
|
|
1244
|
+
if (!板がある) {
|
|
1245
|
+
for (const b of 帯) {
|
|
1246
|
+
onNotice({
|
|
1247
|
+
kind: "band-not-honored",
|
|
1248
|
+
actor: b.actor,
|
|
1249
|
+
line: b.pos?.line ?? 0,
|
|
1250
|
+
message: `type: ${doc.type} は帯を描きません (書いた帯は図に出ません)`,
|
|
1251
|
+
hint: "帯は板の縦線に重ねる四角なので、 type: sequence で使う",
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
const 名前の表 = actorRefTable(doc);
|
|
1257
|
+
const hint = 書ける面の案内(doc);
|
|
1258
|
+
const 言づての数 = doc.flow.filter((s) => 名前の表.has(s.from) && 名前の表.has(s.to)).length;
|
|
1259
|
+
for (const b of 帯) {
|
|
1260
|
+
const line = b.pos?.line ?? 0;
|
|
1261
|
+
if (!名前の表.has(b.actor)) {
|
|
1262
|
+
onNotice({
|
|
1263
|
+
kind: "band-actor-missing",
|
|
1264
|
+
actor: b.actor,
|
|
1265
|
+
line,
|
|
1266
|
+
message: `帯が "${truncateForMessage(b.actor)}" を指していますが、 actors に書かれていません (先頭の面に帯が出ます)`,
|
|
1267
|
+
hint,
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
const 外れ =
|
|
1271
|
+
言づての数 === 0
|
|
1272
|
+
? "板に載る言づてが 1 通もありません"
|
|
1273
|
+
: b.to > 言づての数 - 1
|
|
1274
|
+
? `板に載る言づては ${言づての数} 通で、 最後は ${言づての数 - 1} 番です`
|
|
1275
|
+
: b.from > b.to
|
|
1276
|
+
? "始まりが終わりより後ろです"
|
|
1277
|
+
: undefined;
|
|
1278
|
+
if (外れ === undefined) continue;
|
|
1279
|
+
onNotice({
|
|
1280
|
+
kind: "band-row-out-of-range",
|
|
1281
|
+
actor: b.actor,
|
|
1282
|
+
line,
|
|
1283
|
+
message: `帯 "${truncateForMessage(b.actor)}: ${b.from}..${b.to}" の区間が言づての並びに収まりません (${外れ})`,
|
|
1284
|
+
hint:
|
|
1285
|
+
言づての数 === 0
|
|
1286
|
+
? "帯は言づての行に重ねる四角なので、 flow に言づてを書く"
|
|
1287
|
+
: `0..${言づての数 - 1} の範囲で、 始まりを終わり以下にする (番号は flow に書いた言づての行、 段ではありません)`,
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
|
|
859
1292
|
/**
|
|
860
1293
|
* 矢印に書いた多重度 (`cardinality`) から端の形を描けない時に伝える (#2107)。
|
|
861
1294
|
*
|
|
862
|
-
* 多重度で端の形が決まるのは `type:
|
|
1295
|
+
* 多重度で端の形が決まるのは `type: record` の 6 語だけ (描画側の `ER_CARDINALITY_HEAD`)。
|
|
863
1296
|
* `er` でそれ以外の語を書くと名前に `(語)` と添えるだけで、`er` 以外では何も描かない。
|
|
864
1297
|
* 黙って通すと、書いた側は端の形が付くと思い込む (`N:M` の代わりに `N:N` と書く形で起きる)。
|
|
865
1298
|
*
|
|
@@ -872,7 +1305,7 @@ function reportMessageOptionNotHonored(doc: DslDocument, onNotice?: (n: CompileN
|
|
|
872
1305
|
* |---|---|---|
|
|
873
1306
|
* | 順序図の板の言づて | 図種 | `reportMessageOptionNotHonored` が他の飾りと合わせて伝える |
|
|
874
1307
|
* | `actors` に無い名前を指す矢印 | 名前の解決に使う表 (`actorRefTable`) | 居ない名前の知らせ (`reportMissingFlowActors`) |
|
|
875
|
-
* | 図種の組み立てが知らせた行 | 実際に出た知らせ | 捨てた矢印 / 自分を親にする矢印 /
|
|
1308
|
+
* | 図種の組み立てが知らせた行 | 実際に出た知らせ | 捨てた矢印 / 自分を親にする矢印 / ガントチャートの矢印の飾り (多重度を含む) など |
|
|
876
1309
|
*
|
|
877
1310
|
* 値の図 (`pie` 等) は捨てた本数を最初の矢印の行でまとめて伝えるため、2 本目以降の矢印に書いた
|
|
878
1311
|
* 多重度はここで伝わる。
|
|
@@ -890,7 +1323,7 @@ function reportCardinalityNotHonored(
|
|
|
890
1323
|
onNotice?: (n: CompileNotice) => void,
|
|
891
1324
|
): void {
|
|
892
1325
|
if (!onNotice) return;
|
|
893
|
-
if (doc.type === "sequence"
|
|
1326
|
+
if (doc.type === "sequence") return;
|
|
894
1327
|
const 名前の表 = actorRefTable(doc);
|
|
895
1328
|
for (const s of doc.flow) {
|
|
896
1329
|
const 書いた = 書いた多重度を読む(s.cardinality);
|
|
@@ -900,17 +1333,13 @@ function reportCardinalityNotHonored(
|
|
|
900
1333
|
if (図種が知らせた行.has(line)) continue;
|
|
901
1334
|
const 矢印 = `${truncateForMessage(s.from)} -> ${truncateForMessage(s.to)}`;
|
|
902
1335
|
const 字 = truncateForMessage(書いた.字);
|
|
903
|
-
if (doc.type !== "
|
|
1336
|
+
if (doc.type !== "record") {
|
|
904
1337
|
onNotice({
|
|
905
1338
|
kind: "cardinality-not-honored",
|
|
906
1339
|
actor: s.from,
|
|
907
1340
|
line,
|
|
908
1341
|
message: `${矢印} に書いた多重度 "${字}" は効きません (type: ${doc.type} は多重度を描きません)`,
|
|
909
|
-
|
|
910
|
-
hint:
|
|
911
|
-
doc.type === "class"
|
|
912
|
-
? "クラス図の多重度は、行き先の側を sub、出どころの側を tailSub に書いてください"
|
|
913
|
-
: "多重度から両端の形を描くのは type: er です",
|
|
1342
|
+
hint: "多重度から両端の形を描くのは type: record です",
|
|
914
1343
|
});
|
|
915
1344
|
continue;
|
|
916
1345
|
}
|
|
@@ -938,7 +1367,7 @@ function reportCardinalityNotHonored(
|
|
|
938
1367
|
*/
|
|
939
1368
|
function reportFlowOffsetNotHonored(doc: DslDocument, onNotice?: (n: CompileNotice) => void): void {
|
|
940
1369
|
if (!onNotice) return;
|
|
941
|
-
if (doc.type === "sequence"
|
|
1370
|
+
if (doc.type === "sequence") return;
|
|
942
1371
|
for (const s of doc.flow) {
|
|
943
1372
|
if (s.layoutPos === undefined) continue;
|
|
944
1373
|
if (s.label !== undefined && s.label.trim() !== "") continue;
|
|
@@ -959,38 +1388,34 @@ function reportFlowOffsetNotHonored(doc: DslDocument, onNotice?: (n: CompileNoti
|
|
|
959
1388
|
* 面ごとの箱が無いので、種類 / 大きさ / 位置 / 行 / 色 / 小見出し / 値 / 図形を載せる先も無い。
|
|
960
1389
|
* 黙って落とすと、書いた側は効いていると思い込む。
|
|
961
1390
|
*
|
|
1391
|
+
* **伝える欄を並べない** (#2358)。 以前は 8 種を手で並べていたため、後から足した項目が
|
|
1392
|
+
* どこにも入らなかった (実測 = 箱に書ける 31 項目のうち 11 件が、図も変わらず知らせも出ない)。
|
|
1393
|
+
* 板が描く欄を **除いた残り全部** を伝える。
|
|
1394
|
+
*
|
|
962
1395
|
* 見本 (`parts`) を重ねた面は対象外 = 見本は別経路で図に取り込まれ、板の見出しには並ばない。
|
|
963
1396
|
*/
|
|
964
1397
|
function reportActorKindNotHonored(doc: DslDocument, onNotice?: (n: CompileNotice) => void): void {
|
|
965
1398
|
if (!onNotice) return;
|
|
966
|
-
if (doc.type !== "sequence"
|
|
1399
|
+
if (doc.type !== "sequence") return;
|
|
1400
|
+
/*
|
|
1401
|
+
* **種類で並べ替える図では鳴らさない** (#2655)。
|
|
1402
|
+
*
|
|
1403
|
+
* 種類は板の絵には出ないが、並び順を書いた図では **面の並びを決めている**ので、
|
|
1404
|
+
* 書いた意味は figure に出ている。 落ちたと伝えると、正しく効いている指定に毎回鳴る。
|
|
1405
|
+
*
|
|
1406
|
+
* 畳む前はこの判定を図種 (`solidity`) で行っていた。 型を畳んだので、同じことを
|
|
1407
|
+
* 並び順が効いているかで見る = 古い名前で書いた図は読み替えでこの語が入るため、
|
|
1408
|
+
* 鳴らないという振る舞いが前と変わらない。
|
|
1409
|
+
*/
|
|
1410
|
+
if (並び順が効くか(doc)) return;
|
|
967
1411
|
for (const a of doc.actors) {
|
|
968
1412
|
if (a.partId !== undefined) continue;
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
*
|
|
976
|
-
* 記法を通すと既定の `actor` が必ず入るため、値ではなく書いたかどうかの印で見る。
|
|
977
|
-
* 記法を通さず直接組み立てた場合はこの印が無いので、種類を置いたこと自体を「書いた」 とみなす
|
|
978
|
-
*/
|
|
979
|
-
doc.type === "sequence" && a.kindWritten !== false && a.kind !== undefined ? "種類" : "",
|
|
980
|
-
a.posW !== undefined || a.posH !== undefined ? "大きさ" : "",
|
|
981
|
-
a.posX !== undefined ||
|
|
982
|
-
a.posY !== undefined ||
|
|
983
|
-
a.posRel !== undefined ||
|
|
984
|
-
// 位置のずらし (#1971)。 板は面ごとの箱を持たないので動かす相手が無い
|
|
985
|
-
a.layoutPos !== undefined
|
|
986
|
-
? "位置"
|
|
987
|
-
: "",
|
|
988
|
-
a.rows !== undefined ? "行" : "",
|
|
989
|
-
a.tone !== undefined ? "色" : "",
|
|
990
|
-
a.eyebrow !== undefined ? "小見出し" : "",
|
|
991
|
-
a.value !== undefined ? "値" : "",
|
|
992
|
-
a.shape !== undefined ? "図形" : "",
|
|
993
|
-
].filter((x) => x !== "");
|
|
1413
|
+
/*
|
|
1414
|
+
* 記法を通すと既定の `actor` が必ず入るため、値ではなく書いたかどうかの印で見る。
|
|
1415
|
+
* 記法を通さず直接組み立てた場合はこの印が無いので、種類を置いたこと自体を「書いた」 とみなす
|
|
1416
|
+
*/
|
|
1417
|
+
const 判定で外す = doc.type !== "sequence" || a.kindWritten === false ? ["kind"] : [];
|
|
1418
|
+
const 効かない = 効かない箱の欄を並べる(a, 板が伝えない箱の欄, 判定で外す);
|
|
994
1419
|
if (効かない.length === 0) continue;
|
|
995
1420
|
onNotice({
|
|
996
1421
|
kind: "actor-kind-not-honored",
|
|
@@ -1003,88 +1428,471 @@ function reportActorKindNotHonored(doc: DslDocument, onNotice?: (n: CompileNotic
|
|
|
1003
1428
|
}
|
|
1004
1429
|
|
|
1005
1430
|
/**
|
|
1006
|
-
*
|
|
1007
|
-
*
|
|
1008
|
-
* 縦列は **図種が決める**。 `flow` / `topology` は 1 本にまとめ、 `swimlane` / `er` / `state`
|
|
1009
|
-
* は箱ごとに 1 本作り、 `sequence` はそれがそのまま生命線になる。 図全体を 1 箱にする図種
|
|
1010
|
-
* (`pie` / `bar` 等) では箱が 1 つしかない。
|
|
1431
|
+
* 矢印を骨格として描く図種 (#2364)。
|
|
1011
1432
|
*
|
|
1012
|
-
*
|
|
1013
|
-
*
|
|
1014
|
-
* 縦列を伝える。
|
|
1433
|
+
* 木の図と思考の地図は矢印を親子のつながりとして描き、線の形も向きも階層から決める。
|
|
1434
|
+
* 飾りを載せる先が無い。
|
|
1015
1435
|
*
|
|
1016
|
-
*
|
|
1017
|
-
*
|
|
1018
|
-
|
|
1436
|
+
* ガントチャート (`gantt`) も同じ性質を持つが、知らせを図種ごとの組み立て (`compile/gantt.ts`) の
|
|
1437
|
+
* 中で出しているので、ここには入れない。
|
|
1438
|
+
*/
|
|
1439
|
+
const 矢印を骨格にする図種: ReadonlySet<string> = new Set(["tree", "mind"]);
|
|
1440
|
+
|
|
1441
|
+
/**
|
|
1442
|
+
* 矢印を骨格として描く図で、矢印に書いた飾りが使われないことを伝える (#2364)。
|
|
1019
1443
|
*
|
|
1020
|
-
*
|
|
1444
|
+
* 実測 = 矢印に書ける 21 項目のうち 18 件が、図も変わらず知らせも出なかった (2 図種とも)。
|
|
1445
|
+
* 文字を足すと 19 件になる。
|
|
1021
1446
|
*
|
|
1022
|
-
*
|
|
1023
|
-
*
|
|
1024
|
-
|
|
1447
|
+
* 呼び名は板と同じ表を使う (`効かない矢印の欄の呼び名`) = 同じ欄を 2 つの文言で呼ぶと、
|
|
1448
|
+
* 図種を変えた読み手が同じ飾りだと気付けない。
|
|
1449
|
+
*/
|
|
1450
|
+
function reportSkeletonEdgeOptionNotHonored(
|
|
1451
|
+
doc: DslDocument,
|
|
1452
|
+
図種が知らせた行: ReadonlySet<number>,
|
|
1453
|
+
onNotice?: (n: CompileNotice) => void,
|
|
1454
|
+
): void {
|
|
1455
|
+
if (!onNotice) return;
|
|
1456
|
+
if (!矢印を骨格にする図種.has(doc.type)) return;
|
|
1457
|
+
const 名前の表 = actorRefTable(doc);
|
|
1458
|
+
for (const s of doc.flow) {
|
|
1459
|
+
if (!名前の表.has(s.from) || !名前の表.has(s.to)) continue;
|
|
1460
|
+
/*
|
|
1461
|
+
* 図種の組み立てが知らせた行には重ねない (#2107 と同じ決まり)。
|
|
1462
|
+
*
|
|
1463
|
+
* 捨てた矢印 (自分を親にする形など) は組み立てが既に伝えており、そこへ飾りの知らせを
|
|
1464
|
+
* 足すと同じ行に 2 件並ぶ。 実測 = `A -> A` に多重度を書いた木の図で 2 件出た。
|
|
1465
|
+
*/
|
|
1466
|
+
if (図種が知らせた行.has(s.pos?.line ?? 0)) continue;
|
|
1467
|
+
const 効かない = 効かない矢印の欄を並べる(s, 向きだけを使う図が伝えない矢印の欄);
|
|
1468
|
+
if (効かない.length === 0) continue;
|
|
1469
|
+
onNotice({
|
|
1470
|
+
kind: "edge-option-not-honored",
|
|
1471
|
+
actor: s.from,
|
|
1472
|
+
line: s.pos?.line ?? 0,
|
|
1473
|
+
message: `type: ${doc.type} で "${truncateForMessage(s.from)} -> ${truncateForMessage(s.to)}" に書いた ${効かない.join(" / ")} は描けません (矢印は親子のつながりだけを描きます)`,
|
|
1474
|
+
hint: "つながりの線は階層から決まるため飾りを載せる先がありません。 箱の名前で伝えるか、矢印を自分で引く図種を使ってください",
|
|
1475
|
+
});
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
/**
|
|
1480
|
+
* 木の図で、箱に書いた指定が使われないことを伝える (#2360)。
|
|
1025
1481
|
*
|
|
1026
|
-
*
|
|
1482
|
+
* 木の図は箱を階層の節として描き、位置も大きさも親子関係から決める。 箱ごとの飾りを
|
|
1483
|
+
* 載せる先が無いのは設計どおりだが、**書いても知らせが出なかった** (実測 = 箱に書ける
|
|
1484
|
+
* 31 項目のうち 21 件が、図も変わらず知らせも出ない)。
|
|
1027
1485
|
*
|
|
1028
|
-
*
|
|
1029
|
-
*
|
|
1030
|
-
* 入っている。 二重に伝えると同じ 1 行について知らせが 2 件並ぶ。
|
|
1486
|
+
* 呼び名は板と同じ表を使う (`箱の欄の呼び名`) = 同じ欄を 2 つの文言で呼ぶと、
|
|
1487
|
+
* 図種を変えた読み手が同じ指定だと気付けない。
|
|
1031
1488
|
*/
|
|
1032
|
-
function
|
|
1489
|
+
function reportTreeActorOptionNotHonored(
|
|
1490
|
+
doc: DslDocument,
|
|
1491
|
+
onNotice?: (n: CompileNotice) => void,
|
|
1492
|
+
): void {
|
|
1033
1493
|
if (!onNotice) return;
|
|
1034
|
-
if (doc.type
|
|
1035
|
-
// 縦列を選べる図種では、全ての箱が書いていれば効く (#1263)。 効く形では知らせない
|
|
1036
|
-
const 効く図種 = 縦列を選べる図種.has(doc.type);
|
|
1037
|
-
if (効く図種 && 書いた縦列に置く(doc.type, doc)) return;
|
|
1038
|
-
if (効く図種) {
|
|
1039
|
-
reportLaneMixed(doc, onNotice);
|
|
1040
|
-
return;
|
|
1041
|
-
}
|
|
1494
|
+
if (doc.type !== "tree") return;
|
|
1042
1495
|
for (const a of doc.actors) {
|
|
1043
1496
|
if (a.partId !== undefined) continue;
|
|
1044
|
-
|
|
1497
|
+
// 種類は書かなくても既定の `actor` が入るため、値ではなく書いたかどうかの印で見る (#1058)
|
|
1498
|
+
const 効かない = 効かない箱の欄を並べる(
|
|
1499
|
+
a,
|
|
1500
|
+
木の図が伝えない箱の欄,
|
|
1501
|
+
a.kindWritten === false ? ["kind"] : [],
|
|
1502
|
+
);
|
|
1503
|
+
if (効かない.length === 0) continue;
|
|
1045
1504
|
onNotice({
|
|
1046
|
-
kind: "
|
|
1505
|
+
kind: "actor-option-not-honored",
|
|
1047
1506
|
actor: a.name,
|
|
1048
1507
|
line: a.pos?.line ?? 0,
|
|
1049
|
-
message: `"${truncateForMessage(a.name)}" に書いた
|
|
1050
|
-
hint: "
|
|
1508
|
+
message: `"${truncateForMessage(a.name)}" に書いた ${効かない.join(" / ")} は効きません (type: tree は名前と値だけを描き、位置も大きさも親子関係から決めます)`,
|
|
1509
|
+
hint: "木の図の箱は階層の節なので飾りを載せる先がありません。 呼び名 (subtitle) に書くか、箱を並べる図種を使ってください",
|
|
1051
1510
|
});
|
|
1052
1511
|
}
|
|
1053
1512
|
}
|
|
1054
1513
|
|
|
1055
1514
|
/**
|
|
1056
|
-
*
|
|
1515
|
+
* 知らせの文に出す「その図種が読むもの」 (#2370)。
|
|
1057
1516
|
*
|
|
1058
|
-
*
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1517
|
+
* 表に無い図種は「名前と値」 とする = 足し忘れても文が消えず、読み手に伝わる。
|
|
1518
|
+
*/
|
|
1519
|
+
const 図種が読むものの呼び名: ReadonlyMap<string, string> = new Map([
|
|
1520
|
+
["chart", "名前と値と色"],
|
|
1521
|
+
["gantt", "名前と時期と色と担当"],
|
|
1522
|
+
["journey", "名前と気持ちと接点"],
|
|
1523
|
+
]);
|
|
1524
|
+
|
|
1525
|
+
/**
|
|
1526
|
+
* 箱を名前と値の組として読む図で、箱に書いた指定が使われないことを伝える (#2368 / #2370)。
|
|
1063
1527
|
*
|
|
1064
|
-
*
|
|
1528
|
+
* 対象の図種と、図種ごとに読む欄は `値として読む図種` が持つ (実装が SSOT)。
|
|
1529
|
+
*
|
|
1530
|
+
* 実測 = 箱に書ける 30 項目のうち、値の図で 17 件 / じょうごで 19 件 /
|
|
1531
|
+
* ガントチャートと体験の地図と四象限で 22-23 件が、図も変わらず知らせも出なかった。
|
|
1532
|
+
*
|
|
1533
|
+
* 位置 (`posX` + `posY`) は片方だけだと別の知らせ (#2362) が出るため、1 項目ずつ足す走査では
|
|
1534
|
+
* 「知らせる」 に数えられ、組で書いた時の穴が隠れていた。
|
|
1535
|
+
*
|
|
1536
|
+
* 呼び名は板と木の図と同じ表を使う (`効かない箱の欄の呼び名`) = 同じ欄を 2 つの文言で呼ぶと、
|
|
1537
|
+
* 図種を変えた読み手が同じ指定だと気付けない。
|
|
1065
1538
|
*/
|
|
1066
|
-
function
|
|
1539
|
+
function reportValueChartActorOptionNotHonored(
|
|
1067
1540
|
doc: DslDocument,
|
|
1068
|
-
onNotice?: (
|
|
1541
|
+
onNotice?: (n: CompileNotice) => void,
|
|
1069
1542
|
): void {
|
|
1070
1543
|
if (!onNotice) return;
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
//
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1544
|
+
if (!値として読む図種.has(doc.type)) return;
|
|
1545
|
+
const 伝えない欄 = 値として読む図が伝えない箱の欄(doc.type);
|
|
1546
|
+
// 何を読むかを知らせの文に出す。 図種ごとに違うので表から引く (実装が SSOT)
|
|
1547
|
+
const 読む欄 = 図種が読むものの呼び名.get(doc.type) ?? "名前と値";
|
|
1548
|
+
for (const a of doc.actors) {
|
|
1549
|
+
if (a.partId !== undefined) continue;
|
|
1550
|
+
const 判定で外す: string[] = [];
|
|
1551
|
+
// 種類は書かなくても既定の `actor` が入るため、値ではなく書いたかどうかの印で見る (#1058)
|
|
1552
|
+
if (a.kindWritten === false) 判定で外す.push("kind");
|
|
1553
|
+
/*
|
|
1554
|
+
* 呼び名は **値を書いていない箱でだけ** 値として読まれる (#2390)。
|
|
1555
|
+
* 読む側と同じ式 (`a.value ?? a.subtitle`) に合わせる = 条件を写すと、
|
|
1556
|
+
* 読む側を直した日に伝える側が古くなる。
|
|
1557
|
+
*/
|
|
1558
|
+
if (a.value === undefined) 判定で外す.push("subtitle");
|
|
1559
|
+
const 効かない = 効かない箱の欄を並べる(a, 伝えない欄, 判定で外す);
|
|
1560
|
+
if (効かない.length === 0) continue;
|
|
1561
|
+
onNotice({
|
|
1562
|
+
kind: "actor-option-not-honored",
|
|
1563
|
+
actor: a.name,
|
|
1564
|
+
line: a.pos?.line ?? 0,
|
|
1565
|
+
message: `"${truncateForMessage(a.name)}" に書いた ${効かない.join(" / ")} は効きません (type: ${doc.type} は${読む欄}だけを読み、1 枚の図にまとめて描きます)`,
|
|
1566
|
+
hint: 値として読む図の案内(効かない),
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
/**
|
|
1572
|
+
* 値として読む図の案内を、伝えた欄から組み立てる (#2390)。
|
|
1573
|
+
*
|
|
1574
|
+
* **固定の 1 文を添えない** (#2382 の決まり)。 飾りの欄 (位置 / 大きさ / 図形 ほか) は
|
|
1575
|
+
* 「箱を並べる図種を使ってください」 が行き先になるが、呼び名の行き先は同じ図種の中にある =
|
|
1576
|
+
* 値を消せば値として読まれ、題 (`title`) を書けば箱に出る。
|
|
1577
|
+
* 1 文で済ませると、呼び名だけを書いた読み手が図種を変えろと読まされる。
|
|
1578
|
+
*/
|
|
1579
|
+
function 値として読む図の案内(効かない: readonly string[]): string | undefined {
|
|
1580
|
+
const 案内: string[] = [];
|
|
1581
|
+
if (効かない.includes("呼び名")) {
|
|
1582
|
+
案内.push(
|
|
1583
|
+
"呼び名を値として読ませるなら同じ箱の値 (value) を消します。 箱に題を出すなら title を書きます",
|
|
1584
|
+
);
|
|
1585
|
+
}
|
|
1586
|
+
if (効かない.some((欄) => 欄 !== "呼び名")) {
|
|
1587
|
+
案内.push(
|
|
1588
|
+
"値の図は箱ごとの絵を持たないため飾りを載せる先がありません。 箱を並べる図種を使ってください",
|
|
1589
|
+
);
|
|
1590
|
+
}
|
|
1591
|
+
return 案内.length === 0 ? undefined : 案内.join("、");
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
/**
|
|
1595
|
+
* 箱を並べて線で繋ぐ図で、箱に書いた指定が使われないことを伝える (#2376)。
|
|
1596
|
+
*
|
|
1597
|
+
* 対象の図種と、図種ごとに読まない欄は `骨組みの図種` が持つ (実装が SSOT)。
|
|
1598
|
+
*
|
|
1599
|
+
* 実測 = フロー 447 枚 ・ 泳路の図 35 枚 ・ 配置の図 8 枚の見本すべてで、始まりの印
|
|
1600
|
+
* (`initial`) ・ 終わりの印 (`final`) ・ 印 (`marks`) ・ 前の値 (`previous`) の 4 件が、
|
|
1601
|
+
* 図も変わらず知らせも出なかった。構成の図はこれに 1 件足した形になる。
|
|
1602
|
+
*
|
|
1603
|
+
* 始まりと終わりの印を読むのは状態遷移図だけ (`compile/generic.ts` の札)。
|
|
1604
|
+
* 同じ記法をフローに書いても札は出ないが、書き手には状態遷移図と同じに見える。
|
|
1605
|
+
*
|
|
1606
|
+
* 印 (`marks`) は **行と組にして読み替えられる図種でだけ** 外す (#2377)。
|
|
1607
|
+
* 読み替えの表は `行頭の印にする` が持つので、図種の一覧をここに写さず関数に聞く。
|
|
1608
|
+
* 行を書いていない印は、どの図種でも読み替える先が無いので伝える。
|
|
1609
|
+
*
|
|
1610
|
+
* 案内は **伝えた欄から組み立てる** (#2382)。 3 つの行き先を並べた 1 文を固定で添えていたが、
|
|
1611
|
+
* 状態遷移図を族に入れた時に 2 つが誤りになった = 読み手は既に状態遷移図に居るのに
|
|
1612
|
+
* 「始まりと終わりの印は type: record が描きます」 と読まされ、印については
|
|
1613
|
+
* 「type: record が描きます」 と読まされる (状態遷移図も描く。 足りないのは行のほう)。
|
|
1614
|
+
*
|
|
1615
|
+
* 段 (`stack`) は **図 1 枚ごとに読むかが決まる** (#2386)。 書いた番号の段に置くのは
|
|
1616
|
+
* 全ての箱が縦列を書いた図だけで、1 つでも書いていない箱があると図全体が別の並べ方に落ち、
|
|
1617
|
+
* 段はどの箱でも読まれない。 図種では表せない条件なので、置く側と同じ関数
|
|
1618
|
+
* (`書いた縦列に置く`) に聞く。
|
|
1619
|
+
*
|
|
1620
|
+
*/
|
|
1621
|
+
function reportSkeletonActorOptionNotHonored(
|
|
1622
|
+
doc: DslDocument,
|
|
1623
|
+
onNotice?: (n: CompileNotice) => void,
|
|
1624
|
+
): void {
|
|
1625
|
+
if (!onNotice) return;
|
|
1626
|
+
if (!骨組みの図種.has(doc.type)) return;
|
|
1627
|
+
const 伝えない欄 = 図ごとの条件も見た伝えない箱の欄(doc);
|
|
1628
|
+
for (const a of doc.actors) {
|
|
1629
|
+
if (a.partId !== undefined) continue;
|
|
1630
|
+
const 判定で外す: string[] = [];
|
|
1631
|
+
// 種類は書かなくても既定の `actor` が入るため、値ではなく書いたかどうかの印で見る (#1058)
|
|
1632
|
+
if (a.kindWritten === false || 泳法図で印をどう描く(doc, a) !== undefined) 判定で外す.push("kind");
|
|
1633
|
+
// 行と組にして読み替えられる印は効く = 効いている指定には鳴らさない
|
|
1634
|
+
if (
|
|
1635
|
+
a.rows !== undefined &&
|
|
1636
|
+
a.marks !== undefined &&
|
|
1637
|
+
行頭の印を読むか(doc.type)
|
|
1638
|
+
) {
|
|
1639
|
+
判定で外す.push("marks");
|
|
1640
|
+
}
|
|
1641
|
+
const 効かない = 効かない箱の欄を並べる(a, 伝えない欄, 判定で外す);
|
|
1642
|
+
if (効かない.length === 0) continue;
|
|
1643
|
+
onNotice({
|
|
1644
|
+
kind: "actor-option-not-honored",
|
|
1645
|
+
actor: a.name,
|
|
1646
|
+
line: a.pos?.line ?? 0,
|
|
1647
|
+
message: `"${truncateForMessage(a.name)}" に書いた ${効かない.join(" / ")} は効きません (type: ${doc.type} は箱を並べて線で繋ぐ図です)`,
|
|
1648
|
+
hint:
|
|
1649
|
+
doc.type === "swimlane" && doc.shape === "stages"
|
|
1650
|
+
? "札は段階ごとの列へ actors に書いた順に積み、種類は card で固定されます。 decision は札の右に「分かれ道」を添え、mark-start と mark-end は描きません"
|
|
1651
|
+
: doc.type === "swimlane" && doc.shape === "timeline"
|
|
1652
|
+
? "番号付きの札は軸の左右へ交互に並び、種類は card で固定されます。 decision の脇の札は同じ段へ置き、decision と mark-end は軸の上に描き、mark-start は描きません"
|
|
1653
|
+
: doc.type === "swimlane" && doc.shape === "metro"
|
|
1654
|
+
? "駅は actors に書いた順に並び、種類は station で固定されます。 decision / mark-start / mark-end は分かれ道と始まりと終わりの印として描きます"
|
|
1655
|
+
: 骨組みの図の案内(doc.type, 効かない),
|
|
1656
|
+
});
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
/**
|
|
1661
|
+
* その図で、効かないと伝えない箱の欄 (#2386 / #2388)。
|
|
1662
|
+
*
|
|
1663
|
+
* 図種ごとの違いは族の表 (`骨組みの図種`) が持つ。 **図 1 枚ごとに** 読むかが決まる欄は
|
|
1664
|
+
* 表では表せないので、ここで読まない図から抜いて伝える側へ回す。
|
|
1665
|
+
*
|
|
1666
|
+
* 条件は使う側と同じ関数に聞く = 2 か所に写すと、使う側を直した日に伝える側が古くなる。
|
|
1667
|
+
* 段 (`stack`) は縦列を選べる図種すべてに同じ条件が掛かるので `書いた縦列に置く` を直に呼ぶ。
|
|
1668
|
+
*
|
|
1669
|
+
* 図種ごとに違う条件を持つ欄は 1 つも無い (#2782)。 畳む前は表の図の種類 (`kind`) が
|
|
1670
|
+
* 2 経路に分かれていたが、経路を 1 本にしたので図種の表だけで決まる。
|
|
1671
|
+
*
|
|
1672
|
+
* @param doc 組み立てる本文
|
|
1673
|
+
* @returns 族の除外から、この図では読まない欄を抜いた集合
|
|
1674
|
+
*/
|
|
1675
|
+
function 図ごとの条件も見た伝えない箱の欄(doc: DslDocument): ReadonlySet<string> {
|
|
1676
|
+
const 族の除外 = 骨組みの図が伝えない箱の欄(doc.type);
|
|
1677
|
+
const 段階ごとの箱か = doc.type === "swimlane" && doc.shape === "stages";
|
|
1678
|
+
const 路線図か = doc.type === "swimlane" && doc.shape === "metro";
|
|
1679
|
+
const 時間軸か = doc.type === "swimlane" && doc.shape === "timeline";
|
|
1680
|
+
const 読まない欄: string[] = [];
|
|
1681
|
+
const この形が読む欄: string[] = [];
|
|
1682
|
+
if (doc.type === "flowchart") この形が読む欄.push("titleFontSize", "markGap");
|
|
1683
|
+
if (路線図か) この形が読む欄.push("stationNamePosition");
|
|
1684
|
+
if (時間軸か) この形が読む欄.push("subtitlePlacement");
|
|
1685
|
+
// 段を読まない図では伝える側へ回す。 既に抜けている図種 (c4) は下の `delete` が空振りする
|
|
1686
|
+
if (路線図か || 時間軸か || (!段階ごとの箱か && !書いた縦列に置く(doc.type, doc))) 読まない欄.push("stack");
|
|
1687
|
+
const 残り = new Set(族の除外);
|
|
1688
|
+
// `stage` はこの形でだけ箱の置き先を決める。 他の骨組みの図では知らせる側へ残す (#2797)。
|
|
1689
|
+
let 変えた = 段階ごとの箱か && !残り.has("stage");
|
|
1690
|
+
if (段階ごとの箱か) 残り.add("stage");
|
|
1691
|
+
// 3 形とも種類を固定する (段の箱は札、路線図は駅、時間軸は札)。書いた種類は伝える側へ回す (#2831)
|
|
1692
|
+
if ((段階ごとの箱か || 路線図か || 時間軸か) && 残り.delete("kind")) 変えた = true;
|
|
1693
|
+
for (const 欄 of 読まない欄) if (残り.delete(欄)) 変えた = true;
|
|
1694
|
+
for (const 欄 of この形が読む欄) {
|
|
1695
|
+
if (!残り.has(欄)) 変えた = true;
|
|
1696
|
+
残り.add(欄);
|
|
1697
|
+
}
|
|
1698
|
+
return 変えた ? 残り : 族の除外;
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
/**
|
|
1702
|
+
* 行頭の印を読む図種 (#2382)。 `行頭の印を読むか` に聞いて導く。
|
|
1703
|
+
*
|
|
1704
|
+
* 図種の名前を案内の文に写すと、読み替える図種を足した日に案内だけが古くなる。
|
|
1705
|
+
* 走査の範囲は骨組みの族に閉じる = この案内が出るのも族の中だけ。
|
|
1706
|
+
*/
|
|
1707
|
+
const 行頭の印を読む図種: readonly string[] = [...骨組みの図種.keys()].filter((t) =>
|
|
1708
|
+
行頭の印を読むか(t),
|
|
1709
|
+
);
|
|
1710
|
+
|
|
1711
|
+
/**
|
|
1712
|
+
* 骨組みの図の知らせに添える案内を、伝えた欄から組み立てる (#2382)。
|
|
1713
|
+
*
|
|
1714
|
+
* **伝えていない欄の行き先を書かない**。 3 つの行き先を並べた 1 文を固定で添えていた間、
|
|
1715
|
+
* 状態遷移図では 2 つが誤りになっていた。
|
|
1716
|
+
*
|
|
1717
|
+
* **行き先を持たない欄では案内を付けない**。 当たり障りのない 1 文で埋めると、
|
|
1718
|
+
* 読み手は次に何をすればよいか分からないまま「案内は読んだ」 状態になる。
|
|
1719
|
+
*
|
|
1720
|
+
* @param 図種 いま組んでいる図の種類
|
|
1721
|
+
* @param 効かない 知らせに並べた呼び名 (`効かない箱の欄を並べる` の返り値)
|
|
1722
|
+
* @returns 案内の文。 行き先を持つ欄が 1 つも無ければ `undefined`
|
|
1723
|
+
*/
|
|
1724
|
+
function 骨組みの図の案内(図種: string, 効かない: readonly string[]): string | undefined {
|
|
1725
|
+
const 案内: string[] = [];
|
|
1726
|
+
/*
|
|
1727
|
+
* 段は **縦列を選べる図種でだけ** 書き方の行き先を持つ (#2386)。 その図種で伝えたということは
|
|
1728
|
+
* 縦列を書いていない箱が在ったということなので、足せば効く。
|
|
1729
|
+
* 縦列を選べない図種 (構成の図) では、どう書いても効かないので行き先が無い。
|
|
1730
|
+
*/
|
|
1731
|
+
if (効かない.includes("段") && 縦列を選べる図種.has(図種 as never)) {
|
|
1732
|
+
案内.push("段は全ての箱に縦列 (lane) を書いた図で効きます");
|
|
1733
|
+
}
|
|
1734
|
+
if (効かない.includes("始まりの印") || 効かない.includes("終わりの印")) {
|
|
1735
|
+
案内.push("始まりと終わりの印は type: record が描きます");
|
|
1736
|
+
}
|
|
1737
|
+
if (効かない.includes("前の値")) 案内.push("前の値は値を並べる図が描きます");
|
|
1738
|
+
/*
|
|
1739
|
+
* この族で「色」 に立つのは色番号だけ (#2384)。 色味 (`tone`) は共通の除外に入っているので
|
|
1740
|
+
* ここへ来ない = 呼び名が同じでも、伝えているのは 16 進数で書いた形に限る。
|
|
1741
|
+
*/
|
|
1742
|
+
if (効かない.includes("色")) {
|
|
1743
|
+
案内.push("色番号が効くのは部品を置いた箱だけです。 色の名前 (color: 成功) は箱の色になります");
|
|
1744
|
+
}
|
|
1745
|
+
if (効かない.includes("印")) {
|
|
1746
|
+
案内.push(
|
|
1747
|
+
行頭の印を読む図種.includes(図種)
|
|
1748
|
+
? "行頭の印は行 (rows) と組にして書くと記号になります"
|
|
1749
|
+
: `行頭の印は ${行頭の印を読む図種.map((t) => `type: ${t}`).join(" と ")} が、行 (rows) と組にした時に描きます`,
|
|
1750
|
+
);
|
|
1751
|
+
}
|
|
1752
|
+
return 案内.length === 0 ? undefined : 案内.join("、");
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
/**
|
|
1756
|
+
* 箱の座標を片方だけ書いた時に伝える (#2362)。
|
|
1757
|
+
*
|
|
1758
|
+
* 座標を図に写す処理 (`applyCanvasPivotPositions`) は **横と縦の両方が書かれている** 時だけ
|
|
1759
|
+
* 動く。 片方だけの時は何もしないまま通るため、図が 1 bit も変わらず知らせも出なかった
|
|
1760
|
+
* (実測 = 座標が効く 5 図種すべてで消えた)。
|
|
1761
|
+
*
|
|
1762
|
+
* **効かせる側には倒さない**。 片方だけ動かす書き方は既にずらし (`offsetX` / `offsetY`) が
|
|
1763
|
+
* 受け持っており、座標に同じ役目を持たせると 2 つの書き方が同じことをする。
|
|
1764
|
+
*
|
|
1765
|
+
* 大きさ (`posW` / `posH`) は片方だけでも効くので見ない (実測)。
|
|
1766
|
+
* 部品として置いた箱は別経路 (`mergePartsFromActors`) が位置を決めるので外す。
|
|
1767
|
+
*/
|
|
1768
|
+
function reportHalfWrittenPosition(
|
|
1769
|
+
doc: DslDocument,
|
|
1770
|
+
箱に知らせた行: ReadonlySet<number>,
|
|
1771
|
+
onNotice?: (n: CompileNotice) => void,
|
|
1772
|
+
): void {
|
|
1773
|
+
if (!onNotice) return;
|
|
1774
|
+
for (const a of doc.actors) {
|
|
1775
|
+
if (a.partId !== undefined) continue;
|
|
1776
|
+
/*
|
|
1777
|
+
* 箱の知らせが出た行には重ねない (#2368)。
|
|
1778
|
+
*
|
|
1779
|
+
* 位置そのものが効かない図種では「片方だけでは決まらない」 という案内が誤りになる
|
|
1780
|
+
* (両方書いても効かない)。 その図種では箱の知らせが位置を含めて伝えている。
|
|
1781
|
+
*/
|
|
1782
|
+
if (箱に知らせた行.has(a.pos?.line ?? 0)) continue;
|
|
1783
|
+
const 書いた = a.posX !== undefined ? "posX" : a.posY !== undefined ? "posY" : undefined;
|
|
1784
|
+
if (書いた === undefined) continue;
|
|
1785
|
+
if (a.posX !== undefined && a.posY !== undefined) continue;
|
|
1786
|
+
const 足りない = 書いた === "posX" ? "posY" : "posX";
|
|
1787
|
+
onNotice({
|
|
1788
|
+
kind: "position-axis-missing",
|
|
1789
|
+
actor: a.name,
|
|
1790
|
+
line: a.pos?.line ?? 0,
|
|
1791
|
+
message: `"${truncateForMessage(a.name)}" に書いた ${書いた} だけでは位置が決まりません (${足りない} も書くと効きます)`,
|
|
1792
|
+
hint: `横と縦を両方書くか、片方だけ動かすなら ずらし (offsetX / offsetY) を使ってください`,
|
|
1793
|
+
});
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
/**
|
|
1798
|
+
* 箱に書いた縦列が効かないことを伝える (#1246)。
|
|
1799
|
+
*
|
|
1800
|
+
* 縦列は **図種が決める**。 `flow` / `topology` は 1 本にまとめ、 `swimlane` / `er` / `state`
|
|
1801
|
+
* は箱ごとに 1 本作り、 `sequence` はそれがそのまま生命線になる。 図全体を 1 箱にする図種
|
|
1802
|
+
* (`pie` / `bar` 等) では箱が 1 つしかない。
|
|
1803
|
+
*
|
|
1804
|
+
* **箱の `lane` を読むのは、 `縦列を選べる図種` で全ての箱が書いた時だけ** (#1263)。
|
|
1805
|
+
* 一部の箱だけが書いた形は `reportLaneMixed` が伝えるので、 ここではそれ以外の図種に書いた
|
|
1806
|
+
* 縦列を伝える。
|
|
1807
|
+
*
|
|
1808
|
+
* 黙って捨てると、 書いた縦列は消え、 `lanes:` で宣言した縦列だけが中身のないまま残る。
|
|
1809
|
+
* 実測 (#1246) = `type: flow` で `lane: ui` / `lane: api` を書くと箱は両方 `flow` に入り、
|
|
1810
|
+
* 宣言した `ui` / `api` は空のまま増えた。 知らせは 1 件も出なかった。
|
|
1811
|
+
*
|
|
1812
|
+
* ## なぜ組み立ての側で伝えるのか
|
|
1813
|
+
*
|
|
1814
|
+
* 記法の解析は図種を見ずに 1 行ずつ読む。 そこで弾くと **見本 (parts) の張替え先** まで
|
|
1815
|
+
* 巻き添えになる = 見本では `lane` が実際に読まれる (`mergePartsFromActors` が読む)。
|
|
1816
|
+
* 図種を知っているのは組み立ての側なので、 効くかどうかの判断もここに置く。
|
|
1817
|
+
*
|
|
1818
|
+
* ## 見本と `type: mind` では伝えない
|
|
1819
|
+
*
|
|
1820
|
+
* 見本は上のとおり実際に効く。 `type: mind` は描けない欄をまとめて 1 件で伝えており
|
|
1821
|
+
* (`compileMind` の「名前と副題 / 値、 枝の色しか描けません」)、 そこに `枠の指定` が既に
|
|
1822
|
+
* 入っている。 二重に伝えると同じ 1 行について知らせが 2 件並ぶ。
|
|
1823
|
+
*/
|
|
1824
|
+
function reportLaneNotHonored(doc: DslDocument, onNotice?: (n: CompileNotice) => void): void {
|
|
1825
|
+
if (!onNotice) return;
|
|
1826
|
+
if (doc.type === "mind") return;
|
|
1827
|
+
// 時間軸の lane は縦列ではなく札の担当名なので、箱ごとに省いてよい。
|
|
1828
|
+
if (doc.type === "swimlane" && doc.shape === "timeline") return;
|
|
1829
|
+
// 縦列を選べる図種では、全ての箱が書いていれば効く (#1263)。 効く形では知らせない
|
|
1830
|
+
const 効く図種 = 縦列を選べる図種.has(doc.type);
|
|
1831
|
+
if (効く図種 && 書いた縦列に置く(doc.type, doc)) return;
|
|
1832
|
+
if (効く図種) {
|
|
1833
|
+
reportLaneMixed(doc, onNotice);
|
|
1834
|
+
return;
|
|
1835
|
+
}
|
|
1836
|
+
for (const a of doc.actors) {
|
|
1837
|
+
if (a.partId !== undefined) continue;
|
|
1838
|
+
if (a.lane === undefined) continue;
|
|
1839
|
+
onNotice({
|
|
1840
|
+
kind: "lane-not-honored",
|
|
1841
|
+
actor: a.name,
|
|
1842
|
+
line: a.pos?.line ?? 0,
|
|
1843
|
+
message: `"${truncateForMessage(a.name)}" に書いた lane は効きません (縦列は type: ${doc.type} が決めます)`,
|
|
1844
|
+
hint: "縦列は図種が決めるため箱からは選べません。 lane を消してください (見本では張替え先として使えます)",
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
/**
|
|
1850
|
+
* 「actors に書いた名前で指す」 の直し方を 1 本作る (#1209、 #2404 で帯と共有)。
|
|
1851
|
+
*
|
|
1852
|
+
* **1 度だけ作る**。 知らせごとに全ての面の名前を並べ直すと、 名前も矢印も上限 (各 1,000) まで
|
|
1853
|
+
* 書いた図で数百 MB になる。 並べる数にも上限を置く。
|
|
1854
|
+
*
|
|
1855
|
+
* **1 件ずつの長さも切る**。 件数だけを絞っても、 名前 1 つが 2 万字なら知らせも 2 万字になる
|
|
1856
|
+
* (実測)。 名前も指定も外から来る文字列なので、 表示に使う所はすべて `truncateForMessage` を通す。
|
|
1857
|
+
*
|
|
1858
|
+
* 矢印の端と帯で同じ文を使う = 同じ「actors に無い名前を指した」 という誤りに、 欄ごとに違う
|
|
1859
|
+
* 直し方が返らないようにする。
|
|
1860
|
+
*/
|
|
1861
|
+
function 書ける面の案内(doc: DslDocument): string {
|
|
1862
|
+
const 見せる数 = 8;
|
|
1863
|
+
if (doc.actors.length === 0) return "actors に登場人物を書く";
|
|
1864
|
+
const 名前一覧 = doc.actors
|
|
1865
|
+
.slice(0, 見せる数)
|
|
1081
1866
|
.map((a) => truncateForMessage(a.name))
|
|
1082
1867
|
.join(" / ");
|
|
1083
1868
|
const 残り = doc.actors.length - 見せる数;
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1869
|
+
return `actors に書いた名前で指す (${名前一覧}${残り > 0 ? ` ほか ${残り} 件` : ""})`;
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
/**
|
|
1873
|
+
* 矢印が `actors` に無い名前を指したことを知らせる (#1209)。
|
|
1874
|
+
*
|
|
1875
|
+
* 知らせずに通すと、 **どちらに転んでも書いた人の意図が消える**。 動きを書いていない図では
|
|
1876
|
+
* 種類ごとの組み立てが actors を順に繋ぐため、 書いた矢印そのものが捨てられて label も
|
|
1877
|
+
* 消える (実測 = "変換" が "→" になった)。 動きを書いた図では名前がそのまま下流へ渡り、
|
|
1878
|
+
* 存在しない node を指す図ができて描画の直前で落ちる
|
|
1879
|
+
* (実測 = `unknown-ref: edge "e0-v-c" の from "v" が node に存在しません`)。
|
|
1880
|
+
*
|
|
1881
|
+
* 落ちる場所も消える場所も本文から遠いので、 書いた行で知らせる。
|
|
1882
|
+
*/
|
|
1883
|
+
function reportMissingFlowActors(
|
|
1884
|
+
doc: DslDocument,
|
|
1885
|
+
onNotice?: (notice: CompileNotice) => void,
|
|
1886
|
+
): void {
|
|
1887
|
+
if (!onNotice) return;
|
|
1888
|
+
const 表 = actorRefTable(doc);
|
|
1889
|
+
// hint は **1 度だけ作る**。 知らせごとに全 actor 名を並べ直すと、 名前も矢印も上限
|
|
1890
|
+
// (各 1,000) まで書いた図で数百 MB になる (Round 1 の指摘)。 並べる数にも上限を置く
|
|
1891
|
+
//
|
|
1892
|
+
// **1 件ずつの長さも切る**。 件数だけを絞っても、 名前 1 つが 2 万字なら知らせも 2 万字に
|
|
1893
|
+
// なる (Round 4 の実測)。 名前も矢印の指定も外から来る文字列なので、 表示に使う所は
|
|
1894
|
+
// すべて `truncateForMessage` を通す (光らせる相手の知らせと同じ扱い)。
|
|
1895
|
+
const hint = 書ける面の案内(doc);
|
|
1088
1896
|
const 知らせた = new Set<string>();
|
|
1089
1897
|
for (const s of doc.flow) {
|
|
1090
1898
|
for (const ref of [s.from, s.to]) {
|
|
@@ -1102,18 +1910,48 @@ function reportMissingFlowActors(
|
|
|
1102
1910
|
}
|
|
1103
1911
|
|
|
1104
1912
|
/**
|
|
1105
|
-
* 光らせる相手 (`focus:`)
|
|
1913
|
+
* 光らせる相手 (`focus:`) が届かない分を知らせる。 理由で 2 通りに分ける。
|
|
1106
1914
|
*
|
|
1107
1915
|
* 名前が当たらなかった指定は静かに消える。 光らせたい相手を書いたのに光らない状態が、
|
|
1108
1916
|
* 手掛かりなしで起きる。
|
|
1109
1917
|
*
|
|
1110
|
-
*
|
|
1111
|
-
*
|
|
1112
|
-
*
|
|
1918
|
+
* | 理由 | 見るもの | 知らせ |
|
|
1919
|
+
* |---|---|---|
|
|
1920
|
+
* | 記法にその名前が無い | 記述だけ | `focus-target-missing` (綴り違い) |
|
|
1921
|
+
* | 記法にはあるが、書いたとおりに光らない | 組み上がった図 | `focus-target-not-honored` (#2398) |
|
|
1922
|
+
*
|
|
1923
|
+
* **2 つを 1 つにまとめない**。 直し方が正反対で、前者は書き直せば直り、後者は同じ案内に
|
|
1924
|
+
* 従っても永久に直らない。 出来事 (`events:`) の側は #2336 で同じ分け方を持っており、
|
|
1925
|
+
* 注目先の側だけ持っていなかった。
|
|
1926
|
+
*
|
|
1927
|
+
* 綴り違いの側は **記述だけを見る**。 id の形は図種で違うが「その名前の箱が居るか」
|
|
1928
|
+
* 「その矢印が流れにあるか」 は書かれた内容だけで決まる。 図種ごとの解決経路に検査を分けると、
|
|
1929
|
+
* 経路が増えるたびに検査が取り残される。
|
|
1930
|
+
*
|
|
1931
|
+
* ## 何が光るかは図種で 3 通りに分かれる (実測)
|
|
1932
|
+
*
|
|
1933
|
+
* | 光り方 | 図種 | 知らせる条件 |
|
|
1934
|
+
* |---|---|---|
|
|
1935
|
+
* | 書いた箱 / 矢印が光る | `c4` `flow` `record` `swimlane` `topology` | 図がその相手を持たない時 |
|
|
1936
|
+
* | 書いた名前に合う言づてまで板が進む | `sequence` | 知らせない (下記) |
|
|
1937
|
+
* | 図全体の 1 箱が光る | 残る 15 図種 | 書かなかった箱がある時と、矢印を書いた時 |
|
|
1938
|
+
*
|
|
1939
|
+
* **板の 2 図種では知らせない**。 板は箱も矢印も持たないが、書いた名前は捨てられておらず
|
|
1940
|
+
* 板が何通目まで描くかを決めている (`段の番号`、 実測 = 3 通の図で `focus: ["あ -> い"]` が
|
|
1941
|
+
* 0 通目、 `["う -> あ"]` が 2 通目)。 図に相手が居ないことだけを見ると、効いている指定に
|
|
1942
|
+
* 「選べません」 と知らせることになる。
|
|
1943
|
+
*
|
|
1944
|
+
* **図全体が光る図種でも、全ての箱を書いた段では知らせない**。 光る結果 (全ての箱) と
|
|
1945
|
+
* 書いたこと (全ての箱) が一致するため、伝えることが無い。 知らせるのは **書かなかった箱も
|
|
1946
|
+
* 光る時** = 4 本の棒のうち 1 本だけを書いても 4 本とも光る形で、ここだけが書いたとおりに
|
|
1947
|
+
* ならない。
|
|
1948
|
+
*
|
|
1949
|
+
* @param diagram 組み上がった図。 渡さない時は綴り違いだけを見る
|
|
1113
1950
|
*/
|
|
1114
1951
|
function reportMissingFocusTargets(
|
|
1115
1952
|
doc: DslDocument,
|
|
1116
1953
|
onNotice?: (notice: CompileNotice) => void,
|
|
1954
|
+
diagram?: CdlDiagram,
|
|
1117
1955
|
): void {
|
|
1118
1956
|
if (!onNotice || !doc.animate) return;
|
|
1119
1957
|
const names = new Set(doc.actors.map((a) => a.name));
|
|
@@ -1156,30 +1994,152 @@ function reportMissingFocusTargets(
|
|
|
1156
1994
|
const 部品の名前 = doc.actors.filter((a) => a.partId !== undefined).map((a) => a.name);
|
|
1157
1995
|
const 部品の中を指す = (name: string): boolean => 部品の名前.some((p) => name.startsWith(`${p}__`));
|
|
1158
1996
|
|
|
1997
|
+
// 部品そのものの名前は、取り込む側が図の相手を決める (`部品の名前で光らせる`)。
|
|
1998
|
+
// ここで図を見ると、取り込む前の図にしか当たらず「選べません」 の誤報になる
|
|
1999
|
+
const 部品そのもの = new Set(部品の名前);
|
|
2000
|
+
|
|
2001
|
+
// 板の 2 図種は書いた名前を板の番号に使う (doc comment の表)。 図と突き合わせない
|
|
2002
|
+
const 板になる = doc.type === "sequence";
|
|
2003
|
+
const 見比べる図 = 板になる ? undefined : diagram;
|
|
2004
|
+
|
|
2005
|
+
// 綴り違いの補足は **1 度だけ作る**。 知らせごとに全ての名前を並べ直すと、名前も注目先も
|
|
2006
|
+
// 上限 (各 1,000) まで書いた図で知らせが数百 MB になる (矢印の端の知らせが #1209 で踏んだ形)
|
|
2007
|
+
const 名前の案内 = `actors: に書かれている名前 = ${並べて切る([...names])}`;
|
|
2008
|
+
|
|
1159
2009
|
for (const phase of doc.animate.phases) {
|
|
2010
|
+
/** 記法には書いてあるのに、書いたとおりに光らない指定 (#2398) */
|
|
2011
|
+
const 選べない: string[] = [];
|
|
2012
|
+
/** その段が名指しした箱。 図全体が光る図種で「書かなかった箱があるか」 を見る */
|
|
2013
|
+
const 書いた箱 = new Set<string>();
|
|
2014
|
+
/** 選べない指定に矢印が混じったか。 図全体が光る図種は矢印を 1 本も描かない */
|
|
2015
|
+
let 矢印も書いた = false;
|
|
2016
|
+
/** 書いた箱のうち 1 つでも図に居たか。 居るなら図全体が光る形ではない */
|
|
2017
|
+
let 箱が図に居た = false;
|
|
1160
2018
|
for (const raw of phase.highlight ?? []) {
|
|
1161
2019
|
const entry = parseFocusEntry(raw, names);
|
|
1162
2020
|
const found =
|
|
1163
2021
|
entry.kind === "edge"
|
|
1164
2022
|
? (steps.get(揃える(entry.from))?.has(揃える(entry.to)) ?? false)
|
|
1165
2023
|
: accepted.has(entry.name) || 部品の中を指す(entry.name);
|
|
1166
|
-
if (found)
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
2024
|
+
if (!found) {
|
|
2025
|
+
onNotice({
|
|
2026
|
+
kind: "focus-target-missing",
|
|
2027
|
+
actor: raw,
|
|
2028
|
+
line: phase.pos.line,
|
|
2029
|
+
message:
|
|
2030
|
+
entry.kind === "edge"
|
|
2031
|
+
? `光らせる矢印が流れにありません: "${truncateForMessage(raw)}"`
|
|
2032
|
+
: `光らせる相手が見つかりません: "${truncateForMessage(raw)}"`,
|
|
2033
|
+
hint: entry.kind === "edge" ? "flow: に書いた矢印と同じ向きで書く" : 名前の案内,
|
|
2034
|
+
// 縦列の id は受理しないので、 その旨は hint に出さない (光らせられないため)
|
|
2035
|
+
});
|
|
2036
|
+
continue;
|
|
2037
|
+
}
|
|
2038
|
+
if (見比べる図 === undefined) continue;
|
|
2039
|
+
if (entry.kind === "node") 書いた箱.add(揃える(entry.name));
|
|
2040
|
+
if (entry.kind === "node" && (部品そのもの.has(entry.name) || 部品の中を指す(entry.name)))
|
|
2041
|
+
continue;
|
|
2042
|
+
const 相手 =
|
|
2043
|
+
entry.kind === "edge"
|
|
2044
|
+
? 図の矢印を探す(見比べる図, doc, entry.from, entry.to)
|
|
2045
|
+
: 図の箱を探す(見比べる図, entry.name);
|
|
2046
|
+
if (相手 === undefined) {
|
|
2047
|
+
選べない.push(raw);
|
|
2048
|
+
if (entry.kind === "edge") 矢印も書いた = true;
|
|
2049
|
+
} else if (entry.kind === "node") 箱が図に居た = true;
|
|
1181
2050
|
}
|
|
2051
|
+
if (選べない.length === 0) continue;
|
|
2052
|
+
/*
|
|
2053
|
+
* 全ての箱を書いた段は、光る結果 (図全体) と書いたことが一致する (doc comment の表)。
|
|
2054
|
+
*
|
|
2055
|
+
* **1 つでも図に居た段はここへ入れない**。 箱を 1 つずつ選べる図種で 1 箱だけが図から
|
|
2056
|
+
* 落ちた時、全ての名前を書いていると「一致している」 と読めてしまう。 図全体が光る図種は
|
|
2057
|
+
* どの名前も図に当たらないので、この条件で落ちることはない
|
|
2058
|
+
*/
|
|
2059
|
+
if (!矢印も書いた && !箱が図に居た && [...names].every((n) => 書いた箱.has(n))) continue;
|
|
2060
|
+
/*
|
|
2061
|
+
* **1 行に 1 件だけ出す** (#2398)。 同じ段に書いた名前はどれも同じ理由で選べないので、
|
|
2062
|
+
* 1 つずつ知らせると同じ文が 1 行に並ぶ。 書いた名前は文に並べる。
|
|
2063
|
+
*/
|
|
2064
|
+
const 書かなかった箱 = [...names].filter((n) => !書いた箱.has(n));
|
|
2065
|
+
onNotice({
|
|
2066
|
+
kind: "focus-target-not-honored",
|
|
2067
|
+
actor: 選べない[0]!,
|
|
2068
|
+
line: phase.pos.line,
|
|
2069
|
+
message:
|
|
2070
|
+
`type: ${doc.type} は箱を 1 つずつ選べず図全体が光るため、` +
|
|
2071
|
+
` ${並べて切る(選べない, (x) => `"${x}"`)} を注目先に書いても` +
|
|
2072
|
+
(書かなかった箱.length > 0
|
|
2073
|
+
? ` 書かなかった箱 (${並べて切る(書かなかった箱)}) も光ります`
|
|
2074
|
+
: " 図全体が光ります"),
|
|
2075
|
+
hint: "名前の書き方の問題ではありません。 全ての箱を書くか、箱を 1 つずつ選べる図種 (flow / record など) に変えてください",
|
|
2076
|
+
});
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
/**
|
|
2081
|
+
* 知らせの本文に名前を並べる。 件数も 1 件あたりの長さも切る (#2398)。
|
|
2082
|
+
*
|
|
2083
|
+
* **件数だけを絞っても足りない**。 名前は外から来る文字列なので、1 つが 2 万字なら
|
|
2084
|
+
* 知らせも 2 万字になる (矢印の端の知らせが #1209 で踏んだ形)。 箱の上限は 1,000 件で、
|
|
2085
|
+
* 全て並べると 1 件の知らせが数百 KB になる。
|
|
2086
|
+
*/
|
|
2087
|
+
function 並べて切る(一覧: readonly string[], 包む = (s: string): string => s): string {
|
|
2088
|
+
const 見せる数 = 8;
|
|
2089
|
+
const 並び = 一覧.slice(0, 見せる数).map((x) => 包む(truncateForMessage(x)));
|
|
2090
|
+
const 残り = 一覧.length - 見せる数;
|
|
2091
|
+
return 残り > 0 ? `${並び.join(" / ")} ほか ${残り} 件` : 並び.join(" / ");
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
/**
|
|
2095
|
+
* 図の中から、書いた名前の箱を探す (#2336 / #2398)。
|
|
2096
|
+
*
|
|
2097
|
+
* 名前から作った識別子と、名前そのものと、描かれる題の 3 通りで探す。 記法は名前で書き、
|
|
2098
|
+
* 図の識別子はそこから作られるが、見本を重ねた図など識別子が名前と揃わない形もある。
|
|
2099
|
+
*
|
|
2100
|
+
* **出来事の相手と注目先が同じ探し方を使う**。 片方だけ厳しくすると、正しく書いた名前が
|
|
2101
|
+
* 一方では光り、もう一方では「見つからない」 と知らされる。
|
|
2102
|
+
*/
|
|
2103
|
+
function 図の箱を探す(diagram: CdlDiagram, 名: string): CdlNode | undefined {
|
|
2104
|
+
const slug = slugify(名);
|
|
2105
|
+
return diagram.nodes.find((n) => n.id === slug || n.id === 名 || n.title === 名);
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
/**
|
|
2109
|
+
* 図の中から、書いた両端の矢印を探す (#2336 / #2398)。
|
|
2110
|
+
*
|
|
2111
|
+
* 板になる図種 (`sequence`) は識別子に行の番号が入るので、書いた組が
|
|
2112
|
+
* 流れの何番目かを先に引いてから照合する。
|
|
2113
|
+
*/
|
|
2114
|
+
function 図の矢印を探す(
|
|
2115
|
+
diagram: CdlDiagram,
|
|
2116
|
+
doc: DslDocument,
|
|
2117
|
+
fromName: string,
|
|
2118
|
+
toName: string,
|
|
2119
|
+
used?: ReadonlySet<string>,
|
|
2120
|
+
): CdlEdge | undefined {
|
|
2121
|
+
const from = slugify(fromName);
|
|
2122
|
+
const to = slugify(toName);
|
|
2123
|
+
// 同じ両端を持つ行は出現順に別の線へ写すため、書き写し済みの線を候補から外す。
|
|
2124
|
+
const 未使用 = (edge: CdlEdge): boolean => !used?.has(edge.id);
|
|
2125
|
+
if (doc.type === "sequence") {
|
|
2126
|
+
const stepIdx = doc.flow.findIndex(
|
|
2127
|
+
(step) => slugify(step.from) === from && slugify(step.to) === to,
|
|
2128
|
+
);
|
|
2129
|
+
if (stepIdx < 0) return undefined;
|
|
2130
|
+
return diagram.edges.find(
|
|
2131
|
+
(x) =>
|
|
2132
|
+
未使用(x) &&
|
|
2133
|
+
(x.from === `s${stepIdx}-${from}` || x.from === from) &&
|
|
2134
|
+
(x.to === `s${stepIdx}-${to}` || x.from === x.to),
|
|
2135
|
+
);
|
|
2136
|
+
}
|
|
2137
|
+
// 時間軸の進行線は番号の箱を結ぶが、id は札の両端を残す。
|
|
2138
|
+
// focus と出来事は書き手が札の名前で指定するため、その id の末尾で照合する。
|
|
2139
|
+
if (doc.type === "swimlane" && doc.shape === "timeline") {
|
|
2140
|
+
return diagram.edges.find((x) => 未使用(x) && x.id.endsWith(`-${from}-${to}`));
|
|
1182
2141
|
}
|
|
2142
|
+
return diagram.edges.find((x) => 未使用(x) && x.from === from && x.to === to);
|
|
1183
2143
|
}
|
|
1184
2144
|
|
|
1185
2145
|
/**
|
|
@@ -1217,16 +2177,9 @@ function applyEdgeInlineOptions(
|
|
|
1217
2177
|
return;
|
|
1218
2178
|
}
|
|
1219
2179
|
const used = new Set<string>();
|
|
1220
|
-
// edge.from / edge.to は plain slug (slugify(actor 名))。
|
|
1221
|
-
//
|
|
1222
|
-
// 順序図系 (`sequence` / `solidity`) はここに来ない = #1466 で板になり矢印を作らない。
|
|
1223
2180
|
doc.flow.forEach((s) => {
|
|
1224
|
-
|
|
1225
|
-
const
|
|
1226
|
-
const target = diagram.edges.find((e) => {
|
|
1227
|
-
if (used.has(e.id)) return false;
|
|
1228
|
-
return e.from === fromId && e.to === toId;
|
|
1229
|
-
});
|
|
2181
|
+
// focus・出来事と同じ照合を使い、図種が線の端を置き換えても書いた行との対応を保つ。
|
|
2182
|
+
const target = 図の矢印を探す(diagram, doc, s.from, s.to, used);
|
|
1230
2183
|
if (!target) return;
|
|
1231
2184
|
used.add(target.id);
|
|
1232
2185
|
sourceLines?.set(target.id, s.pos.line);
|
|
@@ -1249,55 +2202,99 @@ function 出来事の相手を解く(
|
|
|
1249
2202
|
): NonNullable<CdlDiagram["eventBindings"]>[number]["target"] | undefined {
|
|
1250
2203
|
if (e.target.kind === "diagram") return { kind: "diagram" };
|
|
1251
2204
|
if (e.target.kind === "edge") {
|
|
1252
|
-
const
|
|
1253
|
-
const to = slugify(e.target.to);
|
|
1254
|
-
let 矢印: CdlEdge | undefined;
|
|
1255
|
-
if (doc.type === "sequence" || doc.type === "solidity") {
|
|
1256
|
-
const stepIdx = doc.flow.findIndex(
|
|
1257
|
-
(step) => slugify(step.from) === from && slugify(step.to) === to,
|
|
1258
|
-
);
|
|
1259
|
-
if (stepIdx >= 0) {
|
|
1260
|
-
矢印 = diagram.edges.find(
|
|
1261
|
-
(x) =>
|
|
1262
|
-
(x.from === `s${stepIdx}-${from}` || x.from === from) &&
|
|
1263
|
-
(x.to === `s${stepIdx}-${to}` || x.from === x.to),
|
|
1264
|
-
);
|
|
1265
|
-
}
|
|
1266
|
-
} else {
|
|
1267
|
-
矢印 = diagram.edges.find((x) => x.from === from && x.to === to);
|
|
1268
|
-
}
|
|
2205
|
+
const 矢印 = 図の矢印を探す(diagram, doc, e.target.from, e.target.to);
|
|
1269
2206
|
return 矢印 ? { kind: "edge", id: 矢印.id } : undefined;
|
|
1270
2207
|
}
|
|
1271
2208
|
const 名 = e.target.name;
|
|
1272
|
-
const slug = slugify(名);
|
|
1273
2209
|
if (e.target.kind === "node") {
|
|
1274
|
-
const 箱 = diagram
|
|
2210
|
+
const 箱 = 図の箱を探す(diagram, 名);
|
|
1275
2211
|
return 箱 ? { kind: "node", id: 箱.id } : undefined;
|
|
1276
2212
|
}
|
|
2213
|
+
const slug = slugify(名);
|
|
1277
2214
|
const 列 = (diagram.lanes ?? []).find((l) => l.id === slug || l.id === 名 || l.label === 名);
|
|
1278
2215
|
return 列 ? { kind: "lane", id: 列.id } : undefined;
|
|
1279
2216
|
}
|
|
1280
2217
|
|
|
2218
|
+
/**
|
|
2219
|
+
* 出来事が指す名前を、書いた人が本文に書いているか (#2336)。
|
|
2220
|
+
*
|
|
2221
|
+
* 相手が解けなかった原因を 2 つに分けるために使う。 **本文に在るなら書き直しても直らない** =
|
|
2222
|
+
* その図種が登場人物を箱にしない (値を並べる図種は図全体で 1 つの箱、 順序図は
|
|
2223
|
+
* 1 枚の板)。 本文に無いなら綴り違いで、書き直せば直る。
|
|
2224
|
+
*
|
|
2225
|
+
* 分けないと、直る件と直らない件に同じ「名前で書く」 という案内が付く。 案内に従っても
|
|
2226
|
+
* 直らない側では、書いた人は綴りを疑って何度も書き直すことになる (実測 = 24 図種のうち
|
|
2227
|
+
* 箱を指せるのは 7 図種)。
|
|
2228
|
+
*
|
|
2229
|
+
* 照合は図の識別子と同じ作り方に揃える = 箱は名前と題の両方で探し (`出来事の相手を解く` と
|
|
2230
|
+
* 同じ 2 経路)、矢印は両端の名前から作った識別子で突き合わせる。 ここだけ厳しくすると、
|
|
2231
|
+
* 正しく書いた名前を綴り違いとして知らせてしまう。
|
|
2232
|
+
*/
|
|
2233
|
+
function 出来事の相手が本文に在る(doc: DslDocument, e: DslEventBinding): boolean {
|
|
2234
|
+
if (e.target.kind === "diagram") return true;
|
|
2235
|
+
if (e.target.kind === "edge") {
|
|
2236
|
+
const from = slugify(e.target.from);
|
|
2237
|
+
const to = slugify(e.target.to);
|
|
2238
|
+
return doc.flow.some((s) => slugify(s.from) === from && slugify(s.to) === to);
|
|
2239
|
+
}
|
|
2240
|
+
const 名 = e.target.name;
|
|
2241
|
+
const slug = slugify(名);
|
|
2242
|
+
if (e.target.kind === "node") {
|
|
2243
|
+
return doc.actors.some(
|
|
2244
|
+
(a) => a.name === 名 || slugify(a.name) === slug || (a.title ?? a.name) === 名,
|
|
2245
|
+
);
|
|
2246
|
+
}
|
|
2247
|
+
return Object.entries(doc.lanes ?? {}).some(
|
|
2248
|
+
([id, 列]) => id === 名 || slugify(id) === slug || 列.label === 名,
|
|
2249
|
+
);
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
/** 出来事の相手の呼び名。 知らせの本文で「何を指したか」 を言うために使う */
|
|
2253
|
+
const 出来事の相手の呼び名: Record<DslEventBinding["target"]["kind"], string> = {
|
|
2254
|
+
node: "箱",
|
|
2255
|
+
lane: "縦列",
|
|
2256
|
+
edge: "矢印",
|
|
2257
|
+
diagram: "図そのもの",
|
|
2258
|
+
};
|
|
2259
|
+
|
|
1281
2260
|
/** 本文に書いた矢印の指定を、対応が取れた矢印へ書き写す。 対応の取り方は呼出側が決める。 */
|
|
1282
2261
|
function 矢印へ書き写す(target: CdlEdge, s: DslStep, doc: DslDocument): void {
|
|
1283
2262
|
// **書いた補足が勝つ** (#1275)。 ここで写さないと 2 つ落ちる。 静止した `type: flow` は
|
|
1284
|
-
// 鎖を作る時に説明文しか渡さないため補足が消え、`
|
|
2263
|
+
// 鎖を作る時に説明文しか渡さないため補足が消え、`record` は見本が多重度から作った補足が
|
|
1285
2264
|
// 残って書いた値が無視される (どちらも実測)
|
|
1286
2265
|
//
|
|
1287
|
-
//
|
|
1288
|
-
//
|
|
1289
|
-
|
|
1290
|
-
|
|
2266
|
+
// `relation` を書いた record の `sub` は行き先側の多重度。それ以外の `sub` は
|
|
2267
|
+
// 従来どおり名前の下の補足にする (#2783)。
|
|
2268
|
+
if (s.sub !== undefined) {
|
|
2269
|
+
if (doc.type === "record" && s.relation !== undefined) target.headLabel = s.sub;
|
|
2270
|
+
else target.sub = s.sub;
|
|
2271
|
+
}
|
|
1291
2272
|
if (s.guard !== undefined) {
|
|
1292
2273
|
target.guard = s.guard;
|
|
1293
2274
|
// FSM preset では sub が guard 同期、 author 明示 guard を sub に反映 (sub 既存なら上書きしない)
|
|
1294
|
-
|
|
2275
|
+
// 書いた条件は名前の下の行にも出す (`sub` が既に在れば上書きしない)
|
|
2276
|
+
if (doc.type === "record" && target.sub === undefined) target.sub = s.guard;
|
|
1295
2277
|
}
|
|
1296
|
-
//
|
|
2278
|
+
// 色味と線の種類も、図種ごとの組み立てではなくここで写す (#2394)。
|
|
2279
|
+
//
|
|
2280
|
+
// **渡す側を図種ごとに並べる形にしない**。 この 2 欄は図種ごとの組み立てが個別に渡していて、
|
|
2281
|
+
// 渡し忘れた図種でそのまま落ちていた (実測 = 線の種類は `record` / 鎖でつないだ `flow`、
|
|
2282
|
+
// 色味は鎖でつないだ `flow` で黙って消える)。 図種を足した日に同じ落とし方が再発するため、
|
|
2283
|
+
// 矢印を描く図種が必ず通るここへ移す。 図種ごとの組み立てに置いていた同じ受け渡しは外した =
|
|
2284
|
+
// 2 か所に置くと、片方だけ直した日に食い違う。 板になる図種 (`sequence`) は
|
|
2285
|
+
// 矢印を作らずここを通らないので、そちらは組み立てが持ったまま
|
|
2286
|
+
if (s.tone !== undefined) target.tone = s.tone;
|
|
2287
|
+
if (s.style !== undefined) target.style = s.style;
|
|
2288
|
+
// 出どころの端に添える字 (#2394)。 engine は `tailLabel` として端に置く (cdl#825)。
|
|
2289
|
+
//
|
|
2290
|
+
if (s.tailSub !== undefined) target.tailLabel = s.tailSub;
|
|
2291
|
+
// 多重度が名前と端にどう出るかは組み立ての時に決まっている (`compile/er-relation.ts`、#2105)。
|
|
1297
2292
|
// ここで名前へ `(1:N)` を足すと、組み立てが名前の下の行に出した語と 2 度並ぶ
|
|
1298
2293
|
if (s.cardinality !== undefined) target.cardinality = s.cardinality;
|
|
1299
2294
|
// 矢印がどの辺から出るか (#1385)。 書かなければ描画側が自動で選ぶ
|
|
1300
2295
|
if (s.side !== undefined) target.side = s.side;
|
|
2296
|
+
if (s.fromSide !== undefined) target.fromSide = s.fromSide;
|
|
2297
|
+
if (s.toSide !== undefined) target.toSide = s.toSide;
|
|
1301
2298
|
// 矢印の先の形 (#1462)。 書かない矢印には値を入れない = 既存の図が変わらない。
|
|
1302
2299
|
//
|
|
1303
2300
|
// **矢印を作る 9 図種すべてがここを通る**。 図種ごとの組み立てにも同じ形を置いたが、
|
|
@@ -1313,6 +2310,13 @@ function 矢印へ書き写す(target: CdlEdge, s: DslStep, doc: DslDocument): v
|
|
|
1313
2310
|
if (s.labelPlate !== undefined) target.labelPlate = s.labelPlate;
|
|
1314
2311
|
if (s.labelOffsetX !== undefined) target.labelOffsetX = s.labelOffsetX;
|
|
1315
2312
|
if (s.labelOffsetY !== undefined) target.labelOffsetY = s.labelOffsetY;
|
|
2313
|
+
if (s.posX1 !== undefined) target.posX1 = s.posX1;
|
|
2314
|
+
if (s.posY1 !== undefined) target.posY1 = s.posY1;
|
|
2315
|
+
if (s.posX2 !== undefined) target.posX2 = s.posX2;
|
|
2316
|
+
if (s.posY2 !== undefined) target.posY2 = s.posY2;
|
|
2317
|
+
if (s.waypoints !== undefined) target.waypoints = s.waypoints;
|
|
2318
|
+
if (s.labelCollision !== undefined) target.labelCollision = s.labelCollision;
|
|
2319
|
+
if (s.labelSize !== undefined) target.labelSize = s.labelSize;
|
|
1316
2320
|
// 矢印の位置のずらし (#1971)。 矢印は自分の位置を持たず、通り道は両端の箱で決まるので、
|
|
1317
2321
|
// 動かせるのは名前だけ。 名前のずらしに足す = 両方書いた時は合わせた量だけ動く
|
|
1318
2322
|
if (s.layoutPos !== undefined) {
|
|
@@ -1429,8 +2433,19 @@ function applyGroupFrames(
|
|
|
1429
2433
|
}
|
|
1430
2434
|
if (置く.length === 0) return;
|
|
1431
2435
|
|
|
1432
|
-
|
|
1433
|
-
|
|
2436
|
+
// **ここで作った枠を測る図から外す** (#2300)。 枠はまだ置いていない = `posX: 0, posY: 0` の
|
|
2437
|
+
// 仮置きなので、 渡すと組の名札が全部同じ `x = 16` に積み上がり、 記法の engine が
|
|
2438
|
+
// 実物と合わない重なりを知らせる (組を 2 つ書くと `1994px²`、 組を N 個書くと最大 N(N-1)/2 件)。
|
|
2439
|
+
// 出来上がった図では 1 度も重ならない (名札の箱は `16.0..87.2` と `564.0..682.4`)。
|
|
2440
|
+
//
|
|
2441
|
+
// 外しても測る値は変わらない。 枠は `posX` と `posY` を持つ縦列として並べ直しの対象から
|
|
2442
|
+
// 外れており (`role: "overlay"` で間隔の調整からも外れる)、 結果からは元から捨てていた。
|
|
2443
|
+
// 渡す時点で外すだけで、 縦列と箱の位置は 1 単位も動かない。
|
|
2444
|
+
const 配置 = 測るために配置する({
|
|
2445
|
+
...diagram,
|
|
2446
|
+
lanes: diagram.lanes.filter((l) => !作った枠.has(l.id)),
|
|
2447
|
+
});
|
|
2448
|
+
const 並びの縦列 = 配置.lanes;
|
|
1434
2449
|
for (const { 枠, 名前, line, 縦列 } of 置く) {
|
|
1435
2450
|
const 束 = 並びの縦列.filter((l) => 縦列.includes(l.id));
|
|
1436
2451
|
const 左 = Math.min(...束.map((l) => l.x ?? 0));
|
|
@@ -1466,9 +2481,9 @@ function applyGroupFrames(
|
|
|
1466
2481
|
}
|
|
1467
2482
|
|
|
1468
2483
|
/**
|
|
1469
|
-
*
|
|
2484
|
+
* ユーザージャーニーの欄を、 それを描けない図種で書いた時に伝える (#1251)。
|
|
1470
2485
|
*
|
|
1471
|
-
* `touchpoint` と `opportunity` は `type: journey` の段だけが持つ。 他の図種では相手が無く、
|
|
2486
|
+
* `touchpoint` と `opportunity` と `opportunityPosition` は `type: journey` の段だけが持つ。 他の図種では相手が無く、
|
|
1472
2487
|
* 黙って捨てると「書いたのに出ない」 が手掛かりなしで起きる。
|
|
1473
2488
|
*
|
|
1474
2489
|
* `type: mind` では伝えない = `compileMind` が描けない欄をまとめて 1 件で伝えており、
|
|
@@ -1488,18 +2503,27 @@ function reportChartFieldsNotHonored(
|
|
|
1488
2503
|
: [
|
|
1489
2504
|
...(a.touchpoint !== undefined ? ["touchpoint"] : []),
|
|
1490
2505
|
...(a.opportunity !== undefined ? ["opportunity"] : []),
|
|
2506
|
+
...(a.opportunityPosition !== undefined ? ["opportunityPosition"] : []),
|
|
1491
2507
|
];
|
|
1492
2508
|
const 工程 =
|
|
1493
2509
|
doc.type === "gantt"
|
|
1494
2510
|
? []
|
|
1495
|
-
: [
|
|
2511
|
+
: [
|
|
2512
|
+
...(a.owner !== undefined ? ["owner"] : []),
|
|
2513
|
+
...(a.end !== undefined ? ["end"] : []),
|
|
2514
|
+
...(a.startLabel !== undefined ? ["startLabel"] : []),
|
|
2515
|
+
...(a.milestone !== undefined ? ["milestone"] : []),
|
|
2516
|
+
...(a.emphasis !== undefined && doc.type !== "record" && !値として読む図が主役を読む(doc.type, doc.shape)
|
|
2517
|
+
? ["emphasis"]
|
|
2518
|
+
: []),
|
|
2519
|
+
];
|
|
1496
2520
|
if (道筋.length > 0) {
|
|
1497
2521
|
onNotice({
|
|
1498
2522
|
kind: "chart-value-unreadable",
|
|
1499
2523
|
actor: a.name,
|
|
1500
2524
|
line: a.pos?.line ?? 0,
|
|
1501
|
-
message: `"${truncateForMessage(a.name)}" に書いた ${道筋.join(" / ")} は効きません (type: ${doc.type}
|
|
1502
|
-
hint: "
|
|
2525
|
+
message: `"${truncateForMessage(a.name)}" に書いた ${道筋.join(" / ")} は効きません (type: ${doc.type} にはユーザージャーニーの欄がありません)`,
|
|
2526
|
+
hint: "ユーザージャーニーを描くなら type: journey を使ってください",
|
|
1503
2527
|
});
|
|
1504
2528
|
}
|
|
1505
2529
|
if (工程.length > 0) {
|
|
@@ -1533,93 +2557,6 @@ function reportAxesNotHonored(doc: DslDocument, onNotice?: (n: CompileNotice) =>
|
|
|
1533
2557
|
});
|
|
1534
2558
|
}
|
|
1535
2559
|
|
|
1536
|
-
/**
|
|
1537
|
-
* 行を組み立て器が加工する図の種類 (#1466)。
|
|
1538
|
-
*
|
|
1539
|
-
* ここに載る種類では、記法に書いた行をそのまま箱へ載せ直さない = 組み立て器が行頭の印に
|
|
1540
|
-
* 合わせて字を落としているため。
|
|
1541
|
-
*/
|
|
1542
|
-
function 行を組み立て器が持つ(type: DslDocument["type"]): boolean {
|
|
1543
|
-
return type === "class";
|
|
1544
|
-
}
|
|
1545
|
-
|
|
1546
|
-
/**
|
|
1547
|
-
* 行と印を組む (#1466)。 群の分け方は図の種類が決める。
|
|
1548
|
-
*
|
|
1549
|
-
* ER は **鍵の群を上にまとめる** (組み立て器 `er()` と同じ形)。
|
|
1550
|
-
* 状態遷移は群を分けないので、書いた並びのまま。
|
|
1551
|
-
*
|
|
1552
|
-
* **空の行では開けない** (cdl `#606`)。 群の区切りは行頭の印が持っている = ER は鍵の名前に
|
|
1553
|
-
* 下線が付く。 空の行はその 2 つ目の手掛かりで、代わりに行の間隔を不揃いにしていた
|
|
1554
|
-
* (鍵と値を持つ箱だけ境目が広がる)。 組み立て器が cdl 0.23.0 で空の行をやめたので、
|
|
1555
|
-
* こちらも同時にやめる = 片方だけ残ると記法と図の照合が落ちる。
|
|
1556
|
-
*/
|
|
1557
|
-
function 行と印を組む(
|
|
1558
|
-
type: DslDocument["type"],
|
|
1559
|
-
rows: readonly string[],
|
|
1560
|
-
marks: readonly string[],
|
|
1561
|
-
): { rows: string[]; rowMarks: (RowMark | null)[] } | null {
|
|
1562
|
-
const 印 = 行頭の印にする(type, marks);
|
|
1563
|
-
if (印 === null) return null;
|
|
1564
|
-
if (type !== "er") {
|
|
1565
|
-
return { rows: [...rows], rowMarks: rows.map((_, i) => 印[i] ?? null) };
|
|
1566
|
-
}
|
|
1567
|
-
const 鍵: number[] = [];
|
|
1568
|
-
const 値: number[] = [];
|
|
1569
|
-
rows.forEach((_, i) => ((印[i]?.underline === true ? 鍵 : 値).push(i)));
|
|
1570
|
-
const 並び = [...鍵, ...値];
|
|
1571
|
-
return {
|
|
1572
|
-
rows: 並び.map((i) => rows[i] ?? ""),
|
|
1573
|
-
rowMarks: 並び.map((i) => 印[i] ?? null),
|
|
1574
|
-
};
|
|
1575
|
-
}
|
|
1576
|
-
|
|
1577
|
-
/**
|
|
1578
|
-
* 書いた語を行頭の印に読み替える (#1466)。
|
|
1579
|
-
*
|
|
1580
|
-
* **軸の意味は図の種類が決める**。 印そのものは 形 (四角 / 山形) × 塗り (塗る / 中空) の
|
|
1581
|
-
* 2 軸で共通だが、その軸が何を指すかは種類ごとに違う。
|
|
1582
|
-
*
|
|
1583
|
-
* | 種類 | 山形 | 塗り |
|
|
1584
|
-
* |---|---|---|
|
|
1585
|
-
* | `er` | 外を指す列 (`fk`) | 空にできない (`opt` を書かない) |
|
|
1586
|
-
* | `state` | 出入りの瞬間 (`entry` / `exit`) | 続く・入る側 (`entry` / `do`) |
|
|
1587
|
-
*
|
|
1588
|
-
* ER の `pk` は印の 2 軸とは別の段 (名前の下線) に載るので、`fk` と重ねて書ける。
|
|
1589
|
-
*
|
|
1590
|
-
* 語を 1 つも知らない図の種類では `null` を返す = 印を付けない。
|
|
1591
|
-
*/
|
|
1592
|
-
function 行頭の印にする(
|
|
1593
|
-
type: DslDocument["type"],
|
|
1594
|
-
marks: readonly string[],
|
|
1595
|
-
): (RowMark | null)[] | null {
|
|
1596
|
-
if (type === "er") {
|
|
1597
|
-
return marks.map((m) => {
|
|
1598
|
-
const 語 = m.trim().split(/\s+/).filter(Boolean);
|
|
1599
|
-
/*
|
|
1600
|
-
* **語を書かない行は「ただの値」**。 印を付けない行にはしない (#1466)。
|
|
1601
|
-
*
|
|
1602
|
-
* ER の印は 2 軸とも既定を持つ = 四角 (外を指さない) で塗る (空にできない)。
|
|
1603
|
-
* 印なしにすると、書かなかった列だけ行頭が空いて群の間と見分けが付かなくなる。
|
|
1604
|
-
*/
|
|
1605
|
-
return {
|
|
1606
|
-
shape: 語.includes("fk") ? ("chevron" as const) : ("square" as const),
|
|
1607
|
-
filled: !語.includes("opt"),
|
|
1608
|
-
...(語.includes("pk") ? { underline: true } : {}),
|
|
1609
|
-
};
|
|
1610
|
-
});
|
|
1611
|
-
}
|
|
1612
|
-
if (type === "state") {
|
|
1613
|
-
return marks.map((m) => {
|
|
1614
|
-
const 語 = m.trim();
|
|
1615
|
-
if (語 === "") return null;
|
|
1616
|
-
const 表 = FSM_ACTION_MARK as Record<string, RowMark>;
|
|
1617
|
-
return 表[語] ?? null;
|
|
1618
|
-
});
|
|
1619
|
-
}
|
|
1620
|
-
return null;
|
|
1621
|
-
}
|
|
1622
|
-
|
|
1623
2560
|
/**
|
|
1624
2561
|
* `lanes:` で作った縦列と、見本 (parts) が作った同一idの縦列を 1 つに重ねる (#1241)。
|
|
1625
2562
|
*
|
|
@@ -1705,7 +2642,7 @@ function applyV05Extensions(
|
|
|
1705
2642
|
/** `lanes:` が新しく作った縦列。 箱が入ったかは見本を重ねた後でないと分からない (#1241) */
|
|
1706
2643
|
追加した縦列out?: DslLane[],
|
|
1707
2644
|
): CdlDiagram {
|
|
1708
|
-
// actor の主要 node を preset 種別で回収する。 sequence
|
|
2645
|
+
// actor の主要 node を preset 種別で回収する。 sequence は header/footer を対で生成する
|
|
1709
2646
|
// preset で主要 node は header、 それ以外の preset は actor 名 slug がそのまま node id になる。
|
|
1710
2647
|
//
|
|
1711
2648
|
// seq-like の非 animate 経路は実 node id を CDL preset 側 slugify (`_` → `-` 置換 + 全角正規化) で
|
|
@@ -1713,7 +2650,7 @@ function applyV05Extensions(
|
|
|
1713
2650
|
// primaryNodeId `a_b-header` が実 node `a-b-header` と食い違い、 inline option (subtitle / eyebrow /
|
|
1714
2651
|
// value / rows) が drop する (#881、 #873 / #877 と同根の dragon⇔CDL slug 不一致)。
|
|
1715
2652
|
//
|
|
1716
|
-
//
|
|
2653
|
+
// **順序図はここを通らない** (#1466)。 `sequence` は 1 枚の板になり、面ごとの
|
|
1717
2654
|
// 箱も縦列も作らなくなった = 書いた欄を写す相手が無い。 面に書いた内容が効かないことは
|
|
1718
2655
|
// `reportActorKindNotHonored` が伝える。
|
|
1719
2656
|
// actor inline option → node merge
|
|
@@ -1723,30 +2660,34 @@ function applyV05Extensions(
|
|
|
1723
2660
|
const primaryNodes = diagram.nodes.filter((n) => n.id === dragonSlug);
|
|
1724
2661
|
for (const node of primaryNodes) {
|
|
1725
2662
|
// 識別に使う名前と、箱に出す題を分ける (#1381)。 preset が actor 名で
|
|
1726
|
-
// node を作る経路 (sequence /
|
|
2663
|
+
// node を作る経路 (sequence / swimlane / c4) もここで書き換える。
|
|
1727
2664
|
if (a.title !== undefined) node.title = a.title;
|
|
1728
2665
|
// `type: c4` では説明の先頭に段の目印 (`L1` / `L2` / `L3`) を書く。 目印は組み立てに
|
|
1729
2666
|
// 段を伝えるためのもので読む人に意味を持たず、 段の名前は枠のラベルが既に出している。
|
|
1730
2667
|
// ここで落とさないと、 組み立てが読み取った目印がそのまま箱の説明として出る (#1098)
|
|
1731
2668
|
const 説明 = doc.type === "c4" ? 段を読み取る(a.subtitle).説明 : a.subtitle;
|
|
1732
2669
|
if (説明 !== undefined) node.subtitle = 説明;
|
|
2670
|
+
if (a.kindForm !== undefined) node.kindForm = a.kindForm;
|
|
2671
|
+
if (a.icon !== undefined) node.icon = a.icon;
|
|
2672
|
+
if (a.emptyRowsNote !== undefined) node.emptyRowsNote = a.emptyRowsNote;
|
|
2673
|
+
if (a.emphasis !== undefined && doc.type === "record") node.emphasis = a.emphasis;
|
|
2674
|
+
if (a.titleAlign !== undefined) node.titleAlign = a.titleAlign;
|
|
2675
|
+
if (a.rowRules !== undefined) node.rowRules = a.rowRules;
|
|
2676
|
+
if (a.rowStripe !== undefined) node.rowStripe = a.rowStripe;
|
|
2677
|
+
if (a.rowLayout !== undefined) node.rowLayout = a.rowLayout;
|
|
2678
|
+
if (a.rowDividerForm !== undefined) node.rowDividerForm = a.rowDividerForm;
|
|
2679
|
+
if (a.rowWell !== undefined) node.rowWell = a.rowWell;
|
|
2680
|
+
if (a.rowMarkForm !== undefined) node.rowMarkForm = a.rowMarkForm;
|
|
2681
|
+
if (a.nestIn !== undefined) node.nestIn = slugify(a.nestIn);
|
|
2682
|
+
if (a.figureCard !== undefined) node.figureCard = a.figureCard;
|
|
1733
2683
|
if (a.eyebrow !== undefined) node.eyebrow = a.eyebrow;
|
|
1734
2684
|
if (a.value !== undefined) node.value = a.value;
|
|
1735
|
-
|
|
1736
|
-
* 行は **組み立て器が持つ図では上書きしない** (#1466)。
|
|
1737
|
-
*
|
|
1738
|
-
* クラス図は行頭の印を出すために、公開の記号 (`+` / `-`) と呼び出しの括弧を字から
|
|
1739
|
-
* 落とし、群の区切り (`───`) を空の行に置き換える。 書いた字をそのまま載せ直すと
|
|
1740
|
-
* その加工が消え、印と字が同じことを 2 度言う形に戻る (実測)。
|
|
1741
|
-
*/
|
|
1742
|
-
if (a.rows !== undefined && !行を組み立て器が持つ(doc.type)) node.rows = a.rows;
|
|
2685
|
+
if (a.rows !== undefined) node.rows = a.rows;
|
|
1743
2686
|
// 行頭の印 (#1466)。 書いた語を図の種類ごとの意味で読み、群の分け方も種類が決める
|
|
1744
|
-
if (a.marks !== undefined && a.rows !== undefined) {
|
|
1745
|
-
const 組 =
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
node.rowMarks = 組.rowMarks;
|
|
1749
|
-
}
|
|
2687
|
+
if (a.marks !== undefined && a.rows !== undefined && 行頭の印を読むか(doc.type)) {
|
|
2688
|
+
const 組 = 鍵の行を上にまとめる(a.rows, 行頭の印にする(a.marks, a.rows.length));
|
|
2689
|
+
node.rows = 組.rows;
|
|
2690
|
+
node.rowMarks = 組.rowMarks;
|
|
1750
2691
|
}
|
|
1751
2692
|
/*
|
|
1752
2693
|
* **行を持たない状態でも欄を置く** (#1466)。
|
|
@@ -1755,7 +2696,7 @@ function applyV05Extensions(
|
|
|
1755
2696
|
* 欄ごと省くと、その箱だけ従来の意匠に落ちて呼び名が消える (組み立て API 側の
|
|
1756
2697
|
* `stateMachine` は同じ理由で空の欄を置いている)。
|
|
1757
2698
|
*/
|
|
1758
|
-
if (doc.type === "
|
|
2699
|
+
if (doc.type === "record" && node.kind === "storage" && node.rowMarks === undefined) {
|
|
1759
2700
|
node.rows = a.rows ?? [];
|
|
1760
2701
|
node.rowMarks = [];
|
|
1761
2702
|
}
|
|
@@ -1791,7 +2732,7 @@ function applyV05Extensions(
|
|
|
1791
2732
|
}
|
|
1792
2733
|
}
|
|
1793
2734
|
// v0.5+ animation phase 後段注入 (CAR-1657 fix、 元 dragon PR #413 report user)。
|
|
1794
|
-
// preset (
|
|
2735
|
+
// 単一箱の preset (chart / c4 / mind / gantt 等) が doc.animate を無視して build するケースを補償。
|
|
1795
2736
|
// 既に preset が phase を生成済 (sequence / flow / swimlane / er / state / topology 経由 = compileGenericWithAnimate) なら skip。
|
|
1796
2737
|
// doc に phase 指定があって diagram.phases が空なら、 preset 由来 lane/node/edge に対して generic phase を注入する。
|
|
1797
2738
|
if (doc.animate && doc.animate.phases.length > 0 && diagram.phases.length === 0) {
|
|
@@ -1804,13 +2745,14 @@ function applyV05Extensions(
|
|
|
1804
2745
|
// `lane-A`) がこの形になり、黙って捨てられていた (実測 = 幅 999 を持つ空の縦列が増え、
|
|
1805
2746
|
// 元の縦列は 360 のままだった)
|
|
1806
2747
|
const 追加した縦列: DslLane[] = 追加した縦列out ?? [];
|
|
1807
|
-
if (doc.lanes) {
|
|
2748
|
+
if (doc.lanes && !(doc.type === "swimlane" && (doc.shape === "stages" || doc.shape === "metro" || doc.shape === "timeline"))) {
|
|
1808
2749
|
for (const [id, laneOpt] of Object.entries(doc.lanes)) {
|
|
1809
2750
|
const lane = diagram.lanes.find((l) => l.id === id);
|
|
1810
2751
|
if (lane) {
|
|
1811
2752
|
if (laneOpt.x !== undefined) lane.x = laneOpt.x;
|
|
1812
2753
|
if (laneOpt.width !== undefined) lane.width = laneOpt.width;
|
|
1813
2754
|
if (laneOpt.label !== undefined) lane.label = laneOpt.label;
|
|
2755
|
+
if (laneOpt.subtitle !== undefined) lane.subtitle = laneOpt.subtitle;
|
|
1814
2756
|
if (laneOpt.contain !== undefined) lane.contain = laneOpt.contain;
|
|
1815
2757
|
if (laneOpt.lifeline !== undefined) lane.lifeline = laneOpt.lifeline;
|
|
1816
2758
|
} else {
|
|
@@ -1825,6 +2767,7 @@ function applyV05Extensions(
|
|
|
1825
2767
|
...(laneOpt.x !== undefined ? { x: laneOpt.x } : {}),
|
|
1826
2768
|
width: laneOpt.width ?? 320,
|
|
1827
2769
|
label: laneOpt.label,
|
|
2770
|
+
subtitle: laneOpt.subtitle,
|
|
1828
2771
|
contain: laneOpt.contain,
|
|
1829
2772
|
lifeline: laneOpt.lifeline,
|
|
1830
2773
|
});
|
|
@@ -1844,6 +2787,7 @@ function applyV05Extensions(
|
|
|
1844
2787
|
...(diagram.viewport ?? {}),
|
|
1845
2788
|
...(doc.viewport.width !== undefined ? { width: doc.viewport.width } : {}),
|
|
1846
2789
|
...(doc.viewport.height !== undefined ? { height: doc.viewport.height } : {}),
|
|
2790
|
+
...(doc.viewport.contentOffsetX !== undefined ? { contentOffsetX: doc.viewport.contentOffsetX } : {}),
|
|
1847
2791
|
...(doc.viewport.gap !== undefined ? { gap: doc.viewport.gap } : {}),
|
|
1848
2792
|
...(doc.viewport.laneGap !== undefined ? { laneGap: doc.viewport.laneGap } : {}),
|
|
1849
2793
|
...(doc.viewport.nodeGap !== undefined ? { nodeGap: doc.viewport.nodeGap } : {}),
|
|
@@ -1857,7 +2801,7 @@ function applyV05Extensions(
|
|
|
1857
2801
|
/**
|
|
1858
2802
|
* v0.5+ animation phase 後段 fallback 注入 (CAR-1657)。
|
|
1859
2803
|
*
|
|
1860
|
-
*
|
|
2804
|
+
* chart / c4 / mind / gantt 等の preset は独自 layout を持ち、compileGenericWithAnimate 経路に
|
|
1861
2805
|
* 乗らないため、 doc.animate.phases があっても diagram.phases が空になる。
|
|
1862
2806
|
* 本 helper が applyV05Extensions から呼ばれて post-hoc に phase を差込む、 lane/node/edge は
|
|
1863
2807
|
* 既存 preset 出力を保持したまま animation だけ追加する。
|
|
@@ -1938,7 +2882,13 @@ function injectPhasesFallback(diagram: CdlDiagram, doc: DslDocument): void {
|
|
|
1938
2882
|
// **`activate` と兼ねない**。 描画側は焦点と別集合で持つ (`cdl#512`) = 焦点が当たり
|
|
1939
2883
|
// 続ける図で毎段引き直しになるため。 書いた段だけが欄を持つ
|
|
1940
2884
|
const drawIds =
|
|
1941
|
-
p.draw !== undefined &&
|
|
2885
|
+
p.draw !== undefined &&
|
|
2886
|
+
描く語がその図を指すか(
|
|
2887
|
+
p.draw,
|
|
2888
|
+
doc.type,
|
|
2889
|
+
doc.shape === undefined ? undefined : (resolveShape(doc.shape) ?? undefined),
|
|
2890
|
+
) &&
|
|
2891
|
+
singleBoxNode !== undefined
|
|
1942
2892
|
? [singleBoxNode.id]
|
|
1943
2893
|
: [];
|
|
1944
2894
|
diagram.phases.push({
|
|
@@ -1959,10 +2909,45 @@ function injectPhasesFallback(diagram: CdlDiagram, doc: DslDocument): void {
|
|
|
1959
2909
|
}
|
|
1960
2910
|
|
|
1961
2911
|
/**
|
|
1962
|
-
*
|
|
2912
|
+
* 向きの行を外しても、同じ組み立ての経路を通るか (#2421)。
|
|
2913
|
+
*
|
|
2914
|
+
* **向きを書くこと自体が経路を切り替える**。 フローは鎖をやめて書いた端のとおりに繋ぎ
|
|
2915
|
+
* (`鎖にしない書き方`)、担当の図は静止図の組み立てから共通の組み立てへ回る
|
|
2916
|
+
* (`共通の組み立てへ回す` の図種ごとの理由)。
|
|
1963
2917
|
*
|
|
1964
|
-
*
|
|
1965
|
-
*
|
|
2918
|
+
* だから「既定と同じ値なら何もしない」 は成り立たない。 実測 = 動きを書かないフローに
|
|
2919
|
+
* `direction: 縦` を足すと、viewBox が 490×539 から 504×631 に変わり、矢印の線種も変わる。
|
|
2920
|
+
*
|
|
2921
|
+
* 同じ経路を通る時だけ、書いた向きは本当に何もしない。 その経路の中で向きを読むのは
|
|
2922
|
+
* `並べる向き` だけで、既定と同じ値ならそこも同じ値を返す。
|
|
2923
|
+
*
|
|
2924
|
+
* **判定は経路を決める関数から導く**。 条件を手で並べると、`鎖にしない書き方` に行が増えた日に
|
|
2925
|
+
* ここだけ古くなる。
|
|
2926
|
+
*/
|
|
2927
|
+
function 向きを外しても同じ経路か(doc: DslDocument): boolean {
|
|
2928
|
+
const 向きなし: DslDocument = { ...doc, direction: undefined };
|
|
2929
|
+
if (doc.type === "flow") return 鎖でつなぐ形か(向きなし) === 鎖でつなぐ形か(doc);
|
|
2930
|
+
if (doc.type === "swimlane") {
|
|
2931
|
+
return 共通の組み立てへ回す("swimlane", 向きなし, false) === 共通の組み立てへ回す("swimlane", doc, true);
|
|
2932
|
+
}
|
|
2933
|
+
/*
|
|
2934
|
+
* 分かれ道の図は向きで経路が変わらない (#2524)。 組み立ては 1 本で、向きは中で
|
|
2935
|
+
* 「縦列 1 本に積むか」 の 1 点だけを切り替える。 既定と同じ値を書いた図は同じ並びになる。
|
|
2936
|
+
*/
|
|
2937
|
+
if (doc.type === "flowchart") return true;
|
|
2938
|
+
return false;
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
/**
|
|
2942
|
+
* 書いた向きが図を変えない時に伝える (#1494 / #2421)。
|
|
2943
|
+
*
|
|
2944
|
+
* 変えない形は 3 つある。 向きを選べない図種に書いた形と、全ての箱が縦列を書いた形と、
|
|
2945
|
+
* 既定と同じ向きを書いて経路も変わらない形。
|
|
2946
|
+
* 前の 2 つは書いた向きが捨てられる形なので `direction-not-honored`、
|
|
2947
|
+
* 3 つ目は書いても書かなくても同じ図になる形なので `direction-same-as-default`。
|
|
2948
|
+
*
|
|
2949
|
+
* **読み手が次に取る手が違うので 1 つにまとめない**。 前者は書き方を変えれば効くが、
|
|
2950
|
+
* 後者は既に効いている値と同じで、行を外すか別の値を書くかの選択になる。
|
|
1966
2951
|
*
|
|
1967
2952
|
* 黙って捨てると「書いたのに変わらない」 が手掛かりなしで起きる。
|
|
1968
2953
|
*/
|
|
@@ -1970,6 +2955,36 @@ function reportDirectionNotHonored(doc: DslDocument, onNotice?: (n: CompileNotic
|
|
|
1970
2955
|
if (!onNotice) return;
|
|
1971
2956
|
if (doc.direction === undefined) return;
|
|
1972
2957
|
const 行 = doc.directionPos?.line ?? doc.pos?.line ?? 0;
|
|
2958
|
+
if (doc.type === "swimlane" && doc.shape === "timeline") {
|
|
2959
|
+
onNotice({
|
|
2960
|
+
kind: "direction-not-honored",
|
|
2961
|
+
actor: doc.title,
|
|
2962
|
+
line: 行,
|
|
2963
|
+
message: "書いた direction は効きません (shape: timeline は段を常に上から下へ並べます)",
|
|
2964
|
+
hint: "段の並びは actors に書いた順で決まります",
|
|
2965
|
+
});
|
|
2966
|
+
return;
|
|
2967
|
+
}
|
|
2968
|
+
if (doc.type === "swimlane" && doc.shape === "metro") {
|
|
2969
|
+
onNotice({
|
|
2970
|
+
kind: "direction-not-honored",
|
|
2971
|
+
actor: doc.title,
|
|
2972
|
+
line: 行,
|
|
2973
|
+
message: "書いた direction は効きません (shape: metro は駅を常に左から右へ並べます)",
|
|
2974
|
+
hint: "駅の並びは actors に書いた順で決まります",
|
|
2975
|
+
});
|
|
2976
|
+
return;
|
|
2977
|
+
}
|
|
2978
|
+
if (doc.type === "swimlane" && doc.shape === "stages") {
|
|
2979
|
+
onNotice({
|
|
2980
|
+
kind: "direction-not-honored",
|
|
2981
|
+
actor: doc.title,
|
|
2982
|
+
line: 行,
|
|
2983
|
+
message: "書いた direction は効きません (shape: stages は段階を常に左から右へ並べます)",
|
|
2984
|
+
hint: "段階の並びは actors に最初に現れた順で決まります",
|
|
2985
|
+
});
|
|
2986
|
+
return;
|
|
2987
|
+
}
|
|
1973
2988
|
if (!向きを選べる図種.has(doc.type)) {
|
|
1974
2989
|
onNotice({
|
|
1975
2990
|
kind: "direction-not-honored",
|
|
@@ -1980,7 +2995,7 @@ function reportDirectionNotHonored(doc: DslDocument, onNotice?: (n: CompileNotic
|
|
|
1980
2995
|
});
|
|
1981
2996
|
return;
|
|
1982
2997
|
}
|
|
1983
|
-
if (書いた縦列に置く(doc.type, doc)) {
|
|
2998
|
+
if (!縦列より向きが勝つ図種.has(doc.type) && 書いた縦列に置く(doc.type, doc)) {
|
|
1984
2999
|
onNotice({
|
|
1985
3000
|
kind: "direction-not-honored",
|
|
1986
3001
|
actor: doc.title,
|
|
@@ -1988,5 +3003,167 @@ function reportDirectionNotHonored(doc: DslDocument, onNotice?: (n: CompileNotic
|
|
|
1988
3003
|
message: "書いた direction は効きません (全ての箱が縦列を書いているので、そちらが優先されます)",
|
|
1989
3004
|
hint: "direction で並べたい時は箱の `lane` を外してください",
|
|
1990
3005
|
});
|
|
3006
|
+
return;
|
|
1991
3007
|
}
|
|
3008
|
+
const 既定 = 既定の向き(doc.type);
|
|
3009
|
+
if (doc.direction === 既定 && 向きを外しても同じ経路か(doc)) {
|
|
3010
|
+
onNotice({
|
|
3011
|
+
kind: "direction-same-as-default",
|
|
3012
|
+
actor: doc.title,
|
|
3013
|
+
line: 行,
|
|
3014
|
+
message: `書いた direction は図を変えません (${既定} は type: ${doc.type} の既定で、この図は向きの行を外しても同じ並びになります)`,
|
|
3015
|
+
hint: `並びを変えるなら ${既定 === "縦" ? "横" : "縦"} を書いてください。 今の並びのままにするなら direction の行は外せます`,
|
|
3016
|
+
});
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
|
|
3020
|
+
/**
|
|
3021
|
+
* 書いた並び順が効かない図種に書かれた時に伝える (#2655)。
|
|
3022
|
+
*
|
|
3023
|
+
* 向きの知らせと同じ形にする。 黙って捨てると「書いたのに並びが変わらない」 が
|
|
3024
|
+
* 手掛かりなしで起きる。
|
|
3025
|
+
*
|
|
3026
|
+
* **既定と同じ値の知らせは持たない** = 並び順は書かなければ書いた順で、`種類` はそれと
|
|
3027
|
+
* 必ず違う結果になる (同じ並びになるのは箱が種類の順に書かれていた時だけで、それは
|
|
3028
|
+
* 図ごとに変わる)。 向きのように「既定と同じ語」 が存在しない。
|
|
3029
|
+
*/
|
|
3030
|
+
function reportOrderNotHonored(doc: DslDocument, onNotice?: (n: CompileNotice) => void): void {
|
|
3031
|
+
if (!onNotice) return;
|
|
3032
|
+
if (doc.order === undefined) return;
|
|
3033
|
+
if (並び順を選べる図種.has(doc.type)) return;
|
|
3034
|
+
onNotice({
|
|
3035
|
+
kind: "order-not-honored",
|
|
3036
|
+
actor: doc.title,
|
|
3037
|
+
line: doc.orderPos?.line ?? doc.pos?.line ?? 0,
|
|
3038
|
+
message: `書いた order は効きません (type: ${doc.type} は並び方そのものが読み方を決めます)`,
|
|
3039
|
+
hint: `order を書けるのは ${[...並び順を選べる図種].join(" / ")} です`,
|
|
3040
|
+
});
|
|
3041
|
+
}
|
|
3042
|
+
|
|
3043
|
+
/**
|
|
3044
|
+
* 書いた形が効かない図種に書かれた時に伝える (#2657)。
|
|
3045
|
+
*
|
|
3046
|
+
* 並べ替えの知らせと同じ形にする。 黙って捨てると「書いたのに形が変わらない」 が
|
|
3047
|
+
* 手掛かりなしで起きる。
|
|
3048
|
+
*
|
|
3049
|
+
* 書ける形は `DIAGRAM_SHAPES` が図種ごとに決める。 別の図種の語は解析後も残し、ここで
|
|
3050
|
+
* 「読めたがこの図種では効かない」 と伝える。
|
|
3051
|
+
*/
|
|
3052
|
+
function reportShapeNotHonored(doc: DslDocument, onNotice?: (n: CompileNotice) => void): void {
|
|
3053
|
+
if (!onNotice) return;
|
|
3054
|
+
if (doc.shape === undefined) return;
|
|
3055
|
+
const 図種の語 = shapesForDiagramType(doc.type);
|
|
3056
|
+
if (図種の語.includes(doc.shape)) return;
|
|
3057
|
+
const hint =
|
|
3058
|
+
図種の語.length > 0
|
|
3059
|
+
? `type: ${doc.type} で書ける shape = ${図種の語.join(" / ")}`
|
|
3060
|
+
: `shape を書けるのは ${Object.entries(DIAGRAM_SHAPES)
|
|
3061
|
+
.map(([type, shapes]) => `type: ${type} (${shapes.join(" / ")})`)
|
|
3062
|
+
.join(" / ")} です`;
|
|
3063
|
+
onNotice({
|
|
3064
|
+
kind: "shape-not-honored",
|
|
3065
|
+
actor: doc.title,
|
|
3066
|
+
line: doc.shapePos?.line ?? doc.pos?.line ?? 0,
|
|
3067
|
+
message: `書いた shape: ${doc.shape} は効きません (type: ${doc.type} では使えません)`,
|
|
3068
|
+
hint,
|
|
3069
|
+
});
|
|
3070
|
+
}
|
|
3071
|
+
|
|
3072
|
+
/** `form` は円グラフだけ、`ticks` はガントチャートだけが読む。 */
|
|
3073
|
+
function reportFormNotHonored(doc: DslDocument, onNotice?: (n: CompileNotice) => void): void {
|
|
3074
|
+
if (doc.form === undefined || (doc.type === "chart" && doc.shape === "pie")) return;
|
|
3075
|
+
onNotice?.({
|
|
3076
|
+
kind: "chart-value-unreadable",
|
|
3077
|
+
actor: doc.title,
|
|
3078
|
+
line: doc.formPos?.line ?? 0,
|
|
3079
|
+
message: `form (見せ方) は shape: pie にだけ効きます (type: ${doc.type} では無視しました)`,
|
|
3080
|
+
});
|
|
3081
|
+
}
|
|
3082
|
+
|
|
3083
|
+
function reportTicksNotHonored(doc: DslDocument, onNotice?: (n: CompileNotice) => void): void {
|
|
3084
|
+
if (doc.type === "gantt") return;
|
|
3085
|
+
const fields = [
|
|
3086
|
+
...(doc.ticks !== undefined ? ["ticks (目盛り)"] : []),
|
|
3087
|
+
...(doc.ganttToday !== undefined ? ["ganttToday"] : []),
|
|
3088
|
+
...(doc.ganttTickLabels !== undefined ? ["ganttTickLabels"] : []),
|
|
3089
|
+
...(doc.ganttBarEnd !== undefined ? ["ganttBarEnd"] : []),
|
|
3090
|
+
...(doc.ganttBarThickness !== undefined ? ["ganttBarThickness"] : []),
|
|
3091
|
+
];
|
|
3092
|
+
if (fields.length === 0) return;
|
|
3093
|
+
onNotice?.({
|
|
3094
|
+
kind: "chart-value-unreadable",
|
|
3095
|
+
actor: doc.title,
|
|
3096
|
+
line: doc.ticksPos?.line ?? 0,
|
|
3097
|
+
message: `${fields.join(" / ")} は type: gantt にだけ効きます (type: ${doc.type} では無視しました)`,
|
|
3098
|
+
});
|
|
3099
|
+
}
|
|
3100
|
+
|
|
3101
|
+
/** 特別な泳路配置が元の lane を組み直す時、補足を黙って捨てないために伝える。 */
|
|
3102
|
+
function reportLaneSubtitleNotHonored(
|
|
3103
|
+
doc: DslDocument,
|
|
3104
|
+
onNotice?: (n: CompileNotice) => void,
|
|
3105
|
+
): void {
|
|
3106
|
+
if (
|
|
3107
|
+
!onNotice ||
|
|
3108
|
+
doc.type !== "swimlane" ||
|
|
3109
|
+
(doc.shape !== "stages" && doc.shape !== "timeline")
|
|
3110
|
+
) return;
|
|
3111
|
+
for (const lane of Object.values(doc.lanes ?? {})) {
|
|
3112
|
+
if (lane.subtitle === undefined) continue;
|
|
3113
|
+
onNotice({
|
|
3114
|
+
kind: "lane-option-not-honored",
|
|
3115
|
+
actor: lane.id,
|
|
3116
|
+
line: lane.pos.line,
|
|
3117
|
+
message: `縦列 "${truncateForMessage(lane.id)}" に書いた subtitle は shape: ${doc.shape} では効きません`,
|
|
3118
|
+
hint: "subtitle を消すか、通常の縦列または shape: metro の路線図で使ってください",
|
|
3119
|
+
});
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3122
|
+
|
|
3123
|
+
/** `stage` を省いた箱で担当または箱名を段階に使ったことを、図全体で 1 件だけ伝える (#2797)。 */
|
|
3124
|
+
function reportStageFromLane(doc: DslDocument, onNotice?: (n: CompileNotice) => void): void {
|
|
3125
|
+
if (!onNotice || doc.type !== "swimlane" || doc.shape !== "stages") return;
|
|
3126
|
+
const 補った = doc.actors.filter(
|
|
3127
|
+
(actor) => actor.stage === undefined && 泳法図で印をどう描く(doc, actor) !== "省く",
|
|
3128
|
+
);
|
|
3129
|
+
const 最初 = 補った[0];
|
|
3130
|
+
if (最初 === undefined) return;
|
|
3131
|
+
onNotice({
|
|
3132
|
+
kind: "stage-from-lane",
|
|
3133
|
+
actor: doc.title,
|
|
3134
|
+
line: 最初.pos?.line ?? doc.pos?.line ?? 0,
|
|
3135
|
+
message: `段階を書いていない箱は担当または箱の名前を段階として使いました: ${補った
|
|
3136
|
+
.map((actor) => truncateForMessage(actor.name))
|
|
3137
|
+
.join(" / ")}`,
|
|
3138
|
+
hint: "段階を分けて決めるなら、各箱に stage または 段階 を書いてください",
|
|
3139
|
+
});
|
|
3140
|
+
}
|
|
3141
|
+
|
|
3142
|
+
/** `lane` を省いた箱で箱名を線路名に使ったことを、図全体で 1 件だけ伝える (#2799)。 */
|
|
3143
|
+
function reportTrackFromName(doc: DslDocument, onNotice?: (n: CompileNotice) => void): void {
|
|
3144
|
+
if (!onNotice || doc.type !== "swimlane" || doc.shape !== "metro") return;
|
|
3145
|
+
const 補った = doc.actors.filter((actor) => actor.lane === undefined);
|
|
3146
|
+
const 最初 = 補った[0];
|
|
3147
|
+
if (最初 === undefined) return;
|
|
3148
|
+
onNotice({
|
|
3149
|
+
kind: "track-from-name",
|
|
3150
|
+
actor: doc.title,
|
|
3151
|
+
line: 最初.pos?.line ?? doc.pos?.line ?? 0,
|
|
3152
|
+
message: `担当を書いていない箱は箱の名前を線路に使いました: ${補った
|
|
3153
|
+
.map((actor) => truncateForMessage(actor.name))
|
|
3154
|
+
.join(" / ")}`,
|
|
3155
|
+
hint: "線路を担当でまとめるなら、各箱に lane を書いてください",
|
|
3156
|
+
});
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
/** `theme:` と別名の `palette:` を両方書いた時に、消す行を 1 件だけ伝える (#2790)。 */
|
|
3160
|
+
function reportThemeAlsoPalette(doc: DslDocument, onNotice?: (n: CompileNotice) => void): void {
|
|
3161
|
+
if (!onNotice || doc.themeAlsoPalettePos === undefined || doc.theme === undefined) return;
|
|
3162
|
+
onNotice({
|
|
3163
|
+
kind: "theme-palette-both",
|
|
3164
|
+
actor: doc.title,
|
|
3165
|
+
line: doc.themeAlsoPalettePos.paletteLine,
|
|
3166
|
+
message: `theme: と palette: を両方書きました。 palette: は theme: の別名なので theme: の値 (${doc.theme}) を使います`,
|
|
3167
|
+
hint: "palette: の行を消してください",
|
|
3168
|
+
});
|
|
1992
3169
|
}
|