@diagrammo/dgmo 0.8.21 → 0.8.23
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/AGENTS.md +2 -1
- package/README.md +1 -0
- package/dist/cli.cjs +145 -93
- package/dist/editor.cjs +20 -3
- package/dist/editor.cjs.map +1 -1
- package/dist/editor.js +20 -3
- package/dist/editor.js.map +1 -1
- package/dist/highlight.cjs +15 -2
- package/dist/highlight.cjs.map +1 -1
- package/dist/highlight.js +15 -2
- package/dist/highlight.js.map +1 -1
- package/dist/index.cjs +20843 -14937
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +426 -17
- package/dist/index.d.ts +426 -17
- package/dist/index.js +20795 -14912
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +380 -0
- package/dist/internal.cjs.map +1 -0
- package/dist/internal.d.cts +179 -0
- package/dist/internal.d.ts +179 -0
- package/dist/internal.js +337 -0
- package/dist/internal.js.map +1 -0
- package/docs/guide/chart-cycle.md +156 -0
- package/docs/guide/chart-journey-map.md +179 -0
- package/docs/guide/chart-pyramid.md +111 -0
- package/docs/guide/chart-sitemap.md +18 -1
- package/docs/guide/chart-tech-radar.md +219 -0
- package/docs/guide/registry.json +6 -0
- package/docs/language-reference.md +177 -6
- package/gallery/fixtures/boxes-and-lines.dgmo +10 -3
- package/gallery/fixtures/c4-full.dgmo +2 -2
- package/gallery/fixtures/cycle/ooda-loop.dgmo +25 -0
- package/gallery/fixtures/cycle/pdca-circle-nodes.dgmo +12 -0
- package/gallery/fixtures/cycle/pdca-minimal.dgmo +6 -0
- package/gallery/fixtures/cycle/sprint-cycle-span.dgmo +17 -0
- package/gallery/fixtures/gantt-full.dgmo +2 -2
- package/gallery/fixtures/gantt.dgmo +2 -2
- package/gallery/fixtures/infra-full.dgmo +2 -2
- package/gallery/fixtures/infra.dgmo +1 -1
- package/gallery/fixtures/pyramid/dikw.dgmo +17 -0
- package/gallery/fixtures/pyramid/inverted-funnel.dgmo +16 -0
- package/gallery/fixtures/pyramid/minimal.dgmo +5 -0
- package/gallery/fixtures/sequence-tags-protocols.dgmo +2 -2
- package/gallery/fixtures/sequence-tags.dgmo +2 -2
- package/gallery/fixtures/tech-radar-dense.dgmo +77 -0
- package/gallery/fixtures/tech-radar.dgmo +36 -0
- package/gallery/fixtures/timeline.dgmo +1 -1
- package/package.json +11 -1
- package/src/boxes-and-lines/layout.ts +309 -33
- package/src/boxes-and-lines/parser.ts +86 -10
- package/src/boxes-and-lines/renderer.ts +250 -91
- package/src/boxes-and-lines/types.ts +1 -1
- package/src/c4/layout.ts +8 -8
- package/src/c4/parser.ts +35 -2
- package/src/c4/renderer.ts +19 -3
- package/src/c4/types.ts +1 -0
- package/src/chart.ts +14 -7
- package/src/cli.ts +5 -35
- package/src/completion.ts +233 -41
- package/src/cycle/layout.ts +723 -0
- package/src/cycle/parser.ts +352 -0
- package/src/cycle/renderer.ts +566 -0
- package/src/cycle/types.ts +98 -0
- package/src/d3.ts +107 -8
- package/src/dgmo-router.ts +82 -3
- package/src/echarts.ts +8 -5
- package/src/editor/dgmo.grammar +5 -1
- package/src/editor/dgmo.grammar.js +1 -1
- package/src/editor/keywords.ts +17 -0
- package/src/gantt/parser.ts +2 -8
- package/src/graph/flowchart-parser.ts +15 -21
- package/src/graph/state-parser.ts +5 -10
- package/src/index.ts +63 -2
- package/src/infra/layout.ts +218 -74
- package/src/infra/parser.ts +32 -8
- package/src/infra/renderer.ts +14 -8
- package/src/infra/types.ts +10 -3
- package/src/internal.ts +16 -0
- package/src/journey-map/layout.ts +386 -0
- package/src/journey-map/parser.ts +540 -0
- package/src/journey-map/renderer.ts +1521 -0
- package/src/journey-map/types.ts +47 -0
- package/src/kanban/parser.ts +3 -10
- package/src/kanban/renderer.ts +31 -15
- package/src/mindmap/parser.ts +12 -18
- package/src/mindmap/renderer.ts +14 -13
- package/src/mindmap/text-wrap.ts +22 -12
- package/src/mindmap/types.ts +2 -2
- package/src/org/collapse.ts +81 -0
- package/src/org/parser.ts +2 -6
- package/src/org/renderer.ts +212 -4
- package/src/pyramid/parser.ts +172 -0
- package/src/pyramid/renderer.ts +684 -0
- package/src/pyramid/types.ts +28 -0
- package/src/render.ts +2 -8
- package/src/sequence/parser.ts +62 -20
- package/src/sequence/renderer.ts +146 -40
- package/src/sharing.ts +1 -0
- package/src/sitemap/layout.ts +21 -6
- package/src/sitemap/parser.ts +26 -17
- package/src/sitemap/renderer.ts +34 -0
- package/src/sitemap/types.ts +1 -0
- package/src/tech-radar/index.ts +14 -0
- package/src/tech-radar/interactive.ts +1112 -0
- package/src/tech-radar/layout.ts +190 -0
- package/src/tech-radar/parser.ts +385 -0
- package/src/tech-radar/renderer.ts +1159 -0
- package/src/tech-radar/shared.ts +187 -0
- package/src/tech-radar/types.ts +81 -0
- package/src/utils/description-helpers.ts +33 -0
- package/src/utils/legend-layout.ts +3 -1
- package/src/utils/parsing.ts +47 -7
- package/src/utils/tag-groups.ts +46 -60
package/src/d3.ts
CHANGED
|
@@ -19,7 +19,10 @@ export type VisualizationType =
|
|
|
19
19
|
| 'timeline'
|
|
20
20
|
| 'venn'
|
|
21
21
|
| 'quadrant'
|
|
22
|
-
| 'sequence'
|
|
22
|
+
| 'sequence'
|
|
23
|
+
| 'tech-radar'
|
|
24
|
+
| 'cycle'
|
|
25
|
+
| 'pyramid';
|
|
23
26
|
|
|
24
27
|
interface D3DataItem {
|
|
25
28
|
label: string;
|
|
@@ -184,6 +187,7 @@ import { makeDgmoError, formatDgmoError, suggest } from './diagnostics';
|
|
|
184
187
|
import {
|
|
185
188
|
collectIndentedValues,
|
|
186
189
|
extractColor,
|
|
190
|
+
normalizeNumericToken,
|
|
187
191
|
parseFirstLine,
|
|
188
192
|
parsePipeMetadata,
|
|
189
193
|
MULTIPLE_PIPE_ERROR,
|
|
@@ -632,7 +636,7 @@ export function parseVisualization(
|
|
|
632
636
|
// Arc link line: source -> target(color) weight
|
|
633
637
|
if (result.type === 'arc') {
|
|
634
638
|
const linkMatch = line.match(
|
|
635
|
-
/^(.+?)\s*->\s*(.+?)(?:\(([^)]+)\))?\s*(?:\s+(\d+(
|
|
639
|
+
/^(.+?)\s*->\s*(.+?)(?:\(([^)]+)\))?\s*(?:\s+(-?[\d,_]+(?:\.[\d]+)?))?$/
|
|
636
640
|
);
|
|
637
641
|
if (linkMatch) {
|
|
638
642
|
const source = linkMatch[1].trim();
|
|
@@ -648,7 +652,9 @@ export function parseVisualization(
|
|
|
648
652
|
result.links.push({
|
|
649
653
|
source,
|
|
650
654
|
target,
|
|
651
|
-
value: linkMatch[4]
|
|
655
|
+
value: linkMatch[4]
|
|
656
|
+
? parseFloat(normalizeNumericToken(linkMatch[4]) ?? linkMatch[4])
|
|
657
|
+
: 1,
|
|
652
658
|
color: linkColor,
|
|
653
659
|
lineNumber,
|
|
654
660
|
});
|
|
@@ -1028,7 +1034,7 @@ export function parseVisualization(
|
|
|
1028
1034
|
|
|
1029
1035
|
// Data points: Label x, y OR Label x y
|
|
1030
1036
|
const pointMatch = line.match(
|
|
1031
|
-
/^(.+?)\s+([0-9]
|
|
1037
|
+
/^(.+?)\s+(-?[0-9][0-9,_]*(?:\.[0-9]+)?)\s*[,\s]\s*(-?[0-9][0-9,_]*(?:\.[0-9]+)?)\s*$/
|
|
1032
1038
|
);
|
|
1033
1039
|
if (pointMatch) {
|
|
1034
1040
|
const label = pointMatch[1].trim();
|
|
@@ -1042,8 +1048,12 @@ export function parseVisualization(
|
|
|
1042
1048
|
) {
|
|
1043
1049
|
result.quadrantPoints.push({
|
|
1044
1050
|
label,
|
|
1045
|
-
x: parseFloat(
|
|
1046
|
-
|
|
1051
|
+
x: parseFloat(
|
|
1052
|
+
normalizeNumericToken(pointMatch[2]) ?? pointMatch[2]
|
|
1053
|
+
),
|
|
1054
|
+
y: parseFloat(
|
|
1055
|
+
normalizeNumericToken(pointMatch[3]) ?? pointMatch[3]
|
|
1056
|
+
),
|
|
1047
1057
|
lineNumber,
|
|
1048
1058
|
});
|
|
1049
1059
|
}
|
|
@@ -1201,7 +1211,8 @@ export function parseVisualization(
|
|
|
1201
1211
|
// Scan from right, capped at P values
|
|
1202
1212
|
let rightIdx = tokens.length - 1;
|
|
1203
1213
|
while (rightIdx >= 0 && values.length < P) {
|
|
1204
|
-
const raw =
|
|
1214
|
+
const raw =
|
|
1215
|
+
normalizeNumericToken(tokens[rightIdx]) ?? tokens[rightIdx];
|
|
1205
1216
|
const num = parseFloat(raw);
|
|
1206
1217
|
if (!isNaN(num) && /^-?\d/.test(raw)) {
|
|
1207
1218
|
values.unshift(num);
|
|
@@ -1385,8 +1396,11 @@ export function parseVisualization(
|
|
|
1385
1396
|
} else if (colonIndex === -1) {
|
|
1386
1397
|
// Try "word weight" or "multi-word-label weight" space-separated format
|
|
1387
1398
|
const lastSpace = line.lastIndexOf(' ');
|
|
1399
|
+
const rawWeight = lastSpace >= 0 ? line.substring(lastSpace + 1) : '';
|
|
1388
1400
|
const maybeWeight =
|
|
1389
|
-
lastSpace >= 0
|
|
1401
|
+
lastSpace >= 0
|
|
1402
|
+
? parseFloat(normalizeNumericToken(rawWeight) ?? rawWeight)
|
|
1403
|
+
: NaN;
|
|
1390
1404
|
if (lastSpace >= 0 && !isNaN(maybeWeight) && maybeWeight > 0) {
|
|
1391
1405
|
result.words.push({
|
|
1392
1406
|
text: line.substring(0, lastSpace).trim(),
|
|
@@ -7041,6 +7055,91 @@ export async function renderForExport(
|
|
|
7041
7055
|
return finalizeSvgExport(container, theme, effectivePalette);
|
|
7042
7056
|
}
|
|
7043
7057
|
|
|
7058
|
+
if (detectedType === 'tech-radar') {
|
|
7059
|
+
const { parseTechRadar } = await import('./tech-radar/parser');
|
|
7060
|
+
const { renderTechRadarForExport } = await import('./tech-radar/renderer');
|
|
7061
|
+
|
|
7062
|
+
const effectivePalette = await resolveExportPalette(theme, palette);
|
|
7063
|
+
const radarParsed = parseTechRadar(content);
|
|
7064
|
+
if (radarParsed.error || radarParsed.quadrants.length === 0) return '';
|
|
7065
|
+
|
|
7066
|
+
const RADAR_EXPORT_W = 1600;
|
|
7067
|
+
const RADAR_EXPORT_H = 1200;
|
|
7068
|
+
const container = createExportContainer(RADAR_EXPORT_W, RADAR_EXPORT_H);
|
|
7069
|
+
renderTechRadarForExport(
|
|
7070
|
+
container,
|
|
7071
|
+
radarParsed,
|
|
7072
|
+
effectivePalette,
|
|
7073
|
+
theme === 'dark',
|
|
7074
|
+
{ width: RADAR_EXPORT_W, height: RADAR_EXPORT_H },
|
|
7075
|
+
viewState
|
|
7076
|
+
);
|
|
7077
|
+
return finalizeSvgExport(container, theme, effectivePalette);
|
|
7078
|
+
}
|
|
7079
|
+
|
|
7080
|
+
if (detectedType === 'journey-map') {
|
|
7081
|
+
const { parseJourneyMap } = await import('./journey-map/parser');
|
|
7082
|
+
const { renderJourneyMap } = await import('./journey-map/renderer');
|
|
7083
|
+
const { layoutJourneyMap } = await import('./journey-map/layout');
|
|
7084
|
+
|
|
7085
|
+
const effectivePalette = await resolveExportPalette(theme, palette);
|
|
7086
|
+
const jmParsed = parseJourneyMap(content, effectivePalette);
|
|
7087
|
+
if (
|
|
7088
|
+
jmParsed.error ||
|
|
7089
|
+
(jmParsed.phases.length === 0 && jmParsed.steps.length === 0)
|
|
7090
|
+
)
|
|
7091
|
+
return '';
|
|
7092
|
+
|
|
7093
|
+
const jmLayout = layoutJourneyMap(jmParsed, effectivePalette);
|
|
7094
|
+
const container = createExportContainer(
|
|
7095
|
+
jmLayout.totalWidth,
|
|
7096
|
+
jmLayout.totalHeight
|
|
7097
|
+
);
|
|
7098
|
+
renderJourneyMap(container, jmParsed, effectivePalette, theme === 'dark', {
|
|
7099
|
+
exportDims: { width: jmLayout.totalWidth, height: jmLayout.totalHeight },
|
|
7100
|
+
});
|
|
7101
|
+
return finalizeSvgExport(container, theme, effectivePalette);
|
|
7102
|
+
}
|
|
7103
|
+
|
|
7104
|
+
if (detectedType === 'cycle') {
|
|
7105
|
+
const { parseCycle } = await import('./cycle/parser');
|
|
7106
|
+
const { renderCycleForExport } = await import('./cycle/renderer');
|
|
7107
|
+
|
|
7108
|
+
const effectivePalette = await resolveExportPalette(theme, palette);
|
|
7109
|
+
const cycleParsed = parseCycle(content);
|
|
7110
|
+
if (cycleParsed.error || cycleParsed.nodes.length === 0) return '';
|
|
7111
|
+
|
|
7112
|
+
const container = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);
|
|
7113
|
+
renderCycleForExport(
|
|
7114
|
+
container,
|
|
7115
|
+
cycleParsed,
|
|
7116
|
+
effectivePalette,
|
|
7117
|
+
theme === 'dark',
|
|
7118
|
+
{ width: EXPORT_WIDTH, height: EXPORT_HEIGHT },
|
|
7119
|
+
viewState
|
|
7120
|
+
);
|
|
7121
|
+
return finalizeSvgExport(container, theme, effectivePalette);
|
|
7122
|
+
}
|
|
7123
|
+
|
|
7124
|
+
if (detectedType === 'pyramid') {
|
|
7125
|
+
const { parsePyramid } = await import('./pyramid/parser');
|
|
7126
|
+
const { renderPyramidForExport } = await import('./pyramid/renderer');
|
|
7127
|
+
|
|
7128
|
+
const effectivePalette = await resolveExportPalette(theme, palette);
|
|
7129
|
+
const pyramidParsed = parsePyramid(content);
|
|
7130
|
+
if (pyramidParsed.error || pyramidParsed.layers.length === 0) return '';
|
|
7131
|
+
|
|
7132
|
+
const container = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);
|
|
7133
|
+
renderPyramidForExport(
|
|
7134
|
+
container,
|
|
7135
|
+
pyramidParsed,
|
|
7136
|
+
effectivePalette,
|
|
7137
|
+
theme === 'dark',
|
|
7138
|
+
{ width: EXPORT_WIDTH, height: EXPORT_HEIGHT }
|
|
7139
|
+
);
|
|
7140
|
+
return finalizeSvgExport(container, theme, effectivePalette);
|
|
7141
|
+
}
|
|
7142
|
+
|
|
7044
7143
|
const parsed = parseVisualization(content, palette);
|
|
7045
7144
|
// Allow sequence diagrams through even if parseVisualization errors —
|
|
7046
7145
|
// sequence is parsed by its own dedicated parser (parseSequenceDgmo)
|
package/src/dgmo-router.ts
CHANGED
|
@@ -19,6 +19,10 @@ import { parseGantt } from './gantt/parser';
|
|
|
19
19
|
import { parseBoxesAndLines } from './boxes-and-lines/parser';
|
|
20
20
|
import { parseMindmap } from './mindmap/parser';
|
|
21
21
|
import { parseWireframe } from './wireframe/parser';
|
|
22
|
+
import { parseTechRadar } from './tech-radar/parser';
|
|
23
|
+
import { parseCycle } from './cycle/parser';
|
|
24
|
+
import { parseJourneyMap } from './journey-map/parser';
|
|
25
|
+
import { parsePyramid } from './pyramid/parser';
|
|
22
26
|
import { parseFirstLine } from './utils/parsing';
|
|
23
27
|
import { makeDgmoError, suggest } from './diagnostics';
|
|
24
28
|
import type { DgmoError } from './diagnostics';
|
|
@@ -137,6 +141,9 @@ const VISUALIZATION_TYPES = new Set([
|
|
|
137
141
|
'timeline',
|
|
138
142
|
'venn',
|
|
139
143
|
'quadrant',
|
|
144
|
+
'tech-radar',
|
|
145
|
+
'cycle',
|
|
146
|
+
'pyramid',
|
|
140
147
|
]);
|
|
141
148
|
const DIAGRAM_TYPES = new Set([
|
|
142
149
|
'sequence',
|
|
@@ -153,6 +160,7 @@ const DIAGRAM_TYPES = new Set([
|
|
|
153
160
|
'boxes-and-lines',
|
|
154
161
|
'mindmap',
|
|
155
162
|
'wireframe',
|
|
163
|
+
'journey-map',
|
|
156
164
|
]);
|
|
157
165
|
const EXTENDED_CHART_TYPES = new Set([
|
|
158
166
|
'scatter',
|
|
@@ -205,6 +213,63 @@ export function getAllChartTypes(): string[] {
|
|
|
205
213
|
return [...DATA_CHART_TYPES, ...VISUALIZATION_TYPES, ...DIAGRAM_TYPES];
|
|
206
214
|
}
|
|
207
215
|
|
|
216
|
+
/**
|
|
217
|
+
* Canonical descriptions for every supported chart type. Shared by the CLI
|
|
218
|
+
* `--chart-types` flag, the editor autocomplete popup, and the MCP
|
|
219
|
+
* `list_chart_types` tool so all three surfaces stay in sync.
|
|
220
|
+
*/
|
|
221
|
+
export const CHART_TYPE_DESCRIPTIONS: Record<string, string> = {
|
|
222
|
+
bar: 'Bar chart — categorical comparisons',
|
|
223
|
+
line: 'Line chart — trends over time; supports era bands (era start -> end Label (color)) for annotating named periods',
|
|
224
|
+
'multi-line':
|
|
225
|
+
'Multi-line chart — multiple series trends over time; supports era bands',
|
|
226
|
+
area: 'Area chart — filled line chart; supports era bands',
|
|
227
|
+
pie: 'Pie chart — part-to-whole proportions',
|
|
228
|
+
doughnut: 'Doughnut chart — ring-style pie chart',
|
|
229
|
+
radar: 'Radar chart — multi-dimensional metrics',
|
|
230
|
+
'polar-area': 'Polar area chart — radial bar chart',
|
|
231
|
+
'bar-stacked': 'Stacked bar chart — multi-series categorical',
|
|
232
|
+
scatter: 'Scatter plot — 2D data points or bubble chart',
|
|
233
|
+
sankey: 'Sankey diagram — flow/allocation visualization',
|
|
234
|
+
chord: 'Chord diagram — circular flow relationships',
|
|
235
|
+
function: 'Function plot — mathematical expressions',
|
|
236
|
+
heatmap: 'Heatmap — matrix intensity visualization',
|
|
237
|
+
funnel: 'Funnel chart — conversion pipeline',
|
|
238
|
+
slope: 'Slope chart — change between two periods',
|
|
239
|
+
wordcloud: 'Word cloud — term frequency visualization',
|
|
240
|
+
arc: 'Arc diagram — network relationships',
|
|
241
|
+
timeline: 'Timeline — events, eras, and date ranges',
|
|
242
|
+
venn: 'Venn diagram — set overlaps',
|
|
243
|
+
quadrant: 'Quadrant chart — 2x2 positioning matrix',
|
|
244
|
+
'tech-radar':
|
|
245
|
+
'Tech radar — technology adoption quadrants (adopt/trial/assess/hold)',
|
|
246
|
+
cycle:
|
|
247
|
+
'Cycle diagram — cyclical process visualization (PDCA, OODA, DevOps loops)',
|
|
248
|
+
sequence: 'Sequence diagram — message/interaction flows',
|
|
249
|
+
flowchart: 'Flowchart — decision trees and process flows',
|
|
250
|
+
class: 'Class diagram — UML class hierarchies',
|
|
251
|
+
er: 'ER diagram — database schemas and relationships',
|
|
252
|
+
org: 'Org chart — hierarchical tree structures',
|
|
253
|
+
kanban: 'Kanban board — task/workflow columns',
|
|
254
|
+
c4: 'C4 diagram — system architecture (context, container, component, deployment)',
|
|
255
|
+
state: 'State diagram — state machine / lifecycle transitions',
|
|
256
|
+
sitemap:
|
|
257
|
+
'Sitemap — navigable UI structure with pages, groups, and cross-link arrows',
|
|
258
|
+
infra:
|
|
259
|
+
'Infrastructure diagram — traffic flow with RPS computation, capacity modeling, and latency analysis',
|
|
260
|
+
gantt:
|
|
261
|
+
'Gantt chart — project scheduling with task dependencies and milestones',
|
|
262
|
+
'boxes-and-lines':
|
|
263
|
+
'Boxes and lines — general-purpose node-edge diagrams with nested groups, tags, and shape inference',
|
|
264
|
+
mindmap: 'Mindmap — radial hierarchy of ideas branching from a central topic',
|
|
265
|
+
wireframe:
|
|
266
|
+
'Wireframe — low-fidelity UI layout with panels, controls, and annotations',
|
|
267
|
+
'journey-map':
|
|
268
|
+
'Journey map — user experience flow with emotion scores, phases, and annotations',
|
|
269
|
+
pyramid:
|
|
270
|
+
'Pyramid — hierarchical layered pyramid (Maslow, DIKW, learning pyramid); inverted for funnel-of-learning style',
|
|
271
|
+
};
|
|
272
|
+
|
|
208
273
|
// ECharts-native types parsed by parseExtendedChart
|
|
209
274
|
const ECHART_TYPES = new Set([
|
|
210
275
|
'scatter',
|
|
@@ -234,6 +299,10 @@ const PARSE_DISPATCH = new Map<
|
|
|
234
299
|
['boxes-and-lines', (c) => parseBoxesAndLines(c)],
|
|
235
300
|
['mindmap', (c) => parseMindmap(c)],
|
|
236
301
|
['wireframe', (c) => parseWireframe(c)],
|
|
302
|
+
['tech-radar', (c) => parseTechRadar(c)],
|
|
303
|
+
['cycle', (c) => parseCycle(c)],
|
|
304
|
+
['journey-map', (c) => parseJourneyMap(c)],
|
|
305
|
+
['pyramid', (c) => parsePyramid(c)],
|
|
237
306
|
]);
|
|
238
307
|
|
|
239
308
|
/**
|
|
@@ -251,7 +320,10 @@ const ALL_KNOWN_TYPES = new Set([
|
|
|
251
320
|
* Parse DGMO content and return diagnostics without rendering.
|
|
252
321
|
* Useful for the CLI and editor to surface all errors before attempting render.
|
|
253
322
|
*/
|
|
254
|
-
export function parseDgmo(content: string): {
|
|
323
|
+
export function parseDgmo(content: string): {
|
|
324
|
+
diagnostics: DgmoError[];
|
|
325
|
+
chartType: string | null;
|
|
326
|
+
} {
|
|
255
327
|
const chartType = parseDgmoChartType(content);
|
|
256
328
|
|
|
257
329
|
if (!chartType) {
|
|
@@ -259,11 +331,14 @@ export function parseDgmo(content: string): { diagnostics: DgmoError[] } {
|
|
|
259
331
|
const colonDiag = detectColonChartType(content);
|
|
260
332
|
if (colonDiag) {
|
|
261
333
|
const fallback = parseVisualization(content).diagnostics;
|
|
262
|
-
return { diagnostics: [colonDiag, ...fallback] };
|
|
334
|
+
return { diagnostics: [colonDiag, ...fallback], chartType: null };
|
|
263
335
|
}
|
|
264
336
|
|
|
265
337
|
// No chart type detected — try visualization parser as fallback
|
|
266
|
-
return {
|
|
338
|
+
return {
|
|
339
|
+
diagnostics: parseVisualization(content).diagnostics,
|
|
340
|
+
chartType: null,
|
|
341
|
+
};
|
|
267
342
|
}
|
|
268
343
|
|
|
269
344
|
const directParser = PARSE_DISPATCH.get(chartType);
|
|
@@ -271,6 +346,7 @@ export function parseDgmo(content: string): { diagnostics: DgmoError[] } {
|
|
|
271
346
|
const result = directParser(content);
|
|
272
347
|
return {
|
|
273
348
|
diagnostics: [...result.diagnostics, ...detectEmptyContent(content)],
|
|
349
|
+
chartType,
|
|
274
350
|
};
|
|
275
351
|
}
|
|
276
352
|
|
|
@@ -278,12 +354,14 @@ export function parseDgmo(content: string): { diagnostics: DgmoError[] } {
|
|
|
278
354
|
const result = parseChart(content);
|
|
279
355
|
return {
|
|
280
356
|
diagnostics: [...result.diagnostics, ...detectEmptyContent(content)],
|
|
357
|
+
chartType,
|
|
281
358
|
};
|
|
282
359
|
}
|
|
283
360
|
if (ECHART_TYPES.has(chartType)) {
|
|
284
361
|
const result = parseExtendedChart(content);
|
|
285
362
|
return {
|
|
286
363
|
diagnostics: [...result.diagnostics, ...detectEmptyContent(content)],
|
|
364
|
+
chartType,
|
|
287
365
|
};
|
|
288
366
|
}
|
|
289
367
|
|
|
@@ -291,6 +369,7 @@ export function parseDgmo(content: string): { diagnostics: DgmoError[] } {
|
|
|
291
369
|
const result = parseVisualization(content);
|
|
292
370
|
return {
|
|
293
371
|
diagnostics: [...result.diagnostics, ...detectEmptyContent(content)],
|
|
372
|
+
chartType,
|
|
294
373
|
};
|
|
295
374
|
}
|
|
296
375
|
|
package/src/echarts.ts
CHANGED
|
@@ -150,6 +150,7 @@ import {
|
|
|
150
150
|
collectIndentedValues,
|
|
151
151
|
extractColor,
|
|
152
152
|
measureIndent,
|
|
153
|
+
normalizeNumericToken,
|
|
153
154
|
parseFirstLine,
|
|
154
155
|
parseSeriesNames,
|
|
155
156
|
} from './utils/parsing';
|
|
@@ -359,10 +360,11 @@ export function parseExtendedChart(
|
|
|
359
360
|
|
|
360
361
|
// Sankey/chord link syntax: Source -> Target Value (directed) or Source -- Target Value (undirected)
|
|
361
362
|
const arrowMatch = trimmed.match(
|
|
362
|
-
/^(.+?)\s*(->|--)\s*(.+?)\s+(\d+(
|
|
363
|
+
/^(.+?)\s*(->|--)\s*(.+?)\s+(-?[\d,_]+(?:\.[\d]+)?)\s*(?:\(([^)]+)\))?\s*$/
|
|
363
364
|
);
|
|
364
365
|
if (arrowMatch) {
|
|
365
|
-
const [, rawSource, arrow, rawTarget,
|
|
366
|
+
const [, rawSource, arrow, rawTarget, rawVal, rawLinkColor] = arrowMatch;
|
|
367
|
+
const val = normalizeNumericToken(rawVal) ?? rawVal;
|
|
366
368
|
const { label: source, color: sourceColor } = extractColor(
|
|
367
369
|
rawSource.trim(),
|
|
368
370
|
palette
|
|
@@ -409,7 +411,7 @@ export function parseExtendedChart(
|
|
|
409
411
|
// Parse "TargetName value (linkColor)" or "TargetName(nodeColor) value (linkColor)"
|
|
410
412
|
// Strip trailing (color) annotation before parseDataRowValues — it can't handle it
|
|
411
413
|
const valColorMatch = trimmed.match(
|
|
412
|
-
/(\d+(
|
|
414
|
+
/(-?[\d,_]+(?:\.[\d]+)?)\s*\(([^)]+)\)\s*$/
|
|
413
415
|
);
|
|
414
416
|
const strippedLine = valColorMatch
|
|
415
417
|
? trimmed.replace(/\s*\([^)]+\)\s*$/, '')
|
|
@@ -449,9 +451,10 @@ export function parseExtendedChart(
|
|
|
449
451
|
|
|
450
452
|
// Bare label at indent 0 (or any indent without a value) = new source node
|
|
451
453
|
const spaceIdx = trimmed.indexOf(' ');
|
|
454
|
+
const lastTok = trimmed.substring(trimmed.lastIndexOf(' ') + 1);
|
|
452
455
|
const hasNumericSuffix =
|
|
453
456
|
spaceIdx >= 0 &&
|
|
454
|
-
!isNaN(parseFloat(
|
|
457
|
+
!isNaN(parseFloat(normalizeNumericToken(lastTok) ?? lastTok));
|
|
455
458
|
if (!hasNumericSuffix) {
|
|
456
459
|
while (sankeyStack.length && sankeyStack.at(-1)!.indent >= indent) {
|
|
457
460
|
sankeyStack.pop();
|
|
@@ -1910,7 +1913,7 @@ function buildFunnelOption(
|
|
|
1910
1913
|
bottom: 20,
|
|
1911
1914
|
width: '60%',
|
|
1912
1915
|
sort: 'descending' as const,
|
|
1913
|
-
gap:
|
|
1916
|
+
gap: 0,
|
|
1914
1917
|
minSize: '8%',
|
|
1915
1918
|
};
|
|
1916
1919
|
|
package/src/editor/dgmo.grammar
CHANGED
|
@@ -30,7 +30,11 @@ contentPart {
|
|
|
30
30
|
Duration { $[0-9]+ ("." $[0-9]+)? ("min" | "bd" | "h" | "d" | "w" | "m" | "q" | "y" | "s") "?"? }
|
|
31
31
|
DateLiteral { $[0-9] $[0-9] $[0-9] $[0-9] "-" $[0-9] $[0-9] ("-" $[0-9] $[0-9])? }
|
|
32
32
|
Percentage { $[0-9]+ ("." $[0-9]+)? "%" }
|
|
33
|
-
Number {
|
|
33
|
+
Number {
|
|
34
|
+
$[0-9] $[0-9]? $[0-9]? ("," $[0-9] $[0-9] $[0-9])+ ("." $[0-9]+)? |
|
|
35
|
+
$[0-9]+ ("_" $[0-9]+)+ ("." $[0-9]+)? |
|
|
36
|
+
$[0-9]+ ("." $[0-9]+)?
|
|
37
|
+
}
|
|
34
38
|
|
|
35
39
|
SectionMarker { "==" }
|
|
36
40
|
Url { "http" "s"? "://" ![ \t\n|,)\]>]+ }
|
|
@@ -10,7 +10,7 @@ export const parser = LRParser.deserialize({
|
|
|
10
10
|
maxTerm: 40,
|
|
11
11
|
skippedNodes: [0],
|
|
12
12
|
repeatNodeCount: 2,
|
|
13
|
-
tokenData: "
|
|
13
|
+
tokenData: "<O~RxOX#oXY#tYZ$PZp#opq#tqt#oux#oxy$Uyz$tz{${{|%S|}%Z}!O%b!O!P#o!P!Q%q!Q![&b![!]/T!]!^#o!^!_/[!_!`/c!`!a/p!a!b/w!b!c#o!c!}0O!}#O3U#O#P#o#P#Q3Z#Q#R#o#R#S0O#S#T#o#T#[0O#[#]3b#]#o0O#o#p#o#p#q;b#q#r#o#r#s;i#s;'S#o;'S;=`;x<%lO#o~#tOq~~#yQv~XY#tpq#t~$UOw~~$]Qc~q~}!O$c#T#o$c~$fRyz$o}!O$c#T#o$c~$tOg~~${Od~q~~%SOn~q~~%ZOk~q~~%bOj~q~~%iPl~q~!`!a%l~%qOX~~%vPq~!P!Q%y~&OSV~OY%yZ;'S%y;'S;=`&[<%lO%y~&_P;=`<%l%y~&i]^~q~uv'b|}'g!O!P(d!Q![*T#R#S.o#U#V)_#W#X)e#[#])e#a#b)r#e#f)e#g#h)e#k#l)e#m#n)e~'gO]~~'jP!Q!['m~'pP!Q!['s~'vP!Q!['y~(OQ^~|}'g!O!P(U~(XP!Q![([~(aP^~!Q![([~(gP!Q![(j~(oY^~uv'b!Q![(j#U#V)_#W#X)e#[#])e#a#b)r#e#f)e#g#h)e#k#l)e#m#n)e~)bP#W#X)e~)jPZ~!a!b)m~)rOZ~~)wQZ~!a!b)m#]#^)}~*QP#b#c)e~*Y]^~uv'b|}'g!O!P(d!Q![+R#R#S.o#U#V)_#W#X)e#[#])e#a#b)r#e#f)e#g#h)e#k#l)e#m#n)e~+W]^~uv'b|}'g!O!P(d!Q![,P#R#S.o#U#V)_#W#X)e#[#])e#a#b)r#e#f)e#g#h)e#k#l)e#m#n)e~,U]^~uv'b}!O,}!O!P(d!Q![-t#R#S.o#U#V)_#W#X)e#[#])e#a#b)r#e#f)e#g#h)e#k#l)e#m#n)e~-QP!Q![-T~-WP!Q![-Z~-`P[~}!O-c~-fP!Q![-i~-lP!Q![-o~-tO[~~-y[^~uv'b!O!P(d!Q![-t#R#S.o#U#V)_#W#X)e#[#])e#a#b)r#e#f)e#g#h)e#k#l)e#m#n)e~.rP!Q![.u~.zR^~!O!P(U!Q![.u#R#S.o~/[Oi~q~~/cOe~q~~/hPq~!_!`/k~/pO_~~/wOf~q~~0OOo~q~~0V_p~q~qr1Ust1Uvw1Uwx1U{|1U}!O2Y!O!P1U!P!Q1U!Q![1U!_!`1U!a!b1U!b!c1U!c!}1U#R#S1U#T#o1U~1Z_p~qr1Ust1Uvw1Uwx1U{|1U}!O2Y!O!P1U!P!Q1U!Q![1U!_!`1U!a!b1U!b!c1U!c!}1U#R#S1U#T#o1U~2]]qr1Ust1Uvw1Uwx1U{|1U!O!P1U!P!Q1U!_!`1U!a!b1U!b!c1U!c!}1U#R#S1U#T#o1U~3ZOa~~3bOb~q~~3iap~q~qr1Ust1Uvw1Uwx1U{|1U}!O2Y!O!P1U!P!Q1U!Q![1U!_!`1U!a!b1U!b!c1U!c!}1U#R#S1U#T#h1U#h#i4n#i#o1U~4sap~qr1Ust1Uvw1Uwx1U{|1U}!O2Y!O!P1U!P!Q1U!Q![1U!_!`1U!a!b1U!b!c1U!c!}1U#R#S1U#T#h1U#h#i5x#i#o1U~5}ap~qr1Ust1Uvw1Uwx1U{|1U}!O2Y!O!P1U!P!Q1U!Q![1U!_!`1U!a!b1U!b!c1U!c!}1U#R#S1U#T#d1U#d#e7S#e#o1U~7Xbp~qr1Ust1Uvw1Uwx1U{|1U}!O2Y!O!P1U!P!Q1U!Q![1U![!]8a!_!`1U!a!b1U!b!c1U!c!}1U#R#S1U#T#g1U#g#h:Z#h#o1U~8dP!P!Q8g~8jP!P!Q8m~8pYOX9`Zp9`qy9`z|9`}!`9`!a#P9`#Q#p9`#q;'S9`;'S;=`:T<%lO9`~9eY`~OX9`Zp9`qy9`z|9`}!`9`!a#P9`#Q#p9`#q;'S9`;'S;=`:T<%lO9`~:WP;=`<%l9`~:``p~qr1Ust1Uvw1Uwx1U{|1U}!O2Y!O!P1U!P!Q1U!Q![1U![!]8a!_!`1U!a!b1U!b!c1U!c!}1U#R#S1U#T#o1U~;iOh~q~~;pPm~q~!`!a;s~;xOY~~;{P;=`<%l#o",
|
|
14
14
|
tokenizers: [0],
|
|
15
15
|
topRules: {"Document":[0,6]},
|
|
16
16
|
specialized: [{term: 32, get: (value, stack) => (specializeKeyword(value, stack) << 1), external: specializeKeyword}],
|
package/src/editor/keywords.ts
CHANGED
|
@@ -14,6 +14,10 @@ export const CHART_TYPES = new Set([
|
|
|
14
14
|
'gantt',
|
|
15
15
|
'boxes-and-lines',
|
|
16
16
|
'wireframe',
|
|
17
|
+
'tech-radar',
|
|
18
|
+
'mindmap',
|
|
19
|
+
'journey-map',
|
|
20
|
+
'pyramid',
|
|
17
21
|
// Data chart types
|
|
18
22
|
'bar',
|
|
19
23
|
'line',
|
|
@@ -65,6 +69,10 @@ export const METADATA_KEYS = new Set([
|
|
|
65
69
|
'top-left',
|
|
66
70
|
'bottom-right',
|
|
67
71
|
'bottom-left',
|
|
72
|
+
// Tech-radar pipe metadata
|
|
73
|
+
'quadrant',
|
|
74
|
+
'ring',
|
|
75
|
+
'trend',
|
|
68
76
|
]);
|
|
69
77
|
|
|
70
78
|
/** Tag declaration keyword. */
|
|
@@ -82,6 +90,8 @@ export const DIRECTIVE_KEYWORDS = new Set([
|
|
|
82
90
|
'critical-path',
|
|
83
91
|
'no-dependencies',
|
|
84
92
|
'sort',
|
|
93
|
+
// Tech-radar
|
|
94
|
+
'rings',
|
|
85
95
|
// Tags
|
|
86
96
|
'tags',
|
|
87
97
|
'import',
|
|
@@ -149,6 +159,8 @@ export const DIRECTIVE_KEYWORDS = new Set([
|
|
|
149
159
|
// Layout
|
|
150
160
|
'direction-tb',
|
|
151
161
|
'direction-lr',
|
|
162
|
+
// Pyramid
|
|
163
|
+
'inverted',
|
|
152
164
|
// Data chart metadata
|
|
153
165
|
'title',
|
|
154
166
|
'series',
|
|
@@ -194,6 +206,11 @@ export const STATUS_KEYWORDS = new Set([
|
|
|
194
206
|
'in-progress',
|
|
195
207
|
'backlog',
|
|
196
208
|
'ready',
|
|
209
|
+
// Tech-radar trend values
|
|
210
|
+
'new',
|
|
211
|
+
'up',
|
|
212
|
+
'down',
|
|
213
|
+
'stable',
|
|
197
214
|
]);
|
|
198
215
|
|
|
199
216
|
/** Modifier keywords — adjust declarations. */
|
package/src/gantt/parser.ts
CHANGED
|
@@ -744,7 +744,7 @@ export function parseGantt(
|
|
|
744
744
|
|
|
745
745
|
// First segment could be empty (just `[Group]`) or have metadata
|
|
746
746
|
let metadata: Record<string, string> = {};
|
|
747
|
-
|
|
747
|
+
const color: string | null = null;
|
|
748
748
|
|
|
749
749
|
const pipeWarn = () => warn(lineNumber, MULTIPLE_PIPE_ERROR);
|
|
750
750
|
if (segments.length > 0 && segments[0].trim()) {
|
|
@@ -758,14 +758,8 @@ export function parseGantt(
|
|
|
758
758
|
);
|
|
759
759
|
}
|
|
760
760
|
|
|
761
|
-
// Extract color from group name if present
|
|
762
|
-
const nameExtracted = extractColor(groupMatch[1], palette);
|
|
763
|
-
if (nameExtracted.color) {
|
|
764
|
-
color = nameExtracted.color;
|
|
765
|
-
}
|
|
766
|
-
|
|
767
761
|
const group: GanttGroup = {
|
|
768
|
-
name:
|
|
762
|
+
name: groupMatch[1],
|
|
769
763
|
color,
|
|
770
764
|
metadata,
|
|
771
765
|
lineNumber,
|
|
@@ -5,7 +5,6 @@ import { makeDgmoError, formatDgmoError, suggest } from '../diagnostics';
|
|
|
5
5
|
import { parseInArrowLabel, matchColorParens } from '../utils/arrows';
|
|
6
6
|
import {
|
|
7
7
|
measureIndent,
|
|
8
|
-
extractColor,
|
|
9
8
|
inferArrowColor,
|
|
10
9
|
parseFirstLine,
|
|
11
10
|
OPTION_NOCOLON_RE,
|
|
@@ -32,55 +31,50 @@ interface NodeRef {
|
|
|
32
31
|
* Try to parse a node reference from a text fragment.
|
|
33
32
|
* Order matters: subroutine & document before process.
|
|
34
33
|
*/
|
|
35
|
-
function parseNodeRef(text: string
|
|
34
|
+
function parseNodeRef(text: string): NodeRef | null {
|
|
36
35
|
const t = text.trim();
|
|
37
36
|
if (!t) return null;
|
|
38
37
|
|
|
39
38
|
// Subroutine: [[Label]]
|
|
40
39
|
let m = t.match(/^\[\[([^\]]+)\]\]$/);
|
|
41
40
|
if (m) {
|
|
42
|
-
const
|
|
43
|
-
return {
|
|
44
|
-
id: nodeId('subroutine', label),
|
|
45
|
-
label,
|
|
46
|
-
shape: 'subroutine',
|
|
47
|
-
color,
|
|
48
|
-
};
|
|
41
|
+
const label = m[1].trim();
|
|
42
|
+
return { id: nodeId('subroutine', label), label, shape: 'subroutine' };
|
|
49
43
|
}
|
|
50
44
|
|
|
51
45
|
// Document: [Label~]
|
|
52
46
|
m = t.match(/^\[([^\]]+)~\]$/);
|
|
53
47
|
if (m) {
|
|
54
|
-
const
|
|
55
|
-
return { id: nodeId('document', label), label, shape: 'document'
|
|
48
|
+
const label = m[1].trim();
|
|
49
|
+
return { id: nodeId('document', label), label, shape: 'document' };
|
|
56
50
|
}
|
|
57
51
|
|
|
58
52
|
// Process: [Label]
|
|
59
53
|
m = t.match(/^\[([^\]]+)\]$/);
|
|
60
54
|
if (m) {
|
|
61
|
-
const
|
|
62
|
-
return { id: nodeId('process', label), label, shape: 'process'
|
|
55
|
+
const label = m[1].trim();
|
|
56
|
+
return { id: nodeId('process', label), label, shape: 'process' };
|
|
63
57
|
}
|
|
64
58
|
|
|
65
59
|
// Terminal: (Label) — use .+ (greedy) so (Label(color)) matches outermost parens
|
|
66
60
|
m = t.match(/^\((.+)\)$/);
|
|
67
61
|
if (m) {
|
|
68
|
-
const
|
|
69
|
-
return { id: nodeId('terminal', label), label, shape: 'terminal'
|
|
62
|
+
const label = m[1].trim();
|
|
63
|
+
return { id: nodeId('terminal', label), label, shape: 'terminal' };
|
|
70
64
|
}
|
|
71
65
|
|
|
72
66
|
// Decision: <Label>
|
|
73
67
|
m = t.match(/^<([^>]+)>$/);
|
|
74
68
|
if (m) {
|
|
75
|
-
const
|
|
76
|
-
return { id: nodeId('decision', label), label, shape: 'decision'
|
|
69
|
+
const label = m[1].trim();
|
|
70
|
+
return { id: nodeId('decision', label), label, shape: 'decision' };
|
|
77
71
|
}
|
|
78
72
|
|
|
79
73
|
// I/O: /Label/
|
|
80
74
|
m = t.match(/^\/([^/]+)\/$/);
|
|
81
75
|
if (m) {
|
|
82
|
-
const
|
|
83
|
-
return { id: nodeId('io', label), label, shape: 'io'
|
|
76
|
+
const label = m[1].trim();
|
|
77
|
+
return { id: nodeId('io', label), label, shape: 'io' };
|
|
84
78
|
}
|
|
85
79
|
|
|
86
80
|
return null;
|
|
@@ -370,7 +364,7 @@ export function parseFlowchart(
|
|
|
370
364
|
|
|
371
365
|
if (segments.length === 1) {
|
|
372
366
|
// Single node reference, no arrows
|
|
373
|
-
const ref = parseNodeRef(segments[0]
|
|
367
|
+
const ref = parseNodeRef(segments[0]);
|
|
374
368
|
if (ref) {
|
|
375
369
|
const node = getOrCreateNode(ref, lineNumber);
|
|
376
370
|
indentStack.push({ nodeId: node.id, indent });
|
|
@@ -398,7 +392,7 @@ export function parseFlowchart(
|
|
|
398
392
|
}
|
|
399
393
|
|
|
400
394
|
// This is a node text segment
|
|
401
|
-
const ref = parseNodeRef(seg
|
|
395
|
+
const ref = parseNodeRef(seg);
|
|
402
396
|
if (!ref) continue;
|
|
403
397
|
|
|
404
398
|
const node = getOrCreateNode(ref, lineNumber);
|
|
@@ -5,7 +5,6 @@ import { makeDgmoError, formatDgmoError, suggest } from '../diagnostics';
|
|
|
5
5
|
import { parseInArrowLabel, matchColorParens } from '../utils/arrows';
|
|
6
6
|
import {
|
|
7
7
|
measureIndent,
|
|
8
|
-
extractColor,
|
|
9
8
|
parseFirstLine,
|
|
10
9
|
OPTION_NOCOLON_RE,
|
|
11
10
|
ALL_CHART_TYPES,
|
|
@@ -173,10 +172,7 @@ interface NodeRef {
|
|
|
173
172
|
color?: string;
|
|
174
173
|
}
|
|
175
174
|
|
|
176
|
-
function parseStateNodeRef(
|
|
177
|
-
text: string,
|
|
178
|
-
palette?: PaletteColors
|
|
179
|
-
): NodeRef | null {
|
|
175
|
+
function parseStateNodeRef(text: string): NodeRef | null {
|
|
180
176
|
const t = text.trim();
|
|
181
177
|
if (!t) return null;
|
|
182
178
|
|
|
@@ -189,14 +185,13 @@ function parseStateNodeRef(
|
|
|
189
185
|
};
|
|
190
186
|
}
|
|
191
187
|
|
|
192
|
-
// State: bare text
|
|
193
|
-
const
|
|
188
|
+
// State: bare text
|
|
189
|
+
const label = t;
|
|
194
190
|
if (!label) return null;
|
|
195
191
|
return {
|
|
196
192
|
id: `state:${label.toLowerCase().trim()}`,
|
|
197
193
|
label,
|
|
198
194
|
shape: 'state',
|
|
199
|
-
color,
|
|
200
195
|
};
|
|
201
196
|
}
|
|
202
197
|
|
|
@@ -380,7 +375,7 @@ export function parseState(
|
|
|
380
375
|
|
|
381
376
|
if (segments.length === 1) {
|
|
382
377
|
// Single state reference, no arrows — this is the canonical definition
|
|
383
|
-
const ref = parseStateNodeRef(segments[0]
|
|
378
|
+
const ref = parseStateNodeRef(segments[0]);
|
|
384
379
|
if (ref) {
|
|
385
380
|
const node = getOrCreateNode(ref, lineNumber);
|
|
386
381
|
// Standalone heading is the "definition" — update lineNumber so
|
|
@@ -409,7 +404,7 @@ export function parseState(
|
|
|
409
404
|
continue;
|
|
410
405
|
}
|
|
411
406
|
|
|
412
|
-
const ref = parseStateNodeRef(seg
|
|
407
|
+
const ref = parseStateNodeRef(seg);
|
|
413
408
|
if (!ref) continue;
|
|
414
409
|
|
|
415
410
|
const node = getOrCreateNode(ref, lineNumber);
|