@diagrammo/dgmo 0.8.2 → 0.8.4
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/.claude/commands/dgmo-diagram-this.md +60 -0
- package/.claude/commands/dgmo-document-project.md +128 -0
- package/.claude/commands/dgmo.md +185 -50
- package/.cursorrules +32 -37
- package/.github/copilot-instructions.md +35 -44
- package/.windsurfrules +32 -37
- package/README.md +4 -4
- package/dist/cli.cjs +189 -194
- package/dist/editor.cjs +336 -0
- package/dist/editor.cjs.map +1 -0
- package/dist/editor.d.cts +27 -0
- package/dist/editor.d.ts +27 -0
- package/dist/editor.js +305 -0
- package/dist/editor.js.map +1 -0
- package/dist/index.cjs +3699 -1564
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -6
- package/dist/index.d.ts +7 -6
- package/dist/index.js +3699 -1564
- package/dist/index.js.map +1 -1
- package/docs/language-reference.md +822 -1060
- package/gallery/fixtures/arc.dgmo +18 -0
- package/gallery/fixtures/area.dgmo +19 -0
- package/gallery/fixtures/bar-stacked.dgmo +10 -0
- package/gallery/fixtures/bar.dgmo +10 -0
- package/gallery/fixtures/c4-full.dgmo +52 -0
- package/gallery/fixtures/c4.dgmo +17 -0
- package/gallery/fixtures/chord.dgmo +12 -0
- package/gallery/fixtures/class-basic.dgmo +14 -0
- package/gallery/fixtures/class-full.dgmo +43 -0
- package/gallery/fixtures/doughnut.dgmo +8 -0
- package/gallery/fixtures/flowchart-basic.dgmo +3 -0
- package/gallery/fixtures/flowchart-colors.dgmo +5 -0
- package/gallery/fixtures/flowchart-complex.dgmo +17 -0
- package/gallery/fixtures/flowchart-decision.dgmo +5 -0
- package/gallery/fixtures/flowchart-full.dgmo +13 -0
- package/gallery/fixtures/flowchart-groups.dgmo +10 -0
- package/gallery/fixtures/flowchart-loop.dgmo +7 -0
- package/gallery/fixtures/flowchart-nested.dgmo +7 -0
- package/gallery/fixtures/flowchart-shapes.dgmo +5 -0
- package/gallery/fixtures/function.dgmo +8 -0
- package/gallery/fixtures/funnel.dgmo +7 -0
- package/gallery/fixtures/gantt-full.dgmo +49 -0
- package/gallery/fixtures/gantt.dgmo +42 -0
- package/gallery/fixtures/heatmap.dgmo +8 -0
- package/gallery/fixtures/infra-full.dgmo +78 -0
- package/gallery/fixtures/infra-overload.dgmo +25 -0
- package/gallery/fixtures/infra.dgmo +47 -0
- package/gallery/fixtures/initiative-status-full.dgmo +46 -0
- package/gallery/fixtures/initiative-status-phases.dgmo +29 -0
- package/gallery/fixtures/initiative-status.dgmo +9 -0
- package/gallery/fixtures/line.dgmo +19 -0
- package/gallery/fixtures/multi-line.dgmo +11 -0
- package/gallery/fixtures/org-basic.dgmo +16 -0
- package/gallery/fixtures/org-full.dgmo +69 -0
- package/gallery/fixtures/org-teams.dgmo +25 -0
- package/gallery/fixtures/pie.dgmo +9 -0
- package/gallery/fixtures/polar-area.dgmo +8 -0
- package/gallery/fixtures/quadrant.dgmo +18 -0
- package/gallery/fixtures/radar.dgmo +8 -0
- package/gallery/fixtures/sankey.dgmo +31 -0
- package/gallery/fixtures/scatter.dgmo +21 -0
- package/gallery/fixtures/sequence-tags-protocols.dgmo +45 -0
- package/gallery/fixtures/sequence-tags.dgmo +41 -0
- package/gallery/fixtures/sequence.dgmo +35 -0
- package/gallery/fixtures/sitemap-basic.dgmo +12 -0
- package/gallery/fixtures/sitemap-full.dgmo +156 -0
- package/gallery/fixtures/slope.dgmo +8 -0
- package/gallery/fixtures/spr-eras.dgmo +62 -0
- package/gallery/fixtures/state.dgmo +30 -0
- package/gallery/fixtures/timeline-intraday.dgmo +14 -0
- package/gallery/fixtures/timeline.dgmo +32 -0
- package/gallery/fixtures/venn.dgmo +10 -0
- package/gallery/fixtures/wordcloud.dgmo +24 -0
- package/package.json +51 -2
- package/src/c4/layout.ts +372 -90
- package/src/c4/parser.ts +113 -62
- package/src/chart.ts +149 -64
- package/src/class/parser.ts +84 -28
- package/src/class/renderer.ts +2 -2
- package/src/cli.ts +179 -77
- package/src/completion.ts +381 -182
- package/src/d3.ts +1026 -428
- package/src/dgmo-mermaid.ts +16 -13
- package/src/dgmo-router.ts +70 -24
- package/src/echarts.ts +682 -169
- package/src/editor/dgmo.grammar +69 -0
- package/src/editor/dgmo.grammar.d.ts +2 -0
- package/src/editor/dgmo.grammar.js +18 -0
- package/src/editor/dgmo.grammar.terms.d.ts +5 -0
- package/src/editor/dgmo.grammar.terms.js +35 -0
- package/src/editor/highlight.ts +36 -0
- package/src/editor/index.ts +28 -0
- package/src/editor/keywords.ts +220 -0
- package/src/editor/tokens.ts +30 -0
- package/src/er/parser.ts +55 -29
- package/src/er/renderer.ts +112 -53
- package/src/gantt/calculator.ts +91 -29
- package/src/gantt/parser.ts +291 -97
- package/src/gantt/renderer.ts +1120 -350
- package/src/graph/flowchart-parser.ts +48 -75
- package/src/graph/state-parser.ts +54 -27
- package/src/infra/parser.ts +161 -177
- package/src/infra/renderer.ts +723 -271
- package/src/infra/types.ts +0 -1
- package/src/initiative-status/parser.ts +144 -56
- package/src/kanban/parser.ts +27 -19
- package/src/org/layout.ts +111 -44
- package/src/org/parser.ts +71 -27
- package/src/org/resolver.ts +3 -3
- package/src/palettes/index.ts +3 -2
- package/src/render.ts +1 -2
- package/src/sequence/parser.ts +209 -100
- package/src/sitemap/parser.ts +73 -44
- package/src/utils/arrows.ts +2 -22
- package/src/utils/duration.ts +39 -21
- package/src/utils/legend-constants.ts +0 -2
- package/src/utils/parsing.ts +82 -72
- package/src/utils/tag-groups.ts +4 -41
- package/src/infra/serialize.ts +0 -67
package/src/c4/parser.ts
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
measureIndent,
|
|
12
12
|
extractColor,
|
|
13
13
|
parsePipeMetadata,
|
|
14
|
-
|
|
14
|
+
MULTIPLE_PIPE_ERROR,
|
|
15
15
|
parseFirstLine,
|
|
16
16
|
OPTION_NOCOLON_RE,
|
|
17
17
|
} from '../utils/parsing';
|
|
@@ -39,7 +39,8 @@ const ELEMENT_RE = /^(person|system|container|component)\s+(.+)$/i;
|
|
|
39
39
|
const IS_A_RE = /\s+is\s+a(?:n)?\s+(\w+)\s*$/i;
|
|
40
40
|
|
|
41
41
|
/** Matches `Name is a <type>` declarations (new preferred syntax) */
|
|
42
|
-
const C4_IS_A_RE =
|
|
42
|
+
const C4_IS_A_RE =
|
|
43
|
+
/^([^:]+?)\s+is\s+an?\s+(person|system|container|component|external|database)\b(.*)$/i;
|
|
43
44
|
|
|
44
45
|
/** Matches relationship arrows: `->`, `~>`, `<->`, `<~>` */
|
|
45
46
|
const RELATIONSHIP_RE = /^(<?-?>|<?~?>)\s*(.+)$/;
|
|
@@ -56,14 +57,13 @@ const SECTION_HEADER_RE = /^(containers|components|deployment)\s*$/i;
|
|
|
56
57
|
/** Matches `container X` references inside deployment nodes */
|
|
57
58
|
const CONTAINER_REF_RE = /^container\s+(.+)$/i;
|
|
58
59
|
|
|
59
|
-
/** Matches indented metadata: `key value` (
|
|
60
|
-
const METADATA_RE = /^([a-z][a-z0-9-]*)
|
|
60
|
+
/** Matches indented metadata: `key: value` (colon-separated) */
|
|
61
|
+
const METADATA_RE = /^([a-z][a-z0-9-]*):\s+(.+)$/i;
|
|
61
62
|
|
|
62
63
|
// ============================================================
|
|
63
64
|
// Helpers
|
|
64
65
|
// ============================================================
|
|
65
66
|
|
|
66
|
-
|
|
67
67
|
const VALID_ELEMENT_TYPES = new Set<string>([
|
|
68
68
|
'person',
|
|
69
69
|
'system',
|
|
@@ -81,10 +81,10 @@ const VALID_SHAPES = new Set<string>([
|
|
|
81
81
|
]);
|
|
82
82
|
|
|
83
83
|
/** Known top-level option keys for C4 diagrams. */
|
|
84
|
-
const KNOWN_C4_OPTIONS = new Set<string>([
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
]);
|
|
84
|
+
const KNOWN_C4_OPTIONS = new Set<string>(['layout']);
|
|
85
|
+
|
|
86
|
+
/** Known C4 boolean options (bare keyword = on). */
|
|
87
|
+
const KNOWN_C4_BOOLEANS = new Set<string>(['direction-tb']);
|
|
88
88
|
|
|
89
89
|
const ALL_CHART_TYPES = [
|
|
90
90
|
'c4',
|
|
@@ -106,9 +106,7 @@ const ALL_CHART_TYPES = [
|
|
|
106
106
|
];
|
|
107
107
|
|
|
108
108
|
/** Map from ParticipantType inference → C4Shape */
|
|
109
|
-
function participantTypeToC4Shape(
|
|
110
|
-
pType: string,
|
|
111
|
-
): C4Shape {
|
|
109
|
+
function participantTypeToC4Shape(pType: string): C4Shape {
|
|
112
110
|
switch (pType) {
|
|
113
111
|
case 'database':
|
|
114
112
|
return 'database';
|
|
@@ -151,7 +149,6 @@ function parseArrowType(arrow: string): C4ArrowType | null {
|
|
|
151
149
|
}
|
|
152
150
|
}
|
|
153
151
|
|
|
154
|
-
|
|
155
152
|
// ============================================================
|
|
156
153
|
// Stack entry types
|
|
157
154
|
// ============================================================
|
|
@@ -192,10 +189,7 @@ type StackEntry =
|
|
|
192
189
|
// Parser
|
|
193
190
|
// ============================================================
|
|
194
191
|
|
|
195
|
-
export function parseC4(
|
|
196
|
-
content: string,
|
|
197
|
-
palette?: PaletteColors,
|
|
198
|
-
): ParsedC4 {
|
|
192
|
+
export function parseC4(content: string, palette?: PaletteColors): ParsedC4 {
|
|
199
193
|
const result: ParsedC4 = {
|
|
200
194
|
title: null,
|
|
201
195
|
titleLineNumber: null,
|
|
@@ -208,10 +202,15 @@ export function parseC4(
|
|
|
208
202
|
error: null,
|
|
209
203
|
};
|
|
210
204
|
|
|
211
|
-
const pushError = (
|
|
205
|
+
const pushError = (
|
|
206
|
+
line: number,
|
|
207
|
+
message: string,
|
|
208
|
+
severity: 'error' | 'warning' = 'error'
|
|
209
|
+
): void => {
|
|
212
210
|
const diag = makeDgmoError(line, message, severity);
|
|
213
211
|
result.diagnostics.push(diag);
|
|
214
|
-
if (!result.error && severity === 'error')
|
|
212
|
+
if (!result.error && severity === 'error')
|
|
213
|
+
result.error = formatDgmoError(diag);
|
|
215
214
|
};
|
|
216
215
|
|
|
217
216
|
const fail = (line: number, message: string): ParsedC4 => {
|
|
@@ -286,10 +285,6 @@ export function parseC4(
|
|
|
286
285
|
pushError(lineNumber, 'Tag groups must appear before content');
|
|
287
286
|
continue;
|
|
288
287
|
}
|
|
289
|
-
if (tagBlockMatch.deprecated) {
|
|
290
|
-
pushError(lineNumber, `'## ${tagBlockMatch.name}' is no longer supported — use 'tag: ${tagBlockMatch.name}' instead`);
|
|
291
|
-
continue;
|
|
292
|
-
}
|
|
293
288
|
currentTagGroup = {
|
|
294
289
|
name: tagBlockMatch.name,
|
|
295
290
|
alias: tagBlockMatch.alias,
|
|
@@ -297,14 +292,23 @@ export function parseC4(
|
|
|
297
292
|
lineNumber,
|
|
298
293
|
};
|
|
299
294
|
if (tagBlockMatch.alias) {
|
|
300
|
-
aliasMap.set(
|
|
295
|
+
aliasMap.set(
|
|
296
|
+
tagBlockMatch.alias.toLowerCase(),
|
|
297
|
+
tagBlockMatch.name.toLowerCase()
|
|
298
|
+
);
|
|
301
299
|
}
|
|
302
300
|
result.tagGroups.push(currentTagGroup);
|
|
303
301
|
continue;
|
|
304
302
|
}
|
|
305
303
|
|
|
306
|
-
// Generic header options (space-separated: `key value`)
|
|
304
|
+
// Generic header options (space-separated: `key value` or bare boolean)
|
|
307
305
|
if (!contentStarted && !currentTagGroup && measureIndent(line) === 0) {
|
|
306
|
+
// Bare boolean options
|
|
307
|
+
if (KNOWN_C4_BOOLEANS.has(trimmed.toLowerCase())) {
|
|
308
|
+
result.options[trimmed.toLowerCase()] = 'on';
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
|
|
308
312
|
const optMatch = trimmed.match(OPTION_NOCOLON_RE);
|
|
309
313
|
if (optMatch) {
|
|
310
314
|
const key = optMatch[1].trim().toLowerCase();
|
|
@@ -323,7 +327,7 @@ export function parseC4(
|
|
|
323
327
|
if (!color) {
|
|
324
328
|
pushError(
|
|
325
329
|
lineNumber,
|
|
326
|
-
`Expected 'Value(color)' in tag group '${currentTagGroup.name}'
|
|
330
|
+
`Expected 'Value(color)' in tag group '${currentTagGroup.name}'`
|
|
327
331
|
);
|
|
328
332
|
continue;
|
|
329
333
|
}
|
|
@@ -338,7 +342,7 @@ export function parseC4(
|
|
|
338
342
|
});
|
|
339
343
|
continue;
|
|
340
344
|
}
|
|
341
|
-
currentTagGroup = null;
|
|
345
|
+
currentTagGroup = null; // eslint-disable-line no-useless-assignment
|
|
342
346
|
}
|
|
343
347
|
|
|
344
348
|
// --- Content phase ---
|
|
@@ -361,7 +365,10 @@ export function parseC4(
|
|
|
361
365
|
}
|
|
362
366
|
|
|
363
367
|
// Check for top-level non-deployment content (section ended)
|
|
364
|
-
if (
|
|
368
|
+
if (
|
|
369
|
+
indent === 0 &&
|
|
370
|
+
(C4_IS_A_RE.test(trimmed) || ELEMENT_RE.test(trimmed))
|
|
371
|
+
) {
|
|
365
372
|
inDeployment = false;
|
|
366
373
|
// Fall through to element parsing below
|
|
367
374
|
} else {
|
|
@@ -371,10 +378,13 @@ export function parseC4(
|
|
|
371
378
|
const refName = refMatch[1].trim();
|
|
372
379
|
if (deployStack.length > 0) {
|
|
373
380
|
deployStack[deployStack.length - 1].node.containerRefs.push(
|
|
374
|
-
refName
|
|
381
|
+
refName
|
|
375
382
|
);
|
|
376
383
|
} else {
|
|
377
|
-
pushError(
|
|
384
|
+
pushError(
|
|
385
|
+
lineNumber,
|
|
386
|
+
`"container ${refName}" must be inside a deployment node`
|
|
387
|
+
);
|
|
378
388
|
}
|
|
379
389
|
continue;
|
|
380
390
|
}
|
|
@@ -382,8 +392,13 @@ export function parseC4(
|
|
|
382
392
|
// Otherwise it's a deployment node (possibly with pipe metadata)
|
|
383
393
|
const segments = trimmed.split('|').map((s) => s.trim());
|
|
384
394
|
const nodeName = segments[0];
|
|
385
|
-
const metadata = parsePipeMetadata(segments, aliasMap, () =>
|
|
386
|
-
|
|
395
|
+
const metadata = parsePipeMetadata(segments, aliasMap, () =>
|
|
396
|
+
pushError(lineNumber, MULTIPLE_PIPE_ERROR)
|
|
397
|
+
);
|
|
398
|
+
const shape = inferC4Shape(
|
|
399
|
+
nodeName,
|
|
400
|
+
metadata.tech ?? metadata.technology
|
|
401
|
+
);
|
|
387
402
|
|
|
388
403
|
const dNode: C4DeploymentNode = {
|
|
389
404
|
name: nodeName,
|
|
@@ -417,8 +432,9 @@ export function parseC4(
|
|
|
417
432
|
// containers / components must be inside an element
|
|
418
433
|
const parentEntry = findParentElement(indent, stack);
|
|
419
434
|
if (parentEntry) {
|
|
420
|
-
parentEntry.element.sectionHeader =
|
|
421
|
-
|
|
435
|
+
parentEntry.element.sectionHeader = sectionType as
|
|
436
|
+
| 'containers'
|
|
437
|
+
| 'components';
|
|
422
438
|
parentEntry.element.sectionHeaderLineNumber = lineNumber;
|
|
423
439
|
stack.push({
|
|
424
440
|
kind: 'section',
|
|
@@ -427,10 +443,7 @@ export function parseC4(
|
|
|
427
443
|
indent,
|
|
428
444
|
});
|
|
429
445
|
} else {
|
|
430
|
-
pushError(
|
|
431
|
-
lineNumber,
|
|
432
|
-
`"${sectionType}" must be inside an element`,
|
|
433
|
-
);
|
|
446
|
+
pushError(lineNumber, `"${sectionType}" must be inside an element`);
|
|
434
447
|
}
|
|
435
448
|
continue;
|
|
436
449
|
}
|
|
@@ -487,9 +500,15 @@ export function parseC4(
|
|
|
487
500
|
if (!rawLabel) break; // empty label — fall through to plain arrow
|
|
488
501
|
|
|
489
502
|
// Reject bidirectional arrows
|
|
490
|
-
if (
|
|
503
|
+
if (
|
|
504
|
+
arrowType === 'bidirectional' ||
|
|
505
|
+
arrowType === 'bidirectional-async'
|
|
506
|
+
) {
|
|
491
507
|
const source = findParentElement(indent, stack)?.element.name ?? '?';
|
|
492
|
-
pushError(
|
|
508
|
+
pushError(
|
|
509
|
+
lineNumber,
|
|
510
|
+
`Bidirectional arrows are no longer supported. Replace with two separate arrows:\n -${rawLabel}-> ${targetBody}\n ${targetBody} -${rawLabel}-> ${source}`
|
|
511
|
+
);
|
|
493
512
|
labeledHandled = true;
|
|
494
513
|
break;
|
|
495
514
|
}
|
|
@@ -546,11 +565,17 @@ export function parseC4(
|
|
|
546
565
|
const arrowType = parseArrowType(relMatch[1]);
|
|
547
566
|
if (arrowType) {
|
|
548
567
|
// Reject bidirectional arrows
|
|
549
|
-
if (
|
|
568
|
+
if (
|
|
569
|
+
arrowType === 'bidirectional' ||
|
|
570
|
+
arrowType === 'bidirectional-async'
|
|
571
|
+
) {
|
|
550
572
|
const arrow = relMatch[1];
|
|
551
573
|
const target = relMatch[2].trim();
|
|
552
574
|
const source = findParentElement(indent, stack)?.element.name ?? '?';
|
|
553
|
-
pushError(
|
|
575
|
+
pushError(
|
|
576
|
+
lineNumber,
|
|
577
|
+
`'${arrow}' bidirectional arrows are no longer supported. Replace with two separate arrows:\n -> ${target}\n ${target} -> ${source}`
|
|
578
|
+
);
|
|
554
579
|
continue;
|
|
555
580
|
}
|
|
556
581
|
|
|
@@ -599,14 +624,22 @@ export function parseC4(
|
|
|
599
624
|
let segments: string[];
|
|
600
625
|
if (remainderTrimmed.startsWith('|')) {
|
|
601
626
|
// remainder has pipe metadata: "| tech: PostgreSQL, team: Data"
|
|
602
|
-
segments = [
|
|
627
|
+
segments = [
|
|
628
|
+
'',
|
|
629
|
+
...remainderTrimmed
|
|
630
|
+
.substring(1)
|
|
631
|
+
.split('|')
|
|
632
|
+
.map((s) => s.trim()),
|
|
633
|
+
];
|
|
603
634
|
} else {
|
|
604
635
|
segments = [remainderTrimmed];
|
|
605
636
|
}
|
|
606
637
|
|
|
607
638
|
// Check for additional `is a <shape>` in the name (e.g., already stripped by C4_IS_A_RE won't happen,
|
|
608
639
|
// but handle remainder like "is a cylinder" after type)
|
|
609
|
-
const remainderIsA = remainderTrimmed.match(
|
|
640
|
+
const remainderIsA = remainderTrimmed.match(
|
|
641
|
+
/^\s*is\s+a(?:n)?\s+(\w+)\s*(.*)$/i
|
|
642
|
+
);
|
|
610
643
|
if (remainderIsA) {
|
|
611
644
|
const shapeName = remainderIsA[1].toLowerCase();
|
|
612
645
|
if (VALID_SHAPES.has(shapeName)) {
|
|
@@ -614,13 +647,19 @@ export function parseC4(
|
|
|
614
647
|
} else {
|
|
615
648
|
pushError(
|
|
616
649
|
lineNumber,
|
|
617
|
-
`Unknown shape "${remainderIsA[1]}". Valid shapes: ${[...VALID_SHAPES].join(', ')}
|
|
650
|
+
`Unknown shape "${remainderIsA[1]}". Valid shapes: ${[...VALID_SHAPES].join(', ')}`
|
|
618
651
|
);
|
|
619
652
|
}
|
|
620
653
|
// Re-parse remainder after shape
|
|
621
654
|
const afterShape = remainderIsA[2].trim();
|
|
622
655
|
if (afterShape.startsWith('|')) {
|
|
623
|
-
segments = [
|
|
656
|
+
segments = [
|
|
657
|
+
'',
|
|
658
|
+
...afterShape
|
|
659
|
+
.substring(1)
|
|
660
|
+
.split('|')
|
|
661
|
+
.map((s) => s.trim()),
|
|
662
|
+
];
|
|
624
663
|
} else {
|
|
625
664
|
segments = [afterShape];
|
|
626
665
|
}
|
|
@@ -635,13 +674,15 @@ export function parseC4(
|
|
|
635
674
|
} else {
|
|
636
675
|
pushError(
|
|
637
676
|
lineNumber,
|
|
638
|
-
`Unknown shape "${nameIsAMatch[1]}". Valid shapes: ${[...VALID_SHAPES].join(', ')}
|
|
677
|
+
`Unknown shape "${nameIsAMatch[1]}". Valid shapes: ${[...VALID_SHAPES].join(', ')}`
|
|
639
678
|
);
|
|
640
679
|
}
|
|
641
680
|
namePart = namePart.substring(0, nameIsAMatch.index!).trim();
|
|
642
681
|
}
|
|
643
682
|
|
|
644
|
-
const metadata = parsePipeMetadata(segments, aliasMap, () =>
|
|
683
|
+
const metadata = parsePipeMetadata(segments, aliasMap, () =>
|
|
684
|
+
pushError(lineNumber, MULTIPLE_PIPE_ERROR)
|
|
685
|
+
);
|
|
645
686
|
|
|
646
687
|
const shape =
|
|
647
688
|
explicitShape ??
|
|
@@ -663,7 +704,7 @@ export function parseC4(
|
|
|
663
704
|
if (existingLine !== undefined) {
|
|
664
705
|
pushError(
|
|
665
706
|
lineNumber,
|
|
666
|
-
`Duplicate element name "${namePart}" (first defined on line ${existingLine})
|
|
707
|
+
`Duplicate element name "${namePart}" (first defined on line ${existingLine})`
|
|
667
708
|
);
|
|
668
709
|
} else {
|
|
669
710
|
knownNames.set(namePart.toLowerCase(), lineNumber);
|
|
@@ -677,7 +718,7 @@ export function parseC4(
|
|
|
677
718
|
const elementMatch = trimmed.match(ELEMENT_RE);
|
|
678
719
|
if (elementMatch) {
|
|
679
720
|
const elementType = elementMatch[1].toLowerCase() as C4ElementType;
|
|
680
|
-
|
|
721
|
+
const nameAndRest = elementMatch[2];
|
|
681
722
|
|
|
682
723
|
// Split on pipe for inline metadata
|
|
683
724
|
const segments = nameAndRest.split('|').map((s) => s.trim());
|
|
@@ -693,7 +734,7 @@ export function parseC4(
|
|
|
693
734
|
} else {
|
|
694
735
|
pushError(
|
|
695
736
|
lineNumber,
|
|
696
|
-
`Unknown shape "${isAMatch[1]}". Valid shapes: ${[...VALID_SHAPES].join(', ')}
|
|
737
|
+
`Unknown shape "${isAMatch[1]}". Valid shapes: ${[...VALID_SHAPES].join(', ')}`
|
|
697
738
|
);
|
|
698
739
|
}
|
|
699
740
|
namePart = namePart.substring(0, isAMatch.index!).trim();
|
|
@@ -702,10 +743,12 @@ export function parseC4(
|
|
|
702
743
|
// Emit deprecation error with migration hint
|
|
703
744
|
pushError(
|
|
704
745
|
lineNumber,
|
|
705
|
-
`'${elementMatch[1]} ${namePart}' prefix syntax is no longer supported — use '${namePart} is a ${elementType}' instead
|
|
746
|
+
`'${elementMatch[1]} ${namePart}' prefix syntax is no longer supported — use '${namePart} is a ${elementType}' instead`
|
|
706
747
|
);
|
|
707
748
|
|
|
708
|
-
const metadata = parsePipeMetadata(segments, aliasMap, () =>
|
|
749
|
+
const metadata = parsePipeMetadata(segments, aliasMap, () =>
|
|
750
|
+
pushError(lineNumber, MULTIPLE_PIPE_ERROR)
|
|
751
|
+
);
|
|
709
752
|
|
|
710
753
|
// Determine shape: explicit > inference
|
|
711
754
|
const shape =
|
|
@@ -728,7 +771,7 @@ export function parseC4(
|
|
|
728
771
|
if (existingLine !== undefined) {
|
|
729
772
|
pushError(
|
|
730
773
|
lineNumber,
|
|
731
|
-
`Duplicate element name "${namePart}" (first defined on line ${existingLine})
|
|
774
|
+
`Duplicate element name "${namePart}" (first defined on line ${existingLine})`
|
|
732
775
|
);
|
|
733
776
|
} else {
|
|
734
777
|
knownNames.set(namePart.toLowerCase(), lineNumber);
|
|
@@ -747,7 +790,7 @@ export function parseC4(
|
|
|
747
790
|
if (parentEntry) {
|
|
748
791
|
const rawKey = metadataMatch[1].trim().toLowerCase();
|
|
749
792
|
|
|
750
|
-
// Special case: `import file.dgmo`
|
|
793
|
+
// Special case: `import: file.dgmo`
|
|
751
794
|
if (rawKey === 'import') {
|
|
752
795
|
parentEntry.element.importPath = metadataMatch[2].trim();
|
|
753
796
|
continue;
|
|
@@ -792,7 +835,7 @@ export function parseC4(
|
|
|
792
835
|
/** Find the nearest parent element entry on the stack at shallower indent. */
|
|
793
836
|
function findParentElement(
|
|
794
837
|
indent: number,
|
|
795
|
-
stack: StackEntry[]
|
|
838
|
+
stack: StackEntry[]
|
|
796
839
|
): ElementStackEntry | null {
|
|
797
840
|
for (let i = stack.length - 1; i >= 0; i--) {
|
|
798
841
|
const entry = stack[i];
|
|
@@ -818,7 +861,7 @@ function attachElement(
|
|
|
818
861
|
element: C4Element,
|
|
819
862
|
indent: number,
|
|
820
863
|
stack: StackEntry[],
|
|
821
|
-
result: ParsedC4
|
|
864
|
+
result: ParsedC4
|
|
822
865
|
): void {
|
|
823
866
|
// Find the immediate context: group, section, or parent element
|
|
824
867
|
let attached = false;
|
|
@@ -860,7 +903,11 @@ function attachElement(
|
|
|
860
903
|
function validateRelationshipTargets(
|
|
861
904
|
result: ParsedC4,
|
|
862
905
|
knownNames: Map<string, number>,
|
|
863
|
-
pushWarning: (
|
|
906
|
+
pushWarning: (
|
|
907
|
+
line: number,
|
|
908
|
+
message: string,
|
|
909
|
+
severity?: 'error' | 'warning'
|
|
910
|
+
) => void
|
|
864
911
|
): void {
|
|
865
912
|
function walkRels(elements: C4Element[]) {
|
|
866
913
|
for (const el of elements) {
|
|
@@ -869,7 +916,7 @@ function validateRelationshipTargets(
|
|
|
869
916
|
pushWarning(
|
|
870
917
|
rel.lineNumber,
|
|
871
918
|
`Relationship target "${rel.target}" not found`,
|
|
872
|
-
'warning'
|
|
919
|
+
'warning'
|
|
873
920
|
);
|
|
874
921
|
}
|
|
875
922
|
}
|
|
@@ -887,7 +934,7 @@ function validateRelationshipTargets(
|
|
|
887
934
|
pushWarning(
|
|
888
935
|
rel.lineNumber,
|
|
889
936
|
`Relationship target "${rel.target}" not found`,
|
|
890
|
-
'warning'
|
|
937
|
+
'warning'
|
|
891
938
|
);
|
|
892
939
|
}
|
|
893
940
|
}
|
|
@@ -896,7 +943,11 @@ function validateRelationshipTargets(
|
|
|
896
943
|
function validateDeploymentRefs(
|
|
897
944
|
result: ParsedC4,
|
|
898
945
|
knownNames: Map<string, number>,
|
|
899
|
-
pushWarning: (
|
|
946
|
+
pushWarning: (
|
|
947
|
+
line: number,
|
|
948
|
+
message: string,
|
|
949
|
+
severity?: 'error' | 'warning'
|
|
950
|
+
) => void
|
|
900
951
|
): void {
|
|
901
952
|
function walkDeploy(nodes: C4DeploymentNode[]) {
|
|
902
953
|
for (const node of nodes) {
|
|
@@ -905,7 +956,7 @@ function validateDeploymentRefs(
|
|
|
905
956
|
pushWarning(
|
|
906
957
|
node.lineNumber,
|
|
907
958
|
`Deployment reference "container ${ref}" not found`,
|
|
908
|
-
'warning'
|
|
959
|
+
'warning'
|
|
909
960
|
);
|
|
910
961
|
}
|
|
911
962
|
}
|