From cc476d59d1440d919f5a32ab4a393fd93138a926 Mon Sep 17 00:00:00 2001 From: omkarht Date: Fri, 27 Jun 2025 13:42:19 +0530 Subject: [PATCH 01/23] 6637-Add new participant types to Sequence Diagrams --- .../sequence/parser/sequenceDiagram.jison | 25 + .../src/diagrams/sequence/sequenceDb.ts | 11 + .../src/diagrams/sequence/sequenceRenderer.ts | 12 +- .../mermaid/src/diagrams/sequence/styles.js | 6 + .../mermaid/src/diagrams/sequence/svgDraw.js | 615 ++++++++++++++++++ 5 files changed, 666 insertions(+), 3 deletions(-) diff --git a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison index d2e81df5f..4b5a82851 100644 --- a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison +++ b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison @@ -31,6 +31,12 @@ "box" { this.begin('LINE'); return 'box'; } "participant" { this.begin('ID'); return 'participant'; } "actor" { this.begin('ID'); return 'participant_actor'; } +"boundary" { this.begin('ID'); return 'participant_boundary'; } +"control" { this.begin('ID'); return 'participant_control'; } +"entity" { this.begin('ID'); return 'participant_entity'; } +"database" { this.begin('ID'); return 'participant_database'; } +"collections" { this.begin('ID'); return 'participant_collections'; } +"queue" { this.begin('ID'); return 'participant_queue'; } "create" return 'create'; "destroy" { this.begin('ID'); return 'destroy'; } [^\<->\->:\n,;]+?([\-]*[^\<->\->:\n,;]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; } @@ -231,6 +237,25 @@ participant_statement | 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} | 'participant_actor' actor 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant'; $$=$2;} | 'destroy' actor 'NEWLINE' {$2.type='destroyParticipant'; $$=$2;} + + | 'participant_boundary' actor 'AS' restOfLine 'NEWLINE' {$2.draw='boundary'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} + | 'participant_boundary' actor 'NEWLINE' {$2.draw='boundary'; $2.type='addParticipant'; $$=$2;} + + | 'participant_control' actor 'AS' restOfLine 'NEWLINE' {$2.draw='control'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} + | 'participant_control' actor 'NEWLINE' {$2.draw='control'; $2.type='addParticipant'; $$=$2;} + + | 'participant_entity' actor 'AS' restOfLine 'NEWLINE' {$2.draw='entity'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} + | 'participant_entity' actor 'NEWLINE' {$2.draw='entity'; $2.type='addParticipant'; $$=$2;} + + | 'participant_database' actor 'AS' restOfLine 'NEWLINE' {$2.draw='database'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} + | 'participant_database' actor 'NEWLINE' {$2.draw='database'; $2.type='addParticipant'; $$=$2;} + + | 'participant_collections' actor 'AS' restOfLine 'NEWLINE' {$2.draw='collections'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} + | 'participant_collections' actor 'NEWLINE' {$2.draw='collections'; $2.type='addParticipant'; $$=$2;} + + | 'participant_queue' actor 'AS' restOfLine 'NEWLINE' {$2.draw='queue'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} + | 'participant_queue' actor 'NEWLINE' {$2.draw='queue'; $2.type='addParticipant'; $$=$2;} + ; note_statement diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDb.ts b/packages/mermaid/src/diagrams/sequence/sequenceDb.ts index c6b44dac0..d8421b84d 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDb.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceDb.ts @@ -75,6 +75,17 @@ const PLACEMENT = { OVER: 2, } as const; +export const PARTICIPANT_TYPE = { + ACTOR: 'actor', + BOUNDARY: 'boundary', + COLLECTIONS: 'collections', + CONTROL: 'control', + DATABASE: 'database', + ENTITY: 'entity', + PARTICIPANT: 'participant', + QUEUE: 'queue', +} as const; + export class SequenceDB implements DiagramDB { private readonly state = new ImperativeState(() => ({ prevActor: undefined, diff --git a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts index cfba92b79..8b4842fc1 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts @@ -10,6 +10,7 @@ import assignWithDepth from '../../assignWithDepth.js'; import utils from '../../utils.js'; import { configureSvgSize } from '../../setupGraphViewbox.js'; import type { Diagram } from '../../Diagram.js'; +import { PARTICIPANT_TYPE } from './sequenceDb.js'; let conf = {}; @@ -724,11 +725,14 @@ function adjustCreatedDestroyedData( msgModel.startx = msgModel.startx - adjustment; } } + const actorArray = [PARTICIPANT_TYPE.ACTOR, PARTICIPANT_TYPE.CONTROL, PARTICIPANT_TYPE.ENTITY]; // if it is a create message if (createdActors.get(msg.to) == index) { const actor = actors.get(msg.to); - const adjustment = actor.type == 'actor' ? ACTOR_TYPE_WIDTH / 2 + 3 : actor.width / 2 + 3; + const adjustment = actorArray.includes(actor.type) + ? ACTOR_TYPE_WIDTH / 2 + 3 + : actor.width / 2 + 3; receiverAdjustment(actor, adjustment); actor.starty = lineStartY - actor.height / 2; bounds.bumpVerticalPos(actor.height / 2); @@ -737,7 +741,7 @@ function adjustCreatedDestroyedData( else if (destroyedActors.get(msg.from) == index) { const actor = actors.get(msg.from); if (conf.mirrorActors) { - const adjustment = actor.type == 'actor' ? ACTOR_TYPE_WIDTH / 2 : actor.width / 2; + const adjustment = actorArray.includes(actor.type) ? ACTOR_TYPE_WIDTH / 2 : actor.width / 2; senderAdjustment(actor, adjustment); } actor.stopy = lineStartY - actor.height / 2; @@ -747,7 +751,9 @@ function adjustCreatedDestroyedData( else if (destroyedActors.get(msg.to) == index) { const actor = actors.get(msg.to); if (conf.mirrorActors) { - const adjustment = actor.type == 'actor' ? ACTOR_TYPE_WIDTH / 2 + 3 : actor.width / 2 + 3; + const adjustment = actorArray.includes(actor.type) + ? ACTOR_TYPE_WIDTH / 2 + 3 + : actor.width / 2 + 3; receiverAdjustment(actor, adjustment); } actor.stopy = lineStartY - actor.height / 2; diff --git a/packages/mermaid/src/diagrams/sequence/styles.js b/packages/mermaid/src/diagrams/sequence/styles.js index 5c36b4ed1..3cee9d3dc 100644 --- a/packages/mermaid/src/diagrams/sequence/styles.js +++ b/packages/mermaid/src/diagrams/sequence/styles.js @@ -12,6 +12,11 @@ const getStyles = (options) => .actor-line { stroke: ${options.actorLineColor}; } + + .innerArc { + stroke-width: 1.5; + stroke-dasharray: none; + } .messageLine0 { stroke-width: 1.5; @@ -115,6 +120,7 @@ const getStyles = (options) => fill: ${options.actorBkg}; stroke-width: 2px; } + `; export default getStyles; diff --git a/packages/mermaid/src/diagrams/sequence/svgDraw.js b/packages/mermaid/src/diagrams/sequence/svgDraw.js index c681c9491..3af3d3cf0 100644 --- a/packages/mermaid/src/diagrams/sequence/svgDraw.js +++ b/packages/mermaid/src/diagrams/sequence/svgDraw.js @@ -3,6 +3,8 @@ import * as svgDrawCommon from '../common/svgDrawCommon.js'; import { ZERO_WIDTH_SPACE, parseFontSize } from '../../utils.js'; import { sanitizeUrl } from '@braintree/sanitize-url'; import * as configApi from '../../config.js'; +import { createInnerCylinderPathD } from '../../rendering-util/rendering-elements/shapes/tiltedCylinder.js'; +import { createCylinderPathD } from '../../rendering-util/rendering-elements/shapes/cylinder.js'; export const ACTOR_TYPE_WIDTH = 18 * 2; const TOP_ACTOR_CLASS = 'actor-top'; @@ -411,6 +413,607 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) { return height; }; +/** + * Draws an actor in the diagram with the attached line + * + * @param {any} elem - The diagram we'll draw to. + * @param {any} actor - The actor to draw. + * @param {any} conf - DrawText implementation discriminator object + * @param {boolean} isFooter - If the actor is the footer one + */ +const drawActorTypeCollections = function (elem, actor, conf, isFooter) { + const actorY = isFooter ? actor.stopy : actor.starty; + const center = actor.x + actor.width / 2; + const centerY = actorY + actor.height; + + const boxplusLineGroup = elem.append('g').lower(); + var g = boxplusLineGroup; + + if (!isFooter) { + actorCnt++; + if (Object.keys(actor.links || {}).length && !conf.forceMenus) { + g.attr('onclick', popupMenuToggle(`actor${actorCnt}_popup`)).attr('cursor', 'pointer'); + } + g.append('line') + .attr('id', 'actor' + actorCnt) + .attr('x1', center) + .attr('y1', centerY) + .attr('x2', center) + .attr('y2', 2000) + .attr('class', 'actor-line 200') + .attr('stroke-width', '0.5px') + .attr('stroke', '#999') + .attr('name', actor.name); + + g = boxplusLineGroup.append('g'); + actor.actorCnt = actorCnt; + + if (actor.links != null) { + g.attr('id', 'root-' + actorCnt); + } + } + + const rect = svgDrawCommon.getNoteRect(); + var cssclass = 'actor'; + if (actor.properties?.class) { + cssclass = actor.properties.class; + } else { + rect.fill = '#eaeaea'; + } + if (isFooter) { + cssclass += ` ${BOTTOM_ACTOR_CLASS}`; + } else { + cssclass += ` ${TOP_ACTOR_CLASS}`; + } + rect.x = actor.x; + rect.y = actorY; + rect.width = actor.width; + rect.height = actor.height; + rect.class = cssclass; + // rect.rx = 3; + // rect.ry = 3; + rect.name = actor.name; + + // 🔹 DRAW STACKED RECTANGLES + const offset = 6; + const shadowRect = { + ...rect, + x: rect.x + offset, + y: rect.y + (isFooter ? +offset : -offset), + class: 'actor', + }; + drawRect(g, shadowRect); + const rectElem = drawRect(g, rect); // draw main rectangle on top + actor.rectData = rect; + + if (actor.properties?.icon) { + const iconSrc = actor.properties.icon.trim(); + if (iconSrc.charAt(0) === '@') { + svgDrawCommon.drawEmbeddedImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc.substr(1)); + } else { + svgDrawCommon.drawImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc); + } + } + + _drawTextCandidateFunc(conf, hasKatex(actor.description))( + actor.description, + g, + rect.x, + rect.y, + rect.width, + rect.height, + { class: `actor ${ACTOR_BOX_CLASS}` }, + conf + ); + + let height = actor.height; + if (rectElem.node) { + const bounds = rectElem.node().getBBox(); + actor.height = bounds.height; + height = bounds.height; + } + + return height; +}; + +const drawActorTypeQueue = function (elem, actor, conf, isFooter) { + const actorY = isFooter ? actor.stopy : actor.starty; + const center = actor.x + actor.width / 2; + const centerY = actorY + actor.height; + + const boxplusLineGroup = elem.append('g').lower(); + let g = boxplusLineGroup; + + if (!isFooter) { + actorCnt++; + if (Object.keys(actor.links || {}).length && !conf.forceMenus) { + g.attr('onclick', popupMenuToggle(`actor${actorCnt}_popup`)).attr('cursor', 'pointer'); + } + g.append('line') + .attr('id', 'actor' + actorCnt) + .attr('x1', center) + .attr('y1', centerY) + .attr('x2', center) + .attr('y2', 2000) + .attr('class', 'actor-line 200') + .attr('stroke-width', '0.5px') + .attr('stroke', '#999') + .attr('name', actor.name); + + g = boxplusLineGroup.append('g'); + actor.actorCnt = actorCnt; + + if (actor.links != null) { + g.attr('id', 'root-' + actorCnt); + } + } + + const rect = svgDrawCommon.getNoteRect(); + let cssclass = 'actor'; + if (actor.properties?.class) { + cssclass = actor.properties.class; + } else { + rect.fill = '#eaeaea'; + } + + if (isFooter) { + cssclass += ` ${BOTTOM_ACTOR_CLASS}`; + } else { + cssclass += ` ${TOP_ACTOR_CLASS}`; + } + + rect.x = actor.x; + rect.y = actorY; + rect.width = actor.width; + rect.height = actor.height; + rect.class = cssclass; + rect.name = actor.name; + + // Cylinder dimensions + const ry = rect.height / 2; + const rx = ry / (2.5 + rect.height / 50); + + // Cylinder base group + const cylinderGroup = g.append('g'); + const cylinderArc = g.append('g'); + + // Main cylinder body + cylinderGroup + .append('path') + .attr( + 'd', + ` + M ${rect.x},${rect.y + ry} + a ${rx},${ry} 0 0 0 0,${rect.height} + h ${rect.width - 2 * rx} + a ${rx},${ry} 0 0 0 0,-${rect.height} + Z + ` + ) + .attr('class', cssclass); + cylinderArc + .append('path') + .attr('d', createInnerCylinderPathD(rect.x - rx, rect.y + ry, rect.width, rect.height, rx, ry)) + .attr('stroke', '#666') + .attr('stroke-width', '1px') + .attr('class', cssclass); + + if (!isFooter) { + cylinderGroup.attr('transform', `translate(${rx}, ${-(rect.height / 2) - rect.y})`); + cylinderArc.attr('transform', `translate(${rect.width / 2}, ${rect.height / 2})`); + } else { + cylinderGroup.attr('transform', `translate(${rx}, ${-(rect.height / 2)})`); + cylinderArc.attr('transform', `translate(${rect.width / 2}, ${actorY + rect.height / 2})`); + } + + actor.rectData = rect; + + if (actor.properties?.icon) { + const iconSrc = actor.properties.icon.trim(); + const iconX = rect.x + rect.width - 20; + const iconY = rect.y + 10; + if (iconSrc.charAt(0) === '@') { + svgDrawCommon.drawEmbeddedImage(g, iconX, iconY, iconSrc.substr(1)); + } else { + svgDrawCommon.drawImage(g, iconX, iconY, iconSrc); + } + } + + _drawTextCandidateFunc(conf, hasKatex(actor.description))( + actor.description, + g, + rect.x, + rect.y, + rect.width, + rect.height, + { class: `actor ${ACTOR_BOX_CLASS}` }, + conf + ); + + let height = actor.height; + const lastPath = cylinderGroup.select('path:last-child'); + if (lastPath.node()) { + const bounds = lastPath.node().getBBox(); + actor.height = bounds.height; + height = bounds.height; + } + + return height; +}; + +const drawActorTypeControl = function (elem, actor, conf, isFooter) { + const actorY = isFooter ? actor.stopy : actor.starty; + const center = actor.x + actor.width / 2; + const centerY = actorY + 75; + + const line = elem.append('g').lower(); + + if (!isFooter) { + actorCnt++; + line + .append('line') + .attr('id', 'actor' + actorCnt) + .attr('x1', center) + .attr('y1', centerY) + .attr('x2', center) + .attr('y2', 2000) + .attr('class', 'actor-line 200') + .attr('stroke-width', '0.5px') + .attr('stroke', '#999') + .attr('name', actor.name); + + actor.actorCnt = actorCnt; + } + const actElem = elem.append('g'); + let cssClass = ACTOR_MAN_FIGURE_CLASS; + if (isFooter) { + cssClass += ` ${BOTTOM_ACTOR_CLASS}`; + } else { + cssClass += ` ${TOP_ACTOR_CLASS}`; + } + actElem.attr('class', cssClass); + actElem.attr('name', actor.name); + + const rect = svgDrawCommon.getNoteRect(); + rect.x = actor.x; + rect.y = actorY; + rect.fill = '#eaeaea'; + rect.width = actor.width; + rect.height = actor.height; + rect.class = 'actor'; + + const cx = actor.x + actor.width / 2; + const cy = actorY + 30; + const r = 18; + + actElem + .append('defs') + .append('marker') + .attr('id', 'filled-head-control') + .attr('refX', 15.5) + .attr('refY', 7) + .attr('markerWidth', 20) + .attr('markerHeight', 28) + .attr('orient', '180') + .append('path') + .attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z'); + + // Draw the base circle + actElem + .append('circle') + .attr('cx', cx) + .attr('cy', cy) + .attr('r', r) + .attr('fill', '#eaeaf7') + .attr('stroke', '#666') + .attr('stroke-width', 1.2); + + // Draw looping arrow as arc path + actElem + .append('path') + .attr('d', `M ${cx},${cy - r}`) + .attr('stroke-width', 1.5) + .attr('marker-end', 'url(#filled-head-control)'); + + const bounds = actElem.node().getBBox(); + actor.height = bounds.height + conf.boxTextMargin; + + _drawTextCandidateFunc(conf, hasKatex(actor.description))( + actor.description, + actElem, + rect.x, + rect.y + (!isFooter ? 30 : 40), + rect.width, + rect.height, + { class: `actor ${ACTOR_MAN_FIGURE_CLASS}` }, + conf + ); + + return actor.height; +}; + +const drawActorTypeEntity = function (elem, actor, conf, isFooter) { + const actorY = isFooter ? actor.stopy : actor.starty; + const center = actor.x + actor.width / 2; + const centerY = actorY + 75; + + const line = elem.append('g').lower(); + + const actElem = elem.append('g'); + let cssClass = ACTOR_MAN_FIGURE_CLASS; + if (isFooter) { + cssClass += ` ${BOTTOM_ACTOR_CLASS}`; + } else { + cssClass += ` ${TOP_ACTOR_CLASS}`; + } + actElem.attr('class', cssClass); + actElem.attr('name', actor.name); + + const rect = svgDrawCommon.getNoteRect(); + rect.x = actor.x; + rect.y = actorY; + rect.fill = '#eaeaea'; + rect.width = actor.width; + rect.height = actor.height; + rect.class = 'actor'; + + const cx = actor.x + actor.width / 2; + const cy = actorY + 25; + const r = 18; + + actElem + .append('circle') + .attr('cx', cx) + .attr('cy', cy) + .attr('r', r) + .attr('width', actor.width) + .attr('height', actor.height); + + actElem + .append('line') + .attr('x1', cx - r) + .attr('x2', cx + r) + .attr('y1', cy + r) + .attr('y2', cy + r) + .attr('stroke', '#333') + .attr('stroke-width', 2); + + const boundBox = actElem.node().getBBox(); + actor.height = boundBox.height + conf.boxTextMargin; + + if (!isFooter) { + actorCnt++; + line + .append('line') + .attr('id', 'actor' + actorCnt) + .attr('x1', center) + .attr('y1', centerY) + .attr('x2', center) + .attr('y2', 2000) + .attr('class', 'actor-line 200') + .attr('stroke-width', '0.5px') + .attr('stroke', '#999') + .attr('name', actor.name); + + actor.actorCnt = actorCnt; + } + + _drawTextCandidateFunc(conf, hasKatex(actor.description))( + actor.description, + actElem, + rect.x, + rect.y + (!isFooter ? (cy + r - actorY) / 2 : (cy - actorY) / 2 + r + 2), + rect.width, + rect.height, + { class: `actor ${ACTOR_MAN_FIGURE_CLASS}` }, + conf + ); + + if (!isFooter) { + actElem.attr('transform', `translate(${0}, ${r / 2})`); + } else { + actElem.attr('transform', `translate(${0}, ${r / 2})`); + } + + return actor.height; +}; + +const drawActorTypeDatabase = function (elem, actor, conf, isFooter) { + const actorY = isFooter ? actor.stopy : actor.starty; + const center = actor.x + actor.width / 2; + const centerY = actorY + actor.height; + + const boxplusLineGroup = elem.append('g').lower(); + let g = boxplusLineGroup; + + if (!isFooter) { + actorCnt++; + if (Object.keys(actor.links || {}).length && !conf.forceMenus) { + g.attr('onclick', popupMenuToggle(`actor${actorCnt}_popup`)).attr('cursor', 'pointer'); + } + g.append('line') + .attr('id', 'actor' + actorCnt) + .attr('x1', center) + .attr('y1', centerY) + .attr('x2', center) + .attr('y2', 2000) + .attr('class', 'actor-line 200') + .attr('stroke-width', '0.5px') + .attr('stroke', '#999') + .attr('name', actor.name); + + g = boxplusLineGroup.append('g'); + actor.actorCnt = actorCnt; + + if (actor.links != null) { + g.attr('id', 'root-' + actorCnt); + } + } + + const rect = svgDrawCommon.getNoteRect(); + + let cssclass = 'actor'; + if (actor.properties?.class) { + cssclass = actor.properties.class; + } else { + rect.fill = '#eaeaea'; + } + + if (isFooter) { + cssclass += ` ${BOTTOM_ACTOR_CLASS}`; + } else { + cssclass += ` ${TOP_ACTOR_CLASS}`; + } + + rect.x = actor.x; + rect.y = actorY; + rect.width = actor.width; + rect.height = actor.height; + rect.class = cssclass; + rect.name = actor.name; + + // Cylinder dimensions + rect.x = actor.x; + rect.y = actorY; + const w = rect.width; + const h = rect.height; + const rx = w / 2; + const ry = rx / (2.5 + w / 50); + + // Cylinder base group + const cylinderGroup = g.append('g'); + + const pathData = createCylinderPathD( + rect.x, + rect.y, + w / 2, + isFooter ? h - ry : h / 2, + rx / 4, + ry / 4 + ); + cylinderGroup.append('path').attr('d', pathData).attr('class', cssclass); + + if (!isFooter) { + cylinderGroup.attr('transform', `translate(${w / 4}, ${-centerY / 2 + 2.75 * ry})`); + } else { + cylinderGroup.attr('transform', `translate(${w / 4}, ${ry / 2})`); + } + + actor.rectData = rect; + + _drawTextCandidateFunc(conf, hasKatex(actor.description))( + actor.description, + g, + rect.x, + rect.y + (!isFooter ? ry * 1.75 : h / 2 + ry), + rect.width, + rect.height, + { class: `actor ${ACTOR_BOX_CLASS}` }, + conf + ); + + let height = actor.height; + const lastPath = cylinderGroup.select('path:last-child'); + if (lastPath.node()) { + const bounds = lastPath.node().getBBox(); + actor.height = bounds.height; + height = bounds.height; + } + + return height; +}; + +const drawActorTypeBoundary = function (elem, actor, conf, isFooter) { + const actorY = isFooter ? actor.stopy : actor.starty; + const center = actor.x + actor.width / 2; + const centerY = actorY + 80; + const radius = 30; + const line = elem.append('g').lower(); + + if (!isFooter) { + actorCnt++; + line + .append('line') + .attr('id', 'actor' + actorCnt) + .attr('x1', center) + .attr('y1', centerY) + .attr('x2', center) + .attr('y2', 2000) + .attr('class', 'actor-line 200') + .attr('stroke-width', '0.5px') + .attr('stroke', '#999') + .attr('name', actor.name); + + actor.actorCnt = actorCnt; + } + const actElem = elem.append('g'); + let cssClass = ACTOR_MAN_FIGURE_CLASS; + if (isFooter) { + cssClass += ` ${BOTTOM_ACTOR_CLASS}`; + } else { + cssClass += ` ${TOP_ACTOR_CLASS}`; + } + actElem.attr('class', cssClass); + actElem.attr('name', actor.name); + + const rect = svgDrawCommon.getNoteRect(); + rect.x = actor.x; + rect.y = actorY; + rect.fill = '#eaeaea'; + rect.width = actor.width; + rect.height = actor.height; + rect.class = 'actor'; + rect.rx = 3; + rect.ry = 3; + + actElem + .append('line') + .attr('id', 'actor-man-torso' + actorCnt) + .attr('x1', actor.x + actor.width / 2 - radius * 2.5) + .attr('y1', actorY + 10) + .attr('x2', actor.x + actor.width / 2 - 15) + .attr('y2', actorY + 10); + + actElem + .append('line') + .attr('id', 'actor-man-arms' + actorCnt) + .attr('x1', actor.x + actor.width / 2 - radius * 2.5) + .attr('y1', actorY + 0) // starting Y + .attr('x2', actor.x + actor.width / 2 - radius * 2.5) + .attr('y2', actorY + 20); // ending Y (26px long, adjust as needed) + + actElem + .append('circle') + .attr('cx', actor.x + actor.width / 2) + .attr('cy', actorY + 10) + .attr('r', radius) + .attr('width', actor.width); + + const bounds = actElem.node().getBBox(); + actor.height = bounds.height + conf.boxTextMargin; + + _drawTextCandidateFunc(conf, hasKatex(actor.description))( + actor.description, + actElem, + rect.x, + rect.y + (!isFooter ? radius / 2 : radius / 2), + rect.width, + rect.height, + { class: `actor ${ACTOR_MAN_FIGURE_CLASS}` }, + conf + ); + + if (!isFooter) { + actElem.attr('transform', `translate(0,${radius / 2 + 7})`); + } else { + actElem.attr('transform', `translate(0,${radius / 2 + 7})`); + } + + // actElem.attr('transform', `translate(${rect.width / 2}, ${actorY + rect.height / 2})`); + + return actor.height; +}; + const drawActorTypeActor = function (elem, actor, conf, isFooter) { const actorY = isFooter ? actor.stopy : actor.starty; const center = actor.x + actor.width / 2; @@ -512,6 +1115,18 @@ export const drawActor = async function (elem, actor, conf, isFooter) { return await drawActorTypeActor(elem, actor, conf, isFooter); case 'participant': return await drawActorTypeParticipant(elem, actor, conf, isFooter); + case 'boundary': + return await drawActorTypeBoundary(elem, actor, conf, isFooter); + case 'control': + return await drawActorTypeControl(elem, actor, conf, isFooter); + case 'entity': + return await drawActorTypeEntity(elem, actor, conf, isFooter); + case 'database': + return await drawActorTypeDatabase(elem, actor, conf, isFooter); + case 'collections': + return await drawActorTypeCollections(elem, actor, conf, isFooter); + case 'queue': + return await drawActorTypeQueue(elem, actor, conf, isFooter); } }; From 76d073b0272d5b3694cb754860717b874195ac49 Mon Sep 17 00:00:00 2001 From: omkarht Date: Fri, 27 Jun 2025 17:56:03 +0530 Subject: [PATCH 02/23] chore: adjusted database and queue shape --- .../src/diagrams/sequence/sequenceRenderer.ts | 7 +- .../mermaid/src/diagrams/sequence/svgDraw.js | 65 +++++++++---------- 2 files changed, 38 insertions(+), 34 deletions(-) diff --git a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts index 8b4842fc1..e44431928 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts @@ -725,7 +725,12 @@ function adjustCreatedDestroyedData( msgModel.startx = msgModel.startx - adjustment; } } - const actorArray = [PARTICIPANT_TYPE.ACTOR, PARTICIPANT_TYPE.CONTROL, PARTICIPANT_TYPE.ENTITY]; + const actorArray = [ + PARTICIPANT_TYPE.ACTOR, + PARTICIPANT_TYPE.CONTROL, + PARTICIPANT_TYPE.ENTITY, + PARTICIPANT_TYPE.DATABASE, + ]; // if it is a create message if (createdActors.get(msg.to) == index) { diff --git a/packages/mermaid/src/diagrams/sequence/svgDraw.js b/packages/mermaid/src/diagrams/sequence/svgDraw.js index 3af3d3cf0..5304f57f5 100644 --- a/packages/mermaid/src/diagrams/sequence/svgDraw.js +++ b/packages/mermaid/src/diagrams/sequence/svgDraw.js @@ -3,8 +3,6 @@ import * as svgDrawCommon from '../common/svgDrawCommon.js'; import { ZERO_WIDTH_SPACE, parseFontSize } from '../../utils.js'; import { sanitizeUrl } from '@braintree/sanitize-url'; import * as configApi from '../../config.js'; -import { createInnerCylinderPathD } from '../../rendering-util/rendering-elements/shapes/tiltedCylinder.js'; -import { createCylinderPathD } from '../../rendering-util/rendering-elements/shapes/cylinder.js'; export const ACTOR_TYPE_WIDTH = 18 * 2; const TOP_ACTOR_CLASS = 'actor-top'; @@ -582,8 +580,7 @@ const drawActorTypeQueue = function (elem, actor, conf, isFooter) { .append('path') .attr( 'd', - ` - M ${rect.x},${rect.y + ry} + `M ${rect.x},${rect.y + ry} a ${rx},${ry} 0 0 0 0,${rect.height} h ${rect.width - 2 * rx} a ${rx},${ry} 0 0 0 0,-${rect.height} @@ -593,18 +590,17 @@ const drawActorTypeQueue = function (elem, actor, conf, isFooter) { .attr('class', cssclass); cylinderArc .append('path') - .attr('d', createInnerCylinderPathD(rect.x - rx, rect.y + ry, rect.width, rect.height, rx, ry)) + .attr( + 'd', + `M ${rect.x},${rect.y + ry} + a ${rx},${ry} 0 0 0 0,${rect.height}` + ) .attr('stroke', '#666') .attr('stroke-width', '1px') .attr('class', cssclass); - if (!isFooter) { - cylinderGroup.attr('transform', `translate(${rx}, ${-(rect.height / 2) - rect.y})`); - cylinderArc.attr('transform', `translate(${rect.width / 2}, ${rect.height / 2})`); - } else { - cylinderGroup.attr('transform', `translate(${rx}, ${-(rect.height / 2)})`); - cylinderArc.attr('transform', `translate(${rect.width / 2}, ${actorY + rect.height / 2})`); - } + cylinderGroup.attr('transform', `translate(${rx}, ${-(rect.height / 2)})`); + cylinderArc.attr('transform', `translate(${rect.width - rx}, ${-rect.height / 2})`); actor.rectData = rect; @@ -821,7 +817,7 @@ const drawActorTypeEntity = function (elem, actor, conf, isFooter) { const drawActorTypeDatabase = function (elem, actor, conf, isFooter) { const actorY = isFooter ? actor.stopy : actor.starty; const center = actor.x + actor.width / 2; - const centerY = actorY + actor.height; + const centerY = actorY + actor.height + 2 * conf.boxTextMargin; const boxplusLineGroup = elem.append('g').lower(); let g = boxplusLineGroup; @@ -875,52 +871,55 @@ const drawActorTypeDatabase = function (elem, actor, conf, isFooter) { // Cylinder dimensions rect.x = actor.x; rect.y = actorY; - const w = rect.width; - const h = rect.height; + const w = rect.width / 4; + const h = rect.height * 0.8; const rx = w / 2; const ry = rx / (2.5 + w / 50); // Cylinder base group const cylinderGroup = g.append('g'); - const pathData = createCylinderPathD( - rect.x, - rect.y, - w / 2, - isFooter ? h - ry : h / 2, - rx / 4, - ry / 4 - ); - cylinderGroup.append('path').attr('d', pathData).attr('class', cssclass); + const d = ` + M ${rect.x},${rect.y + ry} + a ${rx},${ry} 0 0 0 ${w},0 + a ${rx},${ry} 0 0 0 -${w},0 + l 0,${h - 2 * ry} + a ${rx},${ry} 0 0 0 ${w},0 + l 0,-${h - 2 * ry} +`; + // Draw the main cylinder body + cylinderGroup + .append('path') + .attr('d', d) + .attr('fill', '#eaeaea') + .attr('stroke', '#000') + .attr('stroke-width', 1) + .attr('class', cssclass); if (!isFooter) { - cylinderGroup.attr('transform', `translate(${w / 4}, ${-centerY / 2 + 2.75 * ry})`); + cylinderGroup.attr('transform', `translate(${w * 1.5}, ${rect.height / 4 - 2 * ry})`); } else { - cylinderGroup.attr('transform', `translate(${w / 4}, ${ry / 2})`); + cylinderGroup.attr('transform', `translate(${w * 1.5}, ${rect.height / 4 - 2 * ry})`); } - actor.rectData = rect; - _drawTextCandidateFunc(conf, hasKatex(actor.description))( actor.description, g, rect.x, - rect.y + (!isFooter ? ry * 1.75 : h / 2 + ry), + rect.y + (!isFooter ? rect.height / 2 : h / 2 + ry), rect.width, rect.height, { class: `actor ${ACTOR_BOX_CLASS}` }, conf ); - let height = actor.height; const lastPath = cylinderGroup.select('path:last-child'); if (lastPath.node()) { const bounds = lastPath.node().getBBox(); - actor.height = bounds.height; - height = bounds.height; + actor.height = bounds.height + 2 * conf.boxTextMargin; } - return height; + return actor.height; }; const drawActorTypeBoundary = function (elem, actor, conf, isFooter) { From b61bec8fafee523cc545493023151fcd0c13a45d Mon Sep 17 00:00:00 2001 From: omkarht Date: Tue, 1 Jul 2025 12:58:04 +0530 Subject: [PATCH 03/23] added test cases for new participant types of sequence diagram --- .../rendering/sequencediagram-v2.spec.js | 644 ++++++++++++++++++ 1 file changed, 644 insertions(+) create mode 100644 cypress/integration/rendering/sequencediagram-v2.spec.js diff --git a/cypress/integration/rendering/sequencediagram-v2.spec.js b/cypress/integration/rendering/sequencediagram-v2.spec.js new file mode 100644 index 000000000..4b7fe88d5 --- /dev/null +++ b/cypress/integration/rendering/sequencediagram-v2.spec.js @@ -0,0 +1,644 @@ +import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts'; + +const looks = ['classic']; +const participantTypes = [ + 'participant', + 'actor', + 'boundary', + 'control', + 'entity', + 'database', + 'collections', + 'queue', +]; + +const interactionTypes = [ + '->>', // Solid arrow with arrowhead + '-->>', // Dotted arrow with arrowhead + '->', // Solid arrow without arrowhead + '-->', // Dotted arrow without arrowhead + '-x', // Solid arrow with cross + '--x', // Dotted arrow with cross + '->>+', // Solid arrow with arrowhead (activate) + '-->>+', // Dotted arrow with arrowhead (activate) +]; + +const notePositions = ['left of', 'right of', 'over']; + +looks.forEach((look) => { + describe(`Sequence Diagram Tests - ${look} look`, () => { + it('should render all participant types', () => { + let diagramCode = `sequenceDiagram\n`; + participantTypes.forEach((type, index) => { + diagramCode += ` ${type} ${type}${index} as ${type} ${index}\n`; + }); + // Add some basic interactions + for (let i = 0; i < participantTypes.length - 1; i++) { + diagramCode += ` ${participantTypes[i]}${i} ->> ${participantTypes[i + 1]}${i + 1}: Message ${i}\n`; + } + imgSnapshotTest(diagramCode, { look, sequence: { diagramMarginX: 50, diagramMarginY: 10 } }); + }); + + it('should render all interaction types', () => { + let diagramCode = `sequenceDiagram\n`; + // Create two participants + // Add all interaction types + diagramCode += ` participant A\n`; + diagramCode += ` participant B\n`; + interactionTypes.forEach((interaction, index) => { + diagramCode += ` A ${interaction} B: ${interaction} message ${index}\n`; + }); + imgSnapshotTest(diagramCode, { look }); + }); + + it('should render participant creation and destruction', () => { + let diagramCode = `sequenceDiagram\n`; + participantTypes.forEach((type, index) => { + diagramCode += ` ${type} A\n`; + diagramCode += ` ${type} B\n`; + diagramCode += ` create ${type} ${type}${index}\n`; + diagramCode += ` A ->> ${type}${index}: Hello ${type}\n`; + if (index % 2 === 0) { + diagramCode += ` destroy ${type}${index}\n`; + } + }); + imgSnapshotTest(diagramCode, { look }); + }); + + it('should render notes in all positions', () => { + let diagramCode = `sequenceDiagram\n`; + diagramCode += ` participant A\n`; + diagramCode += ` participant B\n`; + notePositions.forEach((position, index) => { + diagramCode += ` Note ${position} A: Note ${position} ${index}\n`; + }); + diagramCode += ` A ->> B: Message with notes\n`; + imgSnapshotTest(diagramCode, { look }); + }); + + it('should render parallel interactions', () => { + let diagramCode = `sequenceDiagram\n`; + participantTypes.slice(0, 4).forEach((type, index) => { + diagramCode += ` ${type} ${type}${index}\n`; + }); + diagramCode += ` par Parallel actions\n`; + for (let i = 0; i < participantTypes.length - 1; i += 2) { + diagramCode += ` ${participantTypes[i]}${i} ->> ${participantTypes[i + 1]}${i + 1}: Message ${i}\n`; + if (i < participantTypes.length - 2) { + diagramCode += ` and\n`; + } + } + diagramCode += ` end\n`; + imgSnapshotTest(diagramCode, { look }); + }); + + it('should render alternative flows', () => { + let diagramCode = `sequenceDiagram\n`; + diagramCode += ` participant A\n`; + diagramCode += ` participant B\n`; + diagramCode += ` alt Successful case\n`; + diagramCode += ` A ->> B: Request\n`; + diagramCode += ` B -->> A: Success\n`; + diagramCode += ` else Failure case\n`; + diagramCode += ` A ->> B: Request\n`; + diagramCode += ` B --x A: Failure\n`; + diagramCode += ` end\n`; + imgSnapshotTest(diagramCode, { look }); + }); + + it('should render loops', () => { + let diagramCode = `sequenceDiagram\n`; + participantTypes.slice(0, 3).forEach((type, index) => { + diagramCode += ` ${type} ${type}${index}\n`; + }); + diagramCode += ` loop For each participant\n`; + for (let i = 0; i < 3; i++) { + diagramCode += ` ${participantTypes[0]}0 ->> ${participantTypes[1]}1: Message ${i}\n`; + } + diagramCode += ` end\n`; + imgSnapshotTest(diagramCode, { look }); + }); + + it('should render boxes around groups', () => { + let diagramCode = `sequenceDiagram\n`; + diagramCode += ` box Group 1\n`; + participantTypes.slice(0, 3).forEach((type, index) => { + diagramCode += ` ${type} ${type}${index}\n`; + }); + diagramCode += ` end\n`; + diagramCode += ` box rgb(200,220,255) Group 2\n`; + participantTypes.slice(3, 6).forEach((type, index) => { + diagramCode += ` ${type} ${type}${index}\n`; + }); + diagramCode += ` end\n`; + // Add some interactions + diagramCode += ` ${participantTypes[0]}0 ->> ${participantTypes[3]}0: Cross-group message\n`; + imgSnapshotTest(diagramCode, { look }); + }); + + it('should render with different font settings', () => { + let diagramCode = `sequenceDiagram\n`; + participantTypes.slice(0, 3).forEach((type, index) => { + diagramCode += ` ${type} ${type}${index}\n`; + }); + diagramCode += ` ${participantTypes[0]}0 ->> ${participantTypes[1]}1: Regular message\n`; + diagramCode += ` Note right of ${participantTypes[1]}1: Regular note\n`; + imgSnapshotTest(diagramCode, { + look, + sequence: { + actorFontFamily: 'courier', + actorFontSize: 14, + messageFontFamily: 'Arial', + messageFontSize: 12, + noteFontFamily: 'times', + noteFontSize: 16, + noteAlign: 'left', + }, + }); + }); + }); +}); + +// Additional tests for specific combinations +describe('Sequence Diagram Special Cases', () => { + it('should render complex sequence with all features', () => { + const diagramCode = ` + sequenceDiagram + box rgb(200,220,255) Authentication + actor User + boundary LoginUI + control AuthService + database UserDB + end + + box rgb(200,255,220) Order Processing + entity Order + queue OrderQueue + collections AuditLogs + end + + User ->> LoginUI: Enter credentials + LoginUI ->> AuthService: Validate + AuthService ->> UserDB: Query user + UserDB -->> AuthService: User data + alt Valid credentials + AuthService -->> LoginUI: Success + LoginUI -->> User: Welcome + + par Place order + User ->> Order: New order + Order ->> OrderQueue: Process + and + Order ->> AuditLogs: Record + end + + loop Until confirmed + OrderQueue ->> Order: Update status + Order -->> User: Notification + end + else Invalid credentials + AuthService --x LoginUI: Failure + LoginUI --x User: Retry + end + `; + imgSnapshotTest(diagramCode, {}); + }); + + it('should render with wrapped messages and notes', () => { + const diagramCode = ` + sequenceDiagram + participant A + participant B + + A ->> B: This is a very long message that should wrap properly in the diagram rendering + Note over A,B: This is a very long note that should also wrap properly when rendered in the diagram + + par Wrapped parallel + A ->> B: Parallel message 1
with explicit line break + and + B ->> A: Parallel message 2
with explicit line break + end + + loop Wrapped loop + Note right of B: This is a long note
in a loop + A ->> B: Message in loop + end + `; + imgSnapshotTest(diagramCode, { sequence: { wrap: true } }); + }); + describe('Sequence Diagram Rendering with Different Participant Types', () => { + it('should render a sequence diagram with various participant types', () => { + imgSnapshotTest( + ` + sequenceDiagram + actor User + participant AuthService as Authentication Service + boundary UI + control OrderController + entity Product + database MongoDB + collections Products + queue OrderQueue + User ->> UI: Login request + UI ->> AuthService: Validate credentials + AuthService -->> UI: Authentication token + UI ->> OrderController: Place order + OrderController ->> Product: Check availability + Product -->> OrderController: Available + OrderController ->> MongoDB: Save order + MongoDB -->> OrderController: Order saved + OrderController ->> OrderQueue: Process payment + OrderQueue -->> User: Order confirmation + ` + ); + }); + + it('should render participant creation and destruction with different types', () => { + imgSnapshotTest( + ` + sequenceDiagram + actor Customer + participant Frontend + boundary PaymentGateway + Customer ->> Frontend: Place order + Frontend ->> OrderProcessor: Process order + create database OrderDB + OrderProcessor ->> OrderDB: Save order + ` + ); + }); + + it('should handle complex interactions between different participant types', () => { + imgSnapshotTest( + ` + sequenceDiagram + box rgba(200,220,255,0.5) System Components + actor User + boundary WebUI + control API + entity BusinessLogic + database MainDB + end + box rgba(200,255,220,0.5) External Services + queue MessageQueue + database AuditLogs + end + + User ->> WebUI: Submit request + WebUI ->> API: Process request + API ->> BusinessLogic: Execute business rules + BusinessLogic ->> MainDB: Query data + MainDB -->> BusinessLogic: Return results + BusinessLogic ->> MessageQueue: Publish event + MessageQueue -->> AuditLogs: Store audit trail + AuditLogs -->> API: Audit complete + API -->> WebUI: Return response + WebUI -->> User: Show results + `, + { sequence: { useMaxWidth: false } } + ); + }); + + it('should render parallel processes with different participant types', () => { + imgSnapshotTest( + ` + sequenceDiagram + actor Customer + participant Frontend + boundary PaymentService + control InventoryManager + entity Order + database OrdersDB + queue NotificationQueue + + Customer ->> Frontend: Place order + Frontend ->> Order: Create order + par Parallel Processing + Order ->> PaymentService: Process payment + and + Order ->> InventoryManager: Reserve items + end + PaymentService -->> Order: Payment confirmed + InventoryManager -->> Order: Items reserved + Order ->> OrdersDB: Save finalized order + OrdersDB -->> Order: Order saved + Order ->> NotificationQueue: Send confirmation + NotificationQueue -->> Customer: Order confirmation + ` + ); + }); + + it('should render different participant types with notes and loops', () => { + imgSnapshotTest( + ` + sequenceDiagram + actor Admin + participant Dashboard + boundary AuthService + control UserManager + entity UserProfile + database UserDB + database Logs + + Admin ->> Dashboard: Open user management + loop Authentication check + Dashboard ->> AuthService: Verify admin rights + AuthService ->> Dashboard: Access granted + end + Dashboard ->> UserManager: List users + UserManager ->> UserDB: Query users + UserDB ->> UserManager: Return user data + Note right of UserDB: Encrypted data
requires decryption + UserManager ->> UserProfile: Format profiles + UserProfile ->> UserManager: Formatted data + UserManager ->> Dashboard: Display users + Dashboard ->> Logs: Record access + Logs ->> Admin: Audit trail + ` + ); + }); + + it('should render different participant types with alternative flows', () => { + imgSnapshotTest( + ` + sequenceDiagram + actor Client + participant MobileApp + boundary CloudService + control DataProcessor + entity Transaction + database TransactionsDB + queue EventBus + + Client ->> MobileApp: Initiate transaction + MobileApp ->> CloudService: Authenticate + alt Authentication successful + CloudService -->> MobileApp: Auth token + MobileApp ->> DataProcessor: Process data + DataProcessor ->> Transaction: Create transaction + Transaction ->> TransactionsDB: Save record + TransactionsDB -->> Transaction: Confirmation + Transaction ->> EventBus: Publish event + EventBus -->> Client: Notification + else Authentication failed + CloudService -->> MobileApp: Error + MobileApp -->> Client: Show error + end + ` + ); + }); + + it('should render different participant types with wrapping text', () => { + imgSnapshotTest( + ` + sequenceDiagram + actor LongNameUser as User With A Very
Long Name + participant FE as Frontend Service
With Long Name + boundary B as Boundary With
Multiline Name + control C as Control With
Multiline Name + entity E as Entity With
Multiline Name + database DB as Database With
Multiline Name + collections COL as Collections With
Multiline Name + queue Q as Queue With
Multiline Name + + LongNameUser ->> FE: This is a very long message that should wrap properly in the diagram + FE ->> B: Another long message
with explicit
line breaks + B -->> FE: Response message that is also quite long and needs to wrap + FE ->> C: Process data + C ->> E: Validate + E -->> C: Validation result + C ->> DB: Save + DB -->> C: Save result + C ->> COL: Log + COL -->> Q: Forward + Q -->> LongNameUser: Final response with confirmation of all actions taken + `, + { sequence: { wrap: true } } + ); + }); + describe('Sequence Diagram - New Participant Types with Long Notes and Messages', () => { + it('should render long notes left of boundary', () => { + imgSnapshotTest( + ` + sequenceDiagram + boundary Alice + actor Bob + Alice->>Bob: Hola + Note left of Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be + Bob->>Alice: I'm short though + `, + {} + ); + }); + + it('should render wrapped long notes left of control', () => { + imgSnapshotTest( + ` + sequenceDiagram + control Alice + actor Bob + Alice->>Bob: Hola + Note left of Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be + Bob->>Alice: I'm short though + `, + {} + ); + }); + + it('should render long notes right of entity', () => { + imgSnapshotTest( + ` + sequenceDiagram + entity Alice + actor Bob + Alice->>Bob: Hola + Note right of Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be + Bob->>Alice: I'm short though + `, + {} + ); + }); + + it('should render wrapped long notes right of database', () => { + imgSnapshotTest( + ` + sequenceDiagram + database Alice + actor Bob + Alice->>Bob: Hola + Note right of Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be + Bob->>Alice: I'm short though + `, + {} + ); + }); + + it('should render long notes over collections', () => { + imgSnapshotTest( + ` + sequenceDiagram + collections Alice + actor Bob + Alice->>Bob: Hola + Note over Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be + Bob->>Alice: I'm short though + `, + {} + ); + }); + + it('should render wrapped long notes over queue', () => { + imgSnapshotTest( + ` + sequenceDiagram + queue Alice + actor Bob + Alice->>Bob: Hola + Note over Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be + Bob->>Alice: I'm short though + `, + {} + ); + }); + + it('should render notes over actor and boundary', () => { + imgSnapshotTest( + ` + sequenceDiagram + actor Alice + boundary Charlie + note over Alice: Some note + note over Charlie: Other note + `, + {} + ); + }); + + it('should render long messages from database to collections', () => { + imgSnapshotTest( + ` + sequenceDiagram + database Alice + collections Bob + Alice->>Bob: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be + Bob->>Alice: I'm short though + `, + {} + ); + }); + + it('should render wrapped long messages from control to entity', () => { + imgSnapshotTest( + ` + sequenceDiagram + control Alice + entity Bob + Alice->>Bob:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be + Bob->>Alice: I'm short though + `, + {} + ); + }); + + it('should render long messages from queue to boundary', () => { + imgSnapshotTest( + ` + sequenceDiagram + queue Alice + boundary Bob + Alice->>Bob: I'm short + Bob->>Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be + `, + {} + ); + }); + + it('should render wrapped long messages from actor to database', () => { + imgSnapshotTest( + ` + sequenceDiagram + actor Alice + database Bob + Alice->>Bob: I'm short + Bob->>Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be + `, + {} + ); + }); + }); + }); + + describe('svg size', () => { + it('should render a sequence diagram when useMaxWidth is true (default)', () => { + renderGraph( + ` + sequenceDiagram + actor Alice + boundary Bob + control John as John
Second Line + Alice ->> Bob: Hello Bob, how are you? + Bob-->>John: How about you John? + Bob--x Alice: I am good thanks! + Bob-x John: I am good thanks! + Note right of John: Bob thinks a long
long time, so long
that the text does
not fit on a row. + Bob-->Alice: Checking with John... + alt either this + Alice->>John: Yes + else or this + Alice->>John: No + else or this will happen + Alice->John: Maybe + end + par this happens in parallel + Alice -->> Bob: Parallel message 1 + and + Alice -->> John: Parallel message 2 + end + `, + { sequence: { useMaxWidth: true } } + ); + cy.get('svg').should((svg) => { + expect(svg).to.have.attr('width', '100%'); + const style = svg.attr('style'); + expect(style).to.match(/^max-width: [\d.]+px;$/); + const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join('')); + expect(maxWidthValue).to.be.within(820 * 0.95, 820 * 1.05); + }); + }); + + it('should render a sequence diagram when useMaxWidth is false', () => { + renderGraph( + ` + sequenceDiagram + actor Alice + boundary Bob + control John as John
Second Line + Alice ->> Bob: Hello Bob, how are you? + Bob-->>John: How about you John? + Bob--x Alice: I am good thanks! + Bob-x John: I am good thanks! + Note right of John: Bob thinks a long
long time, so long
that the text does
not fit on a row. + Bob-->Alice: Checking with John... + alt either this + Alice->>John: Yes + else or this + Alice->>John: No + else or this will happen + Alice->John: Maybe + end + par this happens in parallel + Alice -->> Bob: Parallel message 1 + and + Alice -->> John: Parallel message 2 + end + `, + { sequence: { useMaxWidth: false } } + ); + cy.get('svg').should((svg) => { + const width = parseFloat(svg.attr('width')); + expect(width).to.be.within(820 * 0.95, 820 * 1.05); + expect(svg).to.not.have.attr('style'); + }); + }); + }); +}); From a4a27611dd94ecbb6ebb85ee49fd12c83984596e Mon Sep 17 00:00:00 2001 From: omkarht Date: Tue, 1 Jul 2025 18:11:41 +0530 Subject: [PATCH 04/23] fix: minor refinement --- .../src/diagrams/sequence/sequenceRenderer.ts | 12 ++++++++---- packages/mermaid/src/diagrams/sequence/svgDraw.js | 12 ++++++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts index e44431928..d390ebada 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts @@ -1076,10 +1076,11 @@ export const draw = async function (_text: string, id: string, _version: string, for (const box of bounds.models.boxes) { box.height = bounds.getVerticalPos() - box.y; bounds.insert(box.x, box.y, box.x + box.width, box.height); - box.startx = box.x; - box.starty = box.y; - box.stopx = box.startx + box.width; - box.stopy = box.starty + box.height; + const boxPadding = conf.boxMargin * 2; + box.startx = box.x - boxPadding; + box.starty = box.y - boxPadding * 0.25; + box.stopx = box.startx + box.width + 2 * boxPadding; + box.stopy = box.starty + box.height + boxPadding * 0.75; box.stroke = 'rgb(0,0,0, 0.5)'; svgDraw.drawBox(diagram, box, conf); } @@ -1344,6 +1345,9 @@ async function calculateActorMargins( return (total += actors.get(aKey).width + (actors.get(aKey).margin || 0)); }, 0); + const standardBoxPadding = conf.boxMargin * 8; + totalWidth += standardBoxPadding; + totalWidth -= 2 * conf.boxTextMargin; if (box.wrap) { box.name = utils.wrapLabel(box.name, totalWidth - 2 * conf.wrapPadding, textFont); diff --git a/packages/mermaid/src/diagrams/sequence/svgDraw.js b/packages/mermaid/src/diagrams/sequence/svgDraw.js index 5304f57f5..6b8fa0208 100644 --- a/packages/mermaid/src/diagrams/sequence/svgDraw.js +++ b/packages/mermaid/src/diagrams/sequence/svgDraw.js @@ -712,13 +712,13 @@ const drawActorTypeControl = function (elem, actor, conf, isFooter) { .attr('marker-end', 'url(#filled-head-control)'); const bounds = actElem.node().getBBox(); - actor.height = bounds.height + conf.boxTextMargin; + actor.height = bounds.height + 2 * (conf?.sequence?.labelBoxHeight ?? 0); _drawTextCandidateFunc(conf, hasKatex(actor.description))( actor.description, actElem, rect.x, - rect.y + (!isFooter ? 30 : 40), + rect.y + 30, rect.width, rect.height, { class: `actor ${ACTOR_MAN_FIGURE_CLASS}` }, @@ -774,8 +774,8 @@ const drawActorTypeEntity = function (elem, actor, conf, isFooter) { .attr('stroke', '#333') .attr('stroke-width', 2); - const boundBox = actElem.node().getBBox(); - actor.height = boundBox.height + conf.boxTextMargin; + const bounds = actElem.node().getBBox(); + actor.height = bounds.height + (conf.sequence.labelBoxHeight ?? 0); if (!isFooter) { actorCnt++; @@ -916,7 +916,7 @@ const drawActorTypeDatabase = function (elem, actor, conf, isFooter) { const lastPath = cylinderGroup.select('path:last-child'); if (lastPath.node()) { const bounds = lastPath.node().getBBox(); - actor.height = bounds.height + 2 * conf.boxTextMargin; + actor.height = bounds.height + (conf.sequence.labelBoxHeight ?? 0); } return actor.height; @@ -989,7 +989,7 @@ const drawActorTypeBoundary = function (elem, actor, conf, isFooter) { .attr('width', actor.width); const bounds = actElem.node().getBBox(); - actor.height = bounds.height + conf.boxTextMargin; + actor.height = bounds.height + (conf.sequence.labelBoxHeight ?? 0); _drawTextCandidateFunc(conf, hasKatex(actor.description))( actor.description, From 012530e98e9b8b80962ab270b6bb3b6d9f6ada05 Mon Sep 17 00:00:00 2001 From: omkarht Date: Tue, 1 Jul 2025 19:02:57 +0530 Subject: [PATCH 05/23] added changeset --- .changeset/hungry-baths-glow.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/hungry-baths-glow.md diff --git a/.changeset/hungry-baths-glow.md b/.changeset/hungry-baths-glow.md new file mode 100644 index 000000000..b3084bcab --- /dev/null +++ b/.changeset/hungry-baths-glow.md @@ -0,0 +1,5 @@ +--- +'mermaid': minor +--- + +feat: Added support for new participant types (`actor`, `boundary`, `control`, `entity`, `database`, `collections`, `queue`) in `sequenceDiagram`. From ee82694645080e55ff5f8b42e1d84ec0cdf7d03d Mon Sep 17 00:00:00 2001 From: omkarht Date: Tue, 8 Jul 2025 13:47:41 +0530 Subject: [PATCH 06/23] refactor(lexer): added contextual handling for new participant types as actors --- .../sequence/parser/sequenceDiagram.jison | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison index 4b5a82851..6cd0537cb 100644 --- a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison +++ b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison @@ -12,6 +12,25 @@ %options case-insensitive +%{ +function matchAsActorOrParticipant(tokenName, tokenType) { + const ahead = this._input; + + // Detect if an arrow or colon is coming right after the token + const arrowLike = /^(?:\s)*(->>|-->>|->|-->|<<->>|<<-->>)/; + const colonLike = /^\s*:/; + + // Treat as ACTOR if database appears inline in a message (arrow or colon follows) + if (arrowLike.test(ahead) || colonLike.test(ahead)) { + yytext = tokenName; + return 'ACTOR'; + } + // Otherwise treat as a participant type declaration + this.begin('ID'); + return tokenType; +} +%} + // Special states for recognizing aliases // A special state for grabbing text up to the first comment/newline %x ID ALIAS LINE @@ -31,12 +50,12 @@ "box" { this.begin('LINE'); return 'box'; } "participant" { this.begin('ID'); return 'participant'; } "actor" { this.begin('ID'); return 'participant_actor'; } -"boundary" { this.begin('ID'); return 'participant_boundary'; } -"control" { this.begin('ID'); return 'participant_control'; } -"entity" { this.begin('ID'); return 'participant_entity'; } -"database" { this.begin('ID'); return 'participant_database'; } -"collections" { this.begin('ID'); return 'participant_collections'; } -"queue" { this.begin('ID'); return 'participant_queue'; } +"boundary" { return matchAsActorOrParticipant.call(this, 'boundary', 'participant_boundary'); } +"control" { return matchAsActorOrParticipant.call(this, 'control', 'participant_control'); } +"entity" { return matchAsActorOrParticipant.call(this, 'entity', 'participant_entity'); } +"database" { return matchAsActorOrParticipant.call(this, 'database', 'participant_database'); } +"collections" { return matchAsActorOrParticipant.call(this, 'collections', 'participant_collections'); } +"queue" { return matchAsActorOrParticipant.call(this, 'queue', 'participant_queue'); } "create" return 'create'; "destroy" { this.begin('ID'); return 'destroy'; } [^\<->\->:\n,;]+?([\-]*[^\<->\->:\n,;]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; } From 3abcfbb8d287b45ef71d46590f2bbb8cc8bd9529 Mon Sep 17 00:00:00 2001 From: omkarht Date: Tue, 8 Jul 2025 13:48:27 +0530 Subject: [PATCH 07/23] added unit tests --- .../diagrams/sequence/sequenceDiagram.spec.js | 185 ++++++++++++++++++ 1 file changed, 185 insertions(+) diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js index c3b8c2b4a..7bd278c23 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js +++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js @@ -2038,4 +2038,189 @@ Bob->>Alice:Got it! expect(messages[0].from).toBe('Alice'); expect(messages[0].to).toBe('Bob'); }); + describe('when newly parsing messages ', () => { + it('should parse a message', async () => { + const actor1 = 'database'; + const diagram = await Diagram.fromText(` + sequenceDiagram + database Alice + database Bob + Bob->>+Alice: Hi Alice + Alice->>+Bob: Hi Bob + `); + + const messages = diagram.db.getMessages(); + }); + + it('should parse a message', async () => { + const actor1 = 'database'; + const diagram = await Diagram.fromText(` + sequenceDiagram + participant lead + queue dsa + API->>+Database: getUserb + Database-->>-API: userb + queue --> Database: hello + `); + + const messages = diagram.db.getMessages(); + }); + }); + describe('participant type parsing', () => { + it('should parse boundary participant', async () => { + const diagram = await Diagram.fromText(` + sequenceDiagram + boundary B as Boundary Box + B->B: test + `); + const actors = diagram.db.getActors(); + expect(actors.get('B').type).toBe('boundary'); + expect(actors.get('B').description).toBe('Boundary Box'); + }); + + it('should parse control participant', async () => { + const diagram = await Diagram.fromText(` + sequenceDiagram + control C as Controller + C->C: test + `); + const actors = diagram.db.getActors(); + expect(actors.get('C').type).toBe('control'); + expect(actors.get('C').description).toBe('Controller'); + }); + + it('should parse entity participant', async () => { + const diagram = await Diagram.fromText(` + sequenceDiagram + entity E as Entity + E->E: test + `); + const actors = diagram.db.getActors(); + expect(actors.get('E').type).toBe('entity'); + expect(actors.get('E').description).toBe('Entity'); + }); + + it('should parse database participant', async () => { + const diagram = await Diagram.fromText(` + sequenceDiagram + database D as Database + D->D: test + `); + const actors = diagram.db.getActors(); + expect(actors.get('D').type).toBe('database'); + expect(actors.get('D').description).toBe('Database'); + }); + + it('should parse collections participant', async () => { + const diagram = await Diagram.fromText(` + sequenceDiagram + collections L as List + L->L: test + `); + const actors = diagram.db.getActors(); + expect(actors.get('L').type).toBe('collections'); + expect(actors.get('L').description).toBe('List'); + }); + + it('should parse queue participant', async () => { + const diagram = await Diagram.fromText(` + sequenceDiagram + queue Q as Jobs + Q->Q: test + `); + const actors = diagram.db.getActors(); + expect(actors.get('Q').type).toBe('queue'); + expect(actors.get('Q').description).toBe('Jobs'); + }); + }); + + describe('participant type parsing', () => { + it('should parse actor participant', async () => { + const diagram = await Diagram.fromText(` + sequenceDiagram + queue A as ActorName + A->A: test + `); + const actors = diagram.db.getActors(); + expect(actors.get('A').type).toBe('queue'); + expect(actors.get('A').description).toBe('ActorName'); + }); + + it('should parse participant participant', async () => { + const diagram = await Diagram.fromText(` + sequenceDiagram + database P as PartName + P->P: test + `); + const actors = diagram.db.getActors(); + expect(actors.get('P').type).toBe('database'); + expect(actors.get('P').description).toBe('PartName'); + }); + + it('should parse boundary using actor keyword', async () => { + const diagram = await Diagram.fromText(` + sequenceDiagram + collections B as Boundary + B->B: test + `); + const actors = diagram.db.getActors(); + expect(actors.get('B').type).toBe('collections'); + expect(actors.get('B').description).toBe('Boundary'); + }); + + it('should parse control using participant keyword', async () => { + const diagram = await Diagram.fromText(` + sequenceDiagram + control C as Controller + C->C: test + `); + const actors = diagram.db.getActors(); + expect(actors.get('C').type).toBe('control'); + expect(actors.get('C').description).toBe('Controller'); + }); + + it('should parse entity using actor keyword', async () => { + const diagram = await Diagram.fromText(` + sequenceDiagram + entity E as Entity + E->E: test + `); + const actors = diagram.db.getActors(); + expect(actors.get('E').type).toBe('entity'); + expect(actors.get('E').description).toBe('Entity'); + }); + + it('should parse database using participant keyword', async () => { + const diagram = await Diagram.fromText(` + sequenceDiagram + participant D as Database + D->D: test + `); + const actors = diagram.db.getActors(); + expect(actors.get('D').type).toBe('participant'); + expect(actors.get('D').description).toBe('Database'); + }); + + it('should parse collections using actor keyword', async () => { + const diagram = await Diagram.fromText(` + sequenceDiagram + actor L as List + L->L: test + `); + const actors = diagram.db.getActors(); + expect(actors.get('L').type).toBe('actor'); + expect(actors.get('L').description).toBe('List'); + }); + + it('should parse queue using participant keyword', async () => { + const diagram = await Diagram.fromText(` + sequenceDiagram + participant Q as Jobs + Q->Q: test + `); + const actors = diagram.db.getActors(); + expect(actors.get('Q').type).toBe('participant'); + expect(actors.get('Q').description).toBe('Jobs'); + }); + }); }); From 237d01d510f679bbce6059be2974be6dcde60790 Mon Sep 17 00:00:00 2001 From: omkarht Date: Mon, 28 Jul 2025 16:47:54 +0530 Subject: [PATCH 08/23] fix: shifted matchAsActorOrParticipant function to sequenceDB file from sequenceDiagram.jison file on-behalf-of: @Mermaid-Chart --- .../sequence/parser/sequenceDiagram.jison | 30 ++++--------------- .../src/diagrams/sequence/sequenceDb.ts | 18 +++++++++++ .../src/diagrams/sequence/sequenceDiagram.ts | 8 ++++- 3 files changed, 31 insertions(+), 25 deletions(-) diff --git a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison index 6cd0537cb..359141b92 100644 --- a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison +++ b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison @@ -12,24 +12,6 @@ %options case-insensitive -%{ -function matchAsActorOrParticipant(tokenName, tokenType) { - const ahead = this._input; - - // Detect if an arrow or colon is coming right after the token - const arrowLike = /^(?:\s)*(->>|-->>|->|-->|<<->>|<<-->>)/; - const colonLike = /^\s*:/; - - // Treat as ACTOR if database appears inline in a message (arrow or colon follows) - if (arrowLike.test(ahead) || colonLike.test(ahead)) { - yytext = tokenName; - return 'ACTOR'; - } - // Otherwise treat as a participant type declaration - this.begin('ID'); - return tokenType; -} -%} // Special states for recognizing aliases // A special state for grabbing text up to the first comment/newline @@ -50,12 +32,12 @@ function matchAsActorOrParticipant(tokenName, tokenType) { "box" { this.begin('LINE'); return 'box'; } "participant" { this.begin('ID'); return 'participant'; } "actor" { this.begin('ID'); return 'participant_actor'; } -"boundary" { return matchAsActorOrParticipant.call(this, 'boundary', 'participant_boundary'); } -"control" { return matchAsActorOrParticipant.call(this, 'control', 'participant_control'); } -"entity" { return matchAsActorOrParticipant.call(this, 'entity', 'participant_entity'); } -"database" { return matchAsActorOrParticipant.call(this, 'database', 'participant_database'); } -"collections" { return matchAsActorOrParticipant.call(this, 'collections', 'participant_collections'); } -"queue" { return matchAsActorOrParticipant.call(this, 'queue', 'participant_queue'); } +"boundary" { return yy.matchAsActorOrParticipant('boundary', 'participant_boundary', this._input, this); } +"control" { return yy.matchAsActorOrParticipant('control', 'participant_control', this._input, this); } +"entity" { return yy.matchAsActorOrParticipant('entity', 'participant_entity', this._input, this); } +"database" { return yy.matchAsActorOrParticipant('database', 'participant_database', this._input, this); } +"collections" { return yy.matchAsActorOrParticipant('collections', 'participant_collections', this._input, this); } +"queue" { return yy.matchAsActorOrParticipant('queue', 'participant_queue', this._input, this); } "create" return 'create'; "destroy" { this.begin('ID'); return 'destroy'; } [^\<->\->:\n,;]+?([\-]*[^\<->\->:\n,;]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; } diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDb.ts b/packages/mermaid/src/diagrams/sequence/sequenceDb.ts index d8421b84d..8b600c354 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDb.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceDb.ts @@ -107,6 +107,7 @@ export class SequenceDB implements DiagramDB { this.apply = this.apply.bind(this); this.parseBoxData = this.parseBoxData.bind(this); this.parseMessage = this.parseMessage.bind(this); + this.matchAsActorOrParticipant = this.matchAsActorOrParticipant.bind(this); this.clear(); @@ -341,6 +342,23 @@ export class SequenceDB implements DiagramDB { return message; } + public matchAsActorOrParticipant( + tokenName: string, + tokenType: string, + inputRemainder: string, + lexer: any + ): string { + log.info({ tokenName }); + const arrowLike = /^\s*(->>|-->>|->|-->|<<->>|<<-->>|-x|--x|-\))/; + const colonLike = /^\s*:/; + + if (arrowLike.test(inputRemainder) || colonLike.test(inputRemainder)) { + return 'ACTOR'; + } + lexer.begin('ID'); // used the passed lexer + return tokenType; + } + // We expect the box statement to be color first then description // The color can be rgb,rgba,hsl,hsla, or css code names #hex codes are not supported for now because of the way the char # is handled // We extract first segment as color, the rest of the line is considered as text diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.ts b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.ts index f2b701712..f7ca4938c 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.ts @@ -7,10 +7,16 @@ import { setConfig } from '../../diagram-api/diagramAPI.js'; import renderer from './sequenceRenderer.js'; import type { MermaidConfig } from '../../config.type.js'; +const db = new SequenceDB(); +parser.yy = { + parseMessage: db.parseMessage.bind(db), + matchAsActorOrParticipant: db.matchAsActorOrParticipant.bind(db), +}; + export const diagram: DiagramDefinition = { parser, get db() { - return new SequenceDB(); + return db; }, renderer, styles, From 334fe87bc6c3df222f03538efac514caa472d747 Mon Sep 17 00:00:00 2001 From: omkarht Date: Mon, 28 Jul 2025 18:33:58 +0530 Subject: [PATCH 09/23] fix: fixed failing test cases on-behalf-of: @Mermaid-Chart --- packages/mermaid/src/diagrams/sequence/sequenceDiagram.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.ts b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.ts index f7ca4938c..f2b701712 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.ts @@ -7,16 +7,10 @@ import { setConfig } from '../../diagram-api/diagramAPI.js'; import renderer from './sequenceRenderer.js'; import type { MermaidConfig } from '../../config.type.js'; -const db = new SequenceDB(); -parser.yy = { - parseMessage: db.parseMessage.bind(db), - matchAsActorOrParticipant: db.matchAsActorOrParticipant.bind(db), -}; - export const diagram: DiagramDefinition = { parser, get db() { - return db; + return new SequenceDB(); }, renderer, styles, From d525acc05b455a7fbdff496a53e12466291bfcd2 Mon Sep 17 00:00:00 2001 From: omkarht Date: Wed, 6 Aug 2025 17:08:57 +0530 Subject: [PATCH 10/23] chore: Add documentation for the syntax of the newly added participant type on-behalf-of: @Mermaid-Chart --- docs/syntax/sequenceDiagram.md | 216 ++++++++++++++++++ .../src/docs/syntax/sequenceDiagram.md | 120 ++++++++++ 2 files changed, 336 insertions(+) diff --git a/docs/syntax/sequenceDiagram.md b/docs/syntax/sequenceDiagram.md index 84240a0cd..b197f402b 100644 --- a/docs/syntax/sequenceDiagram.md +++ b/docs/syntax/sequenceDiagram.md @@ -74,6 +74,222 @@ sequenceDiagram Bob->>Alice: Hi Alice ``` +### Boundary + +If you want to use the boundary symbol for a participant, use the `boundary` statement as shown below. + +```mermaid-example +sequenceDiagram + boundary theBoundary + participant Bob + theBoundary->>Bob: Request from boundary + Bob->>theBoundary: Response to boundary +``` + +```mermaid +sequenceDiagram + boundary theBoundary + participant Bob + theBoundary->>Bob: Request from boundary + Bob->>theBoundary: Response to boundary +``` + +```mermaid-example +sequenceDiagram + boundary theBoundary + participant Bob + theBoundary->>Bob: Request from boundary + Bob->>theBoundary: Response to boundary +``` + +```mermaid +sequenceDiagram + boundary theBoundary + participant Bob + theBoundary->>Bob: Request from boundary + Bob->>theBoundary: Response to boundary +``` + +### Control + +If you want to use the control symbol for a participant, use the `control` statement as shown below. + +```mermaid-example +sequenceDiagram + control theControl + participant Alice + theControl->>Alice: Control request + Alice->>theControl: Control response +``` + +```mermaid +sequenceDiagram + control theControl + participant Alice + theControl->>Alice: Control request + Alice->>theControl: Control response +``` + +```mermaid-example +sequenceDiagram + control theControl + participant Alice + theControl->>Alice: Control request + Alice->>theControl: Control response +``` + +```mermaid +sequenceDiagram + control theControl + participant Alice + theControl->>Alice: Control request + Alice->>theControl: Control response +``` + +### Entity + +If you want to use the entity symbol for a participant, use the `entity` statement as shown below. + +```mermaid-example +sequenceDiagram + entity theEntity + participant Bob + theEntity->>Bob: Entity request + Bob->>theEntity: Entity response +``` + +```mermaid +sequenceDiagram + entity theEntity + participant Bob + theEntity->>Bob: Entity request + Bob->>theEntity: Entity response +``` + +```mermaid-example +sequenceDiagram + entity theEntity + participant Bob + theEntity->>Bob: Entity request + Bob->>theEntity: Entity response +``` + +```mermaid +sequenceDiagram + entity theEntity + participant Bob + theEntity->>Bob: Entity request + Bob->>theEntity: Entity response +``` + +### Database + +If you want to use the database symbol for a participant, use the `database` statement as shown below. + +```mermaid-example +sequenceDiagram + database theDb + participant Alice + theDb->>Alice: DB query + Alice->>theDb: DB result +``` + +```mermaid +sequenceDiagram + database theDb + participant Alice + theDb->>Alice: DB query + Alice->>theDb: DB result +``` + +```mermaid-example +sequenceDiagram + database theDb + participant Alice + theDb->>Alice: DB query + Alice->>theDb: DB result +``` + +```mermaid +sequenceDiagram + database theDb + participant Alice + theDb->>Alice: DB query + Alice->>theDb: DB result +``` + +### Collections + +If you want to use the collections symbol for a participant, use the `collections` statement as shown below. + +```mermaid-example +sequenceDiagram + collections theCollection + participant Bob + theCollection->>Bob: Collections request + Bob->>theCollection: Collections response +``` + +```mermaid +sequenceDiagram + collections theCollection + participant Bob + theCollection->>Bob: Collections request + Bob->>theCollection: Collections response +``` + +```mermaid-example +sequenceDiagram + collections theCollection + participant Bob + theCollection->>Bob: Collections request + Bob->>theCollection: Collections response +``` + +```mermaid +sequenceDiagram + collections theCollection + participant Bob + theCollection->>Bob: Collections request + Bob->>theCollection: Collections response +``` + +### Queue + +If you want to use the queue symbol for a participant, use the `queue` statement as shown below. + +```mermaid-example +sequenceDiagram + queue theQueue + participant Alice + theQueue->>Alice: Queue message + Alice->>theQueue: Queue +``` + +```mermaid +sequenceDiagram + queue theQueue + participant Alice + theQueue->>Alice: Queue message + Alice->>theQueue: Queue +``` + +```mermaid-example +sequenceDiagram + queue theQueue + participant Alice + theQueue->>Alice: Queue message + Alice->>theQueue: Queue +``` + +```mermaid +sequenceDiagram + queue theQueue + participant Alice + theQueue->>Alice: Queue message + Alice->>theQueue: Queue +``` + ### Aliases The actor can have a convenient identifier and a descriptive label. diff --git a/packages/mermaid/src/docs/syntax/sequenceDiagram.md b/packages/mermaid/src/docs/syntax/sequenceDiagram.md index 3087eb743..63c6bfc8f 100644 --- a/packages/mermaid/src/docs/syntax/sequenceDiagram.md +++ b/packages/mermaid/src/docs/syntax/sequenceDiagram.md @@ -46,6 +46,126 @@ sequenceDiagram Bob->>Alice: Hi Alice ``` +### Boundary + +If you want to use the boundary symbol for a participant, use the `boundary` statement as shown below. + +```mermaid-example +sequenceDiagram + boundary theBoundary + participant Bob + theBoundary->>Bob: Request from boundary + Bob->>theBoundary: Response to boundary +``` + +```mermaid +sequenceDiagram + boundary theBoundary + participant Bob + theBoundary->>Bob: Request from boundary + Bob->>theBoundary: Response to boundary +``` + +### Control + +If you want to use the control symbol for a participant, use the `control` statement as shown below. + +```mermaid-example +sequenceDiagram + control theControl + participant Alice + theControl->>Alice: Control request + Alice->>theControl: Control response +``` + +```mermaid +sequenceDiagram + control theControl + participant Alice + theControl->>Alice: Control request + Alice->>theControl: Control response +``` + +### Entity + +If you want to use the entity symbol for a participant, use the `entity` statement as shown below. + +```mermaid-example +sequenceDiagram + entity theEntity + participant Bob + theEntity->>Bob: Entity request + Bob->>theEntity: Entity response +``` + +```mermaid +sequenceDiagram + entity theEntity + participant Bob + theEntity->>Bob: Entity request + Bob->>theEntity: Entity response +``` + +### Database + +If you want to use the database symbol for a participant, use the `database` statement as shown below. + +```mermaid-example +sequenceDiagram + database theDb + participant Alice + theDb->>Alice: DB query + Alice->>theDb: DB result +``` + +```mermaid +sequenceDiagram + database theDb + participant Alice + theDb->>Alice: DB query + Alice->>theDb: DB result +``` + +### Collections + +If you want to use the collections symbol for a participant, use the `collections` statement as shown below. + +```mermaid-example +sequenceDiagram + collections theCollection + participant Bob + theCollection->>Bob: Collections request + Bob->>theCollection: Collections response +``` + +```mermaid +sequenceDiagram + collections theCollection + participant Bob + theCollection->>Bob: Collections request + Bob->>theCollection: Collections response +``` + +### Queue + +If you want to use the queue symbol for a participant, use the `queue` statement as shown below. + +```mermaid-example +sequenceDiagram + queue theQueue + participant Alice + theQueue->>Alice: Queue message + Alice->>theQueue: Queue +``` + +```mermaid +sequenceDiagram + queue theQueue + participant Alice + theQueue->>Alice: Queue message + Alice->>theQueue: Queue +``` + ### Aliases The actor can have a convenient identifier and a descriptive label. From 99dbeba40718daea7ae16a78d664ec2b87d6eb85 Mon Sep 17 00:00:00 2001 From: omkarht Date: Wed, 6 Aug 2025 17:10:55 +0530 Subject: [PATCH 11/23] fix: refactored code on-behalf-of: @Mermaid-Chart --- packages/mermaid/src/diagrams/sequence/sequenceDb.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDb.ts b/packages/mermaid/src/diagrams/sequence/sequenceDb.ts index 8b600c354..dc8a645fd 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDb.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceDb.ts @@ -348,7 +348,6 @@ export class SequenceDB implements DiagramDB { inputRemainder: string, lexer: any ): string { - log.info({ tokenName }); const arrowLike = /^\s*(->>|-->>|->|-->|<<->>|<<-->>|-x|--x|-\))/; const colonLike = /^\s*:/; From 327a5aa9fde535589f5c7e3ce0598527ca6abd0b Mon Sep 17 00:00:00 2001 From: omkarht Date: Thu, 7 Aug 2025 18:07:13 +0530 Subject: [PATCH 12/23] chore: added logs in sequenceDiagram.jison on-behalf-of: @Mermaid-Chart --- .../sequence/parser/sequenceDiagram.jison | 262 ++++++++++++------ 1 file changed, 179 insertions(+), 83 deletions(-) diff --git a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison index 359141b92..5902f76ba 100644 --- a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison +++ b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison @@ -15,88 +15,99 @@ // Special states for recognizing aliases // A special state for grabbing text up to the first comment/newline -%x ID ALIAS LINE +%x ID ALIAS LINE CONFIG CONFIG_DATA %x acc_title %x acc_descr %x acc_descr_multiline %% -[\n]+ return 'NEWLINE'; -\s+ /* skip all whitespace */ -((?!\n)\s)+ /* skip same-line whitespace */ -\#[^\n]* /* skip comments */ -\%%(?!\{)[^\n]* /* skip comments */ -[^\}]\%\%[^\n]* /* skip comments */ -[0-9]+(?=[ \n]+) return 'NUM'; -"box" { this.begin('LINE'); return 'box'; } -"participant" { this.begin('ID'); return 'participant'; } -"actor" { this.begin('ID'); return 'participant_actor'; } +[\n]+ { console.log("NEWLINE"); return 'NEWLINE'; } +\s+ { console.log("WHITESPACE"); /* skip whitespace */ } +((?!\n)\s)+ { console.log("SAME-LINE-WHITESPACE"); /* skip same-line whitespace */ } +\#[^\n]* { console.log("COMMENT"); /* skip comments */ } +\%%(?!\{)[^\n]* { console.log("COMMENT"); /* skip comments */ } +[^\}]\%\%[^\n]* { console.log("COMMENT"); /* skip comments */ } +[0-9]+(?=[ \n]+) { console.log("NUM:", yytext); return 'NUM'; } +[^\<->\->:\n,;@]+?([\-]*[^\<->\->:\n,;@]+?)*?(?=\@\{) { console.log("LEXER:ACTOR_WITH_CONFIG_OBJECT:", yytext); yytext = yytext.trim(); return 'ACTOR_WITH_CONFIG'; } +// Enhanced config handling rules +\@\{ { console.log("CONFIG_START"); this.begin('CONFIG'); return 'CONFIG_START'; } +[^\}]+ { console.log("CONFIG_CONTENT:", yytext); return 'CONFIG_CONTENT'; } +\} { console.log("CONFIG_END"); this.popState(); return 'CONFIG_END'; } +"box" { console.log("BOX"); this.begin('LINE'); return 'box'; } +"participant" { console.log("PARTICIPANT"); this.begin('ID'); return 'participant'; } +"actor" { console.log("ACTOR_TYPE_ACTOR"); this.begin('ID'); return 'participant_actor'; } "boundary" { return yy.matchAsActorOrParticipant('boundary', 'participant_boundary', this._input, this); } "control" { return yy.matchAsActorOrParticipant('control', 'participant_control', this._input, this); } "entity" { return yy.matchAsActorOrParticipant('entity', 'participant_entity', this._input, this); } "database" { return yy.matchAsActorOrParticipant('database', 'participant_database', this._input, this); } "collections" { return yy.matchAsActorOrParticipant('collections', 'participant_collections', this._input, this); } "queue" { return yy.matchAsActorOrParticipant('queue', 'participant_queue', this._input, this); } -"create" return 'create'; -"destroy" { this.begin('ID'); return 'destroy'; } -[^\<->\->:\n,;]+?([\-]*[^\<->\->:\n,;]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; } -"as" { this.popState(); this.popState(); this.begin('LINE'); return 'AS'; } -(?:) { this.popState(); this.popState(); return 'NEWLINE'; } -"loop" { this.begin('LINE'); return 'loop'; } -"rect" { this.begin('LINE'); return 'rect'; } -"opt" { this.begin('LINE'); return 'opt'; } -"alt" { this.begin('LINE'); return 'alt'; } -"else" { this.begin('LINE'); return 'else'; } -"par" { this.begin('LINE'); return 'par'; } -"par_over" { this.begin('LINE'); return 'par_over'; } -"and" { this.begin('LINE'); return 'and'; } -"critical" { this.begin('LINE'); return 'critical'; } -"option" { this.begin('LINE'); return 'option'; } -"break" { this.begin('LINE'); return 'break'; } -(?:[:]?(?:no)?wrap:)?[^#\n;]* { this.popState(); return 'restOfLine'; } -"end" return 'end'; -"left of" return 'left_of'; -"right of" return 'right_of'; -"links" return 'links'; -"link" return 'link'; -"properties" return 'properties'; -"details" return 'details'; -"over" return 'over'; -"note" return 'note'; -"activate" { this.begin('ID'); return 'activate'; } -"deactivate" { this.begin('ID'); return 'deactivate'; } -"title"\s[^#\n;]+ return 'title'; -"title:"\s[^#\n;]+ return 'legacy_title'; -accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; } -(?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; } -accDescr\s*":"\s* { this.begin("acc_descr");return 'acc_descr'; } -(?!\n|;|#)*[^\n]* { this.popState(); return "acc_descr_value"; } -accDescr\s*"{"\s* { this.begin("acc_descr_multiline");} -[\}] { this.popState(); } -[^\}]* return "acc_descr_multiline_value"; -"sequenceDiagram" return 'SD'; -"autonumber" return 'autonumber'; -"off" return 'off'; -"," return ','; -";" return 'NEWLINE'; -[^\+\<->\->:\n,;]+((?!(\-x|\-\-x|\-\)|\-\-\)))[\-]*[^\+\<->\->:\n,;]+)* { yytext = yytext.trim(); return 'ACTOR'; } -"->>" return 'SOLID_ARROW'; -"<<->>" return 'BIDIRECTIONAL_SOLID_ARROW'; -"-->>" return 'DOTTED_ARROW'; -"<<-->>" return 'BIDIRECTIONAL_DOTTED_ARROW'; -"->" return 'SOLID_OPEN_ARROW'; -"-->" return 'DOTTED_OPEN_ARROW'; -\-[x] return 'SOLID_CROSS'; -\-\-[x] return 'DOTTED_CROSS'; -\-[\)] return 'SOLID_POINT'; -\-\-[\)] return 'DOTTED_POINT'; -":"(?:(?:no)?wrap:)?[^#\n;]* return 'TXT'; -":" return 'TXT'; -"+" return '+'; -"-" return '-'; -<> return 'NEWLINE'; -. return 'INVALID'; +"create" { console.log("CREATE"); return 'create'; } +"destroy" { console.log("DESTROY"); this.begin('ID'); return 'destroy'; } +// Updated ID rules to handle @{...} config +// [^\<->\->:\n,;@]+?([\-]*[^\<->\->:\n,;@]+?)*?(?=\@\{) { console.log("LEXER:ACTOR_WITH_CONFIG:", yytext); yytext = yytext.trim(); return 'ACTOR_WITH_CONFIG'; } +[^\<->\->:\n,;]+?([\-]*[^\<->\->:\n,;]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { console.log("ACTOR:", yytext); yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; } +"as" { console.log("AS"); this.popState(); this.popState(); this.begin('LINE'); return 'AS'; } +(?:) { console.log("ALIAS_END"); this.popState(); this.popState(); return 'NEWLINE'; } +// // Enhanced config handling rules +// \@\{ { console.log("CONFIG_START"); this.begin('CONFIG'); return 'CONFIG_START'; } +// [^\}]+ { console.log("CONFIG_CONTENT:", yytext); return 'CONFIG_CONTENT'; } +// \} { console.log("CONFIG_END"); this.popState(); return 'CONFIG_END'; } +"loop" { console.log("LOOP"); this.begin('LINE'); return 'loop'; } +"rect" { console.log("RECT"); this.begin('LINE'); return 'rect'; } +"opt" { console.log("OPT"); this.begin('LINE'); return 'opt'; } +"alt" { console.log("ALT"); this.begin('LINE'); return 'alt'; } +"else" { console.log("ELSE"); this.begin('LINE'); return 'else'; } +"par" { console.log("PAR"); this.begin('LINE'); return 'par'; } +"par_over" { console.log("PAR_OVER"); this.begin('LINE'); return 'par_over'; } +"and" { console.log("AND"); this.begin('LINE'); return 'and'; } +"critical" { console.log("CRITICAL"); this.begin('LINE'); return 'critical'; } +"option" { console.log("OPTION"); this.begin('LINE'); return 'option'; } +"break" { console.log("BREAK"); this.begin('LINE'); return 'break'; } +(?:[:]?(?:no)?wrap:)?[^#\n;]* { console.log("REST_OF_LINE:", yytext); this.popState(); return 'restOfLine'; } +"end" { console.log("END"); return 'end'; } +"left of" { console.log("LEFT_OF"); return 'left_of'; } +"right of" { console.log("RIGHT_OF"); return 'right_of'; } +"links" { console.log("LINKS"); return 'links'; } +"link" { console.log("LINK"); return 'link'; } +"properties" { console.log("PROPERTIES"); return 'properties'; } +"details" { console.log("DETAILS"); return 'details'; } +"over" { console.log("OVER"); return 'over'; } +"note" { console.log("NOTE"); return 'note'; } +"activate" { console.log("ACTIVATE"); this.begin('ID'); return 'activate'; } +"deactivate" { console.log("DEACTIVATE"); this.begin('ID'); return 'deactivate'; } +"title"\s[^#\n;]+ { console.log("TITLE"); return 'title'; } +"title:"\s[^#\n;]+ { console.log("LEGACY_TITLE"); return 'legacy_title'; } +accTitle\s*":"\s* { console.log("ACC_TITLE"); this.begin("acc_title"); return 'acc_title'; } +(?!\n|;|#)*[^\n]* { console.log("ACC_TITLE_VALUE:", yytext); this.popState(); return "acc_title_value"; } +accDescr\s*":"\s* { console.log("ACC_DESCR"); this.begin("acc_descr"); return 'acc_descr'; } +(?!\n|;|#)*[^\n]* { console.log("ACC_DESCR_VALUE:", yytext); this.popState(); return "acc_descr_value"; } +accDescr\s*"{"\s* { console.log("ACC_DESCR_MULTILINE_START"); this.begin("acc_descr_multiline"); } +[\}] { console.log("ACC_DESCR_MULTILINE_END"); this.popState(); } +[^\}]* { console.log("ACC_DESCR_MULTILINE_VALUE:", yytext); return "acc_descr_multiline_value"; } +"sequenceDiagram" { console.log("SEQUENCE_DIAGRAM"); return 'SD'; } +"autonumber" { console.log("AUTONUMBER"); return 'autonumber'; } +"off" { console.log("OFF"); return 'off'; } +"," { console.log("COMMA"); return ','; } +";" { console.log("SEMICOLON"); return 'NEWLINE'; } +[^\+\<->\->:\n,;]+((?!(\-x|\-\-x|\-\)|\-\-\)))[\-]*[^\+\<->\->:\n,;]+)* { console.log("ACTOR_GENERIC:", yytext); yytext = yytext.trim(); return 'ACTOR'; } +"->>" { console.log("SOLID_ARROW"); return 'SOLID_ARROW'; } +"<<->>" { console.log("BIDIRECTIONAL_SOLID_ARROW"); return 'BIDIRECTIONAL_SOLID_ARROW'; } +"-->>" { console.log("DOTTED_ARROW"); return 'DOTTED_ARROW'; } +"<<-->>" { console.log("BIDIRECTIONAL_DOTTED_ARROW"); return 'BIDIRECTIONAL_DOTTED_ARROW'; } +"->" { console.log("SOLID_OPEN_ARROW"); return 'SOLID_OPEN_ARROW'; } +"-->" { console.log("DOTTED_OPEN_ARROW"); return 'DOTTED_OPEN_ARROW'; } +\-[x] { console.log("SOLID_CROSS"); return 'SOLID_CROSS'; } +\-\-[x] { console.log("DOTTED_CROSS"); return 'DOTTED_CROSS'; } +\-[\)] { console.log("SOLID_POINT"); return 'SOLID_POINT'; } +\-\-[\)] { console.log("DOTTED_POINT"); return 'DOTTED_POINT'; } +":"(?:(?:no)?wrap:)?[^#\n;]* { console.log("TEXT_WITH_WRAP:", yytext); return 'TXT'; } +":" { console.log("TEXT"); return 'TXT'; } +"+" { console.log("PLUS"); return '+'; } +"-" { console.log("MINUS"); return '-'; } +<> { console.log("EOF"); return 'NEWLINE'; } +. { console.log("INVALID:", yytext); return 'INVALID'; } /lex @@ -233,13 +244,40 @@ else_sections ; participant_statement - : 'participant' actor 'AS' restOfLine 'NEWLINE' {$2.draw='participant'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} - | 'participant' actor 'NEWLINE' {$2.draw='participant'; $2.type='addParticipant';$$=$2;} - | 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} - | 'participant_actor' actor 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant'; $$=$2;} - | 'destroy' actor 'NEWLINE' {$2.type='destroyParticipant'; $$=$2;} - - | 'participant_boundary' actor 'AS' restOfLine 'NEWLINE' {$2.draw='boundary'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} + : 'participant' actor_with_config 'AS' restOfLine 'NEWLINE' + { + console.log('Participant with config and alias:', $2, $4); + $2.draw = 'participant'; + $2.type = 'addParticipant'; + $2.description = yy.parseMessage($4); + $$ = $2; + } + | 'participant' actor_with_config 'NEWLINE' + { + console.log('Participant with config without alias:', $2); + $2.draw = 'participant'; + $2.type = 'addParticipant'; + $$ = $2; + } + | 'participant_actor' actor_with_config 'AS' restOfLine 'NEWLINE' + { + $2.draw = 'actor'; + $2.type = 'addParticipant'; + $2.description = yy.parseMessage($4); + $$ = $2; + } + | 'participant_actor' actor_with_config 'NEWLINE' + { + $2.draw = 'actor'; + $2.type = 'addParticipant'; + $$ = $2; + } + | 'destroy' actor 'NEWLINE' + { + $2.type = 'destroyParticipant'; + $$ = $2; + } + | 'participant_boundary' actor 'AS' restOfLine 'NEWLINE' {$2.draw='boundary'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} | 'participant_boundary' actor 'NEWLINE' {$2.draw='boundary'; $2.type='addParticipant'; $$=$2;} | 'participant_control' actor 'AS' restOfLine 'NEWLINE' {$2.draw='control'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} @@ -327,13 +365,71 @@ signal { $$ = [$1,$3,{type: 'addMessage', from:$1.actor, to:$3.actor, signalType:$2, msg:$4}]} ; -// actor -// : actor_participant -// | actor_actor -// ; +actor_with_config + : ACTOR_WITH_CONFIG config_object // Changed from ACTOR + { + console.log("ACTOR_WITH_CONFIG Actor with config:", $1, $2); + $$ = { + type: 'addParticipant', + actor: $1, + config: $2 + }; + } + | ACTOR config_object + { + console.log("Actor with config:", $1, $2); + $$ = { + type: 'addParticipant', + actor: $1, + config: $2 + }; + } + | ACTOR + { + console.log("Actor without config:", $1); + $$ = { + type: 'addParticipant', + actor: $1, + }; + } + ; -actor: ACTOR {$$={ type: 'addParticipant', actor:$1}}; -// actor_actor: ACTOR {$$={type: 'addActor', actor:$1}}; +config_object + : CONFIG_START CONFIG_CONTENT CONFIG_END + { + console.log("Parsing config content:", $2); + try { + // Remove any trailing whitespace/newlines + const content = $2.trim(); + $$ = JSON.parse(content); + console.log("Successfully parsed JSON config:", $$); + } catch (e) { + console.log("JSON parse failed, using raw content"); + $$ = $2.trim(); + } + } + ; +actor + : ACTOR_WITH_CONFIG // Add this case + { + console.log("Actor with config flag:", $1); + $$ = { type: 'addParticipant', actor: $1 }; + } + | actor config_object + { + console.log("Actor with config:", $1, $2); + $$ = { + type: 'addParticipant', + actor: $1.actor, + config: $2 + }; + } + | ACTOR + { + console.log("Basic actor:", $1); + $$ = { type: 'addParticipant', actor: $1 }; + } + ; signaltype : SOLID_OPEN_ARROW { $$ = yy.LINETYPE.SOLID_OPEN; } From 981829a426f1b7ee182a263c9316dd46295075ee Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Thu, 7 Aug 2025 15:21:13 +0200 Subject: [PATCH 13/23] Updated order of lexer statements in the grammar --- .../diagrams/sequence/parser/sequenceDiagram.jison | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison index 5902f76ba..442bec590 100644 --- a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison +++ b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison @@ -29,11 +29,14 @@ \%%(?!\{)[^\n]* { console.log("COMMENT"); /* skip comments */ } [^\}]\%\%[^\n]* { console.log("COMMENT"); /* skip comments */ } [0-9]+(?=[ \n]+) { console.log("NUM:", yytext); return 'NUM'; } -[^\<->\->:\n,;@]+?([\-]*[^\<->\->:\n,;@]+?)*?(?=\@\{) { console.log("LEXER:ACTOR_WITH_CONFIG_OBJECT:", yytext); yytext = yytext.trim(); return 'ACTOR_WITH_CONFIG'; } -// Enhanced config handling rules +// Enhanced config handling rules - moved before other ID rules for proper precedence \@\{ { console.log("CONFIG_START"); this.begin('CONFIG'); return 'CONFIG_START'; } [^\}]+ { console.log("CONFIG_CONTENT:", yytext); return 'CONFIG_CONTENT'; } -\} { console.log("CONFIG_END"); this.popState(); return 'CONFIG_END'; } +\} { console.log("CONFIG_END"); this.popState(); this.popState(); return 'CONFIG_END'; } +// ACTOR_WITH_CONFIG rule must come before general ACTOR rule for proper precedence +[^\<->\->:\n,;@]+?([\-]*[^\<->\->:\n,;@]+?)*?(?=\@\{) { console.log("LEXER:ACTOR_WITH_CONFIG_OBJECT:", yytext); yytext = yytext.trim(); return 'ACTOR_WITH_CONFIG'; } +// General ACTOR rule - now comes after the more specific ACTOR_WITH_CONFIG rule +[^\<->\->:\n,;@]+?([\-]*[^\<->\->:\n,;@]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { console.log("ACTOR:", yytext); yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; } "box" { console.log("BOX"); this.begin('LINE'); return 'box'; } "participant" { console.log("PARTICIPANT"); this.begin('ID'); return 'participant'; } "actor" { console.log("ACTOR_TYPE_ACTOR"); this.begin('ID'); return 'participant_actor'; } @@ -45,9 +48,6 @@ "queue" { return yy.matchAsActorOrParticipant('queue', 'participant_queue', this._input, this); } "create" { console.log("CREATE"); return 'create'; } "destroy" { console.log("DESTROY"); this.begin('ID'); return 'destroy'; } -// Updated ID rules to handle @{...} config -// [^\<->\->:\n,;@]+?([\-]*[^\<->\->:\n,;@]+?)*?(?=\@\{) { console.log("LEXER:ACTOR_WITH_CONFIG:", yytext); yytext = yytext.trim(); return 'ACTOR_WITH_CONFIG'; } -[^\<->\->:\n,;]+?([\-]*[^\<->\->:\n,;]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { console.log("ACTOR:", yytext); yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; } "as" { console.log("AS"); this.popState(); this.popState(); this.begin('LINE'); return 'AS'; } (?:) { console.log("ALIAS_END"); this.popState(); this.popState(); return 'NEWLINE'; } // // Enhanced config handling rules From 677ff82d1359cd85058575dd0241135bcbe39beb Mon Sep 17 00:00:00 2001 From: omkarht Date: Thu, 7 Aug 2025 20:45:23 +0530 Subject: [PATCH 14/23] chore: implemented new syntx on-behalf-of: @Mermaid-Chart --- .../sequence/parser/sequenceDiagram.jison | 277 ++++++------------ .../src/diagrams/sequence/sequenceDb.ts | 36 ++- .../diagrams/sequence/sequenceDiagram.spec.js | 95 ++---- packages/mermaid/src/types.ts | 12 + 4 files changed, 150 insertions(+), 270 deletions(-) diff --git a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison index 442bec590..87ef8212f 100644 --- a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison +++ b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison @@ -12,7 +12,6 @@ %options case-insensitive - // Special states for recognizing aliases // A special state for grabbing text up to the first comment/newline %x ID ALIAS LINE CONFIG CONFIG_DATA @@ -22,92 +21,79 @@ %x acc_descr_multiline %% -[\n]+ { console.log("NEWLINE"); return 'NEWLINE'; } -\s+ { console.log("WHITESPACE"); /* skip whitespace */ } -((?!\n)\s)+ { console.log("SAME-LINE-WHITESPACE"); /* skip same-line whitespace */ } -\#[^\n]* { console.log("COMMENT"); /* skip comments */ } -\%%(?!\{)[^\n]* { console.log("COMMENT"); /* skip comments */ } -[^\}]\%\%[^\n]* { console.log("COMMENT"); /* skip comments */ } -[0-9]+(?=[ \n]+) { console.log("NUM:", yytext); return 'NUM'; } -// Enhanced config handling rules - moved before other ID rules for proper precedence -\@\{ { console.log("CONFIG_START"); this.begin('CONFIG'); return 'CONFIG_START'; } -[^\}]+ { console.log("CONFIG_CONTENT:", yytext); return 'CONFIG_CONTENT'; } -\} { console.log("CONFIG_END"); this.popState(); this.popState(); return 'CONFIG_END'; } -// ACTOR_WITH_CONFIG rule must come before general ACTOR rule for proper precedence -[^\<->\->:\n,;@]+?([\-]*[^\<->\->:\n,;@]+?)*?(?=\@\{) { console.log("LEXER:ACTOR_WITH_CONFIG_OBJECT:", yytext); yytext = yytext.trim(); return 'ACTOR_WITH_CONFIG'; } -// General ACTOR rule - now comes after the more specific ACTOR_WITH_CONFIG rule -[^\<->\->:\n,;@]+?([\-]*[^\<->\->:\n,;@]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { console.log("ACTOR:", yytext); yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; } -"box" { console.log("BOX"); this.begin('LINE'); return 'box'; } -"participant" { console.log("PARTICIPANT"); this.begin('ID'); return 'participant'; } -"actor" { console.log("ACTOR_TYPE_ACTOR"); this.begin('ID'); return 'participant_actor'; } -"boundary" { return yy.matchAsActorOrParticipant('boundary', 'participant_boundary', this._input, this); } -"control" { return yy.matchAsActorOrParticipant('control', 'participant_control', this._input, this); } -"entity" { return yy.matchAsActorOrParticipant('entity', 'participant_entity', this._input, this); } -"database" { return yy.matchAsActorOrParticipant('database', 'participant_database', this._input, this); } -"collections" { return yy.matchAsActorOrParticipant('collections', 'participant_collections', this._input, this); } -"queue" { return yy.matchAsActorOrParticipant('queue', 'participant_queue', this._input, this); } -"create" { console.log("CREATE"); return 'create'; } -"destroy" { console.log("DESTROY"); this.begin('ID'); return 'destroy'; } -"as" { console.log("AS"); this.popState(); this.popState(); this.begin('LINE'); return 'AS'; } -(?:) { console.log("ALIAS_END"); this.popState(); this.popState(); return 'NEWLINE'; } -// // Enhanced config handling rules -// \@\{ { console.log("CONFIG_START"); this.begin('CONFIG'); return 'CONFIG_START'; } -// [^\}]+ { console.log("CONFIG_CONTENT:", yytext); return 'CONFIG_CONTENT'; } -// \} { console.log("CONFIG_END"); this.popState(); return 'CONFIG_END'; } -"loop" { console.log("LOOP"); this.begin('LINE'); return 'loop'; } -"rect" { console.log("RECT"); this.begin('LINE'); return 'rect'; } -"opt" { console.log("OPT"); this.begin('LINE'); return 'opt'; } -"alt" { console.log("ALT"); this.begin('LINE'); return 'alt'; } -"else" { console.log("ELSE"); this.begin('LINE'); return 'else'; } -"par" { console.log("PAR"); this.begin('LINE'); return 'par'; } -"par_over" { console.log("PAR_OVER"); this.begin('LINE'); return 'par_over'; } -"and" { console.log("AND"); this.begin('LINE'); return 'and'; } -"critical" { console.log("CRITICAL"); this.begin('LINE'); return 'critical'; } -"option" { console.log("OPTION"); this.begin('LINE'); return 'option'; } -"break" { console.log("BREAK"); this.begin('LINE'); return 'break'; } -(?:[:]?(?:no)?wrap:)?[^#\n;]* { console.log("REST_OF_LINE:", yytext); this.popState(); return 'restOfLine'; } -"end" { console.log("END"); return 'end'; } -"left of" { console.log("LEFT_OF"); return 'left_of'; } -"right of" { console.log("RIGHT_OF"); return 'right_of'; } -"links" { console.log("LINKS"); return 'links'; } -"link" { console.log("LINK"); return 'link'; } -"properties" { console.log("PROPERTIES"); return 'properties'; } -"details" { console.log("DETAILS"); return 'details'; } -"over" { console.log("OVER"); return 'over'; } -"note" { console.log("NOTE"); return 'note'; } -"activate" { console.log("ACTIVATE"); this.begin('ID'); return 'activate'; } -"deactivate" { console.log("DEACTIVATE"); this.begin('ID'); return 'deactivate'; } -"title"\s[^#\n;]+ { console.log("TITLE"); return 'title'; } -"title:"\s[^#\n;]+ { console.log("LEGACY_TITLE"); return 'legacy_title'; } -accTitle\s*":"\s* { console.log("ACC_TITLE"); this.begin("acc_title"); return 'acc_title'; } -(?!\n|;|#)*[^\n]* { console.log("ACC_TITLE_VALUE:", yytext); this.popState(); return "acc_title_value"; } -accDescr\s*":"\s* { console.log("ACC_DESCR"); this.begin("acc_descr"); return 'acc_descr'; } -(?!\n|;|#)*[^\n]* { console.log("ACC_DESCR_VALUE:", yytext); this.popState(); return "acc_descr_value"; } -accDescr\s*"{"\s* { console.log("ACC_DESCR_MULTILINE_START"); this.begin("acc_descr_multiline"); } -[\}] { console.log("ACC_DESCR_MULTILINE_END"); this.popState(); } -[^\}]* { console.log("ACC_DESCR_MULTILINE_VALUE:", yytext); return "acc_descr_multiline_value"; } -"sequenceDiagram" { console.log("SEQUENCE_DIAGRAM"); return 'SD'; } -"autonumber" { console.log("AUTONUMBER"); return 'autonumber'; } -"off" { console.log("OFF"); return 'off'; } -"," { console.log("COMMA"); return ','; } -";" { console.log("SEMICOLON"); return 'NEWLINE'; } -[^\+\<->\->:\n,;]+((?!(\-x|\-\-x|\-\)|\-\-\)))[\-]*[^\+\<->\->:\n,;]+)* { console.log("ACTOR_GENERIC:", yytext); yytext = yytext.trim(); return 'ACTOR'; } -"->>" { console.log("SOLID_ARROW"); return 'SOLID_ARROW'; } -"<<->>" { console.log("BIDIRECTIONAL_SOLID_ARROW"); return 'BIDIRECTIONAL_SOLID_ARROW'; } -"-->>" { console.log("DOTTED_ARROW"); return 'DOTTED_ARROW'; } -"<<-->>" { console.log("BIDIRECTIONAL_DOTTED_ARROW"); return 'BIDIRECTIONAL_DOTTED_ARROW'; } -"->" { console.log("SOLID_OPEN_ARROW"); return 'SOLID_OPEN_ARROW'; } -"-->" { console.log("DOTTED_OPEN_ARROW"); return 'DOTTED_OPEN_ARROW'; } -\-[x] { console.log("SOLID_CROSS"); return 'SOLID_CROSS'; } -\-\-[x] { console.log("DOTTED_CROSS"); return 'DOTTED_CROSS'; } -\-[\)] { console.log("SOLID_POINT"); return 'SOLID_POINT'; } -\-\-[\)] { console.log("DOTTED_POINT"); return 'DOTTED_POINT'; } -":"(?:(?:no)?wrap:)?[^#\n;]* { console.log("TEXT_WITH_WRAP:", yytext); return 'TXT'; } -":" { console.log("TEXT"); return 'TXT'; } -"+" { console.log("PLUS"); return '+'; } -"-" { console.log("MINUS"); return '-'; } -<> { console.log("EOF"); return 'NEWLINE'; } -. { console.log("INVALID:", yytext); return 'INVALID'; } +[\n]+ return 'NEWLINE'; +\s+ /* skip all whitespace */ +((?!\n)\s)+ /* skip same-line whitespace */ +\#[^\n]* /* skip comments */ +\%%(?!\{)[^\n]* /* skip comments */ +[^\}]\%\%[^\n]* /* skip comments */ +[0-9]+(?=[ \n]+) return 'NUM'; +\@\{ { this.begin('CONFIG'); return 'CONFIG_START'; } +[^\}]+ { return 'CONFIG_CONTENT'; } +\} { this.popState(); this.popState(); return 'CONFIG_END'; } +[^\<->\->:\n,;@]+?([\-]*[^\<->\->:\n,;@]+?)*?(?=\@\{) { yytext = yytext.trim(); return 'ACTOR'; } +[^\<->\->:\n,;@]+?([\-]*[^\<->\->:\n,;@]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; } +"box" { this.begin('LINE'); return 'box'; } +"participant" { this.begin('ID'); return 'participant'; } +"actor" { this.begin('ID'); return 'participant_actor'; } +"create" return 'create'; +"destroy" { this.begin('ID'); return 'destroy'; } +"as" { this.popState(); this.popState(); this.begin('LINE'); return 'AS'; } +(?:) { this.popState(); this.popState(); return 'NEWLINE'; } +"loop" { this.begin('LINE'); return 'loop'; } +"rect" { this.begin('LINE'); return 'rect'; } +"opt" { this.begin('LINE'); return 'opt'; } +"alt" { this.begin('LINE'); return 'alt'; } +"else" { this.begin('LINE'); return 'else'; } +"par" { this.begin('LINE'); return 'par'; } +"par_over" { this.begin('LINE'); return 'par_over'; } +"and" { this.begin('LINE'); return 'and'; } +"critical" { this.begin('LINE'); return 'critical'; } +"option" { this.begin('LINE'); return 'option'; } +"break" { this.begin('LINE'); return 'break'; } +(?:[:]?(?:no)?wrap:)?[^#\n;]* { this.popState(); return 'restOfLine'; } +"end" return 'end'; +"left of" return 'left_of'; +"right of" return 'right_of'; +"links" return 'links'; +"link" return 'link'; +"properties" return 'properties'; +"details" return 'details'; +"over" return 'over'; +"note" return 'note'; +"activate" { this.begin('ID'); return 'activate'; } +"deactivate" { this.begin('ID'); return 'deactivate'; } +"title"\s[^#\n;]+ return 'title'; +"title:"\s[^#\n;]+ return 'legacy_title'; +accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; } +(?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; } +accDescr\s*":"\s* { this.begin("acc_descr");return 'acc_descr'; } +(?!\n|;|#)*[^\n]* { this.popState(); return "acc_descr_value"; } +accDescr\s*"{"\s* { this.begin("acc_descr_multiline");} +[\}] { this.popState(); } +[^\}]* return "acc_descr_multiline_value"; +"sequenceDiagram" return 'SD'; +"autonumber" return 'autonumber'; +"off" return 'off'; +"," return ','; +";" return 'NEWLINE'; +[^\+\<->\->:\n,;]+((?!(\-x|\-\-x|\-\)|\-\-\)))[\-]*[^\+\<->\->:\n,;]+)* { yytext = yytext.trim(); return 'ACTOR'; } +"->>" return 'SOLID_ARROW'; +"<<->>" return 'BIDIRECTIONAL_SOLID_ARROW'; +"-->>" return 'DOTTED_ARROW'; +"<<-->>" return 'BIDIRECTIONAL_DOTTED_ARROW'; +"->" return 'SOLID_OPEN_ARROW'; +"-->" return 'DOTTED_OPEN_ARROW'; +\-[x] return 'SOLID_CROSS'; +\-\-[x] return 'DOTTED_CROSS'; +\-[\)] return 'SOLID_POINT'; +\-\-[\)] return 'DOTTED_POINT'; +":"(?:(?:no)?wrap:)?[^#\n;]* return 'TXT'; +":" return 'TXT'; +"+" return '+'; +"-" return '-'; +<> return 'NEWLINE'; +. return 'INVALID'; /lex @@ -244,56 +230,12 @@ else_sections ; participant_statement - : 'participant' actor_with_config 'AS' restOfLine 'NEWLINE' - { - console.log('Participant with config and alias:', $2, $4); - $2.draw = 'participant'; - $2.type = 'addParticipant'; - $2.description = yy.parseMessage($4); - $$ = $2; - } - | 'participant' actor_with_config 'NEWLINE' - { - console.log('Participant with config without alias:', $2); - $2.draw = 'participant'; - $2.type = 'addParticipant'; - $$ = $2; - } - | 'participant_actor' actor_with_config 'AS' restOfLine 'NEWLINE' - { - $2.draw = 'actor'; - $2.type = 'addParticipant'; - $2.description = yy.parseMessage($4); - $$ = $2; - } - | 'participant_actor' actor_with_config 'NEWLINE' - { - $2.draw = 'actor'; - $2.type = 'addParticipant'; - $$ = $2; - } - | 'destroy' actor 'NEWLINE' - { - $2.type = 'destroyParticipant'; - $$ = $2; - } - | 'participant_boundary' actor 'AS' restOfLine 'NEWLINE' {$2.draw='boundary'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} - | 'participant_boundary' actor 'NEWLINE' {$2.draw='boundary'; $2.type='addParticipant'; $$=$2;} - - | 'participant_control' actor 'AS' restOfLine 'NEWLINE' {$2.draw='control'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} - | 'participant_control' actor 'NEWLINE' {$2.draw='control'; $2.type='addParticipant'; $$=$2;} - - | 'participant_entity' actor 'AS' restOfLine 'NEWLINE' {$2.draw='entity'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} - | 'participant_entity' actor 'NEWLINE' {$2.draw='entity'; $2.type='addParticipant'; $$=$2;} - - | 'participant_database' actor 'AS' restOfLine 'NEWLINE' {$2.draw='database'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} - | 'participant_database' actor 'NEWLINE' {$2.draw='database'; $2.type='addParticipant'; $$=$2;} - - | 'participant_collections' actor 'AS' restOfLine 'NEWLINE' {$2.draw='collections'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} - | 'participant_collections' actor 'NEWLINE' {$2.draw='collections'; $2.type='addParticipant'; $$=$2;} - - | 'participant_queue' actor 'AS' restOfLine 'NEWLINE' {$2.draw='queue'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} - | 'participant_queue' actor 'NEWLINE' {$2.draw='queue'; $2.type='addParticipant'; $$=$2;} + : 'participant' actor 'AS' restOfLine 'NEWLINE' {$2.draw='participant'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} + | 'participant' actor 'NEWLINE' {$2.draw='participant'; $2.type='addParticipant';$$=$2;} + | 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} + | 'participant_actor' actor 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant'; $$=$2;} + | 'destroy' actor 'NEWLINE' {$2.type='destroyParticipant'; $$=$2;} + | 'participant' actor_with_config 'NEWLINE' { $2.draw = 'participant'; $2.type = 'addParticipant'; $$ = $2;} ; @@ -366,76 +308,35 @@ signal ; actor_with_config - : ACTOR_WITH_CONFIG config_object // Changed from ACTOR + : ACTOR config_object { - console.log("ACTOR_WITH_CONFIG Actor with config:", $1, $2); $$ = { type: 'addParticipant', actor: $1, config: $2 }; } - | ACTOR config_object - { - console.log("Actor with config:", $1, $2); - $$ = { - type: 'addParticipant', - actor: $1, - config: $2 - }; - } - | ACTOR - { - console.log("Actor without config:", $1); - $$ = { - type: 'addParticipant', - actor: $1, - }; - } ; config_object : CONFIG_START CONFIG_CONTENT CONFIG_END { - console.log("Parsing config content:", $2); - try { - // Remove any trailing whitespace/newlines - const content = $2.trim(); - $$ = JSON.parse(content); - console.log("Successfully parsed JSON config:", $$); - } catch (e) { - console.log("JSON parse failed, using raw content"); - $$ = $2.trim(); - } - } - ; -actor - : ACTOR_WITH_CONFIG // Add this case - { - console.log("Actor with config flag:", $1); - $$ = { type: 'addParticipant', actor: $1 }; - } - | actor config_object - { - console.log("Actor with config:", $1, $2); - $$ = { - type: 'addParticipant', - actor: $1.actor, - config: $2 - }; - } - | ACTOR - { - console.log("Basic actor:", $1); - $$ = { type: 'addParticipant', actor: $1 }; + $$ = $2.trim(); } ; +// actor +// : actor_participant +// | actor_actor +// ; + +actor: ACTOR {$$={ type: 'addParticipant', actor:$1}}; +// actor_actor: ACTOR {$$={type: 'addActor', actor:$1}}; signaltype : SOLID_OPEN_ARROW { $$ = yy.LINETYPE.SOLID_OPEN; } | DOTTED_OPEN_ARROW { $$ = yy.LINETYPE.DOTTED_OPEN; } | SOLID_ARROW { $$ = yy.LINETYPE.SOLID; } - | BIDIRECTIONAL_SOLID_ARROW { $$ = yy.LINETYPE.BIDIRECTIONAL_SOLID; } + | BIDIRECTIONAL_SOLID_ARROW { $$ = yy.LINETYPE.BIDIRECTIONAL_SOLID; } | DOTTED_ARROW { $$ = yy.LINETYPE.DOTTED; } | BIDIRECTIONAL_DOTTED_ARROW { $$ = yy.LINETYPE.BIDIRECTIONAL_DOTTED; } | SOLID_CROSS { $$ = yy.LINETYPE.SOLID_CROSS; } diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDb.ts b/packages/mermaid/src/diagrams/sequence/sequenceDb.ts index dc8a645fd..4f06a1ebe 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDb.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceDb.ts @@ -1,4 +1,5 @@ import { getConfig } from '../../diagram-api/diagramAPI.js'; +import * as yaml from 'js-yaml'; import type { DiagramDB } from '../../diagram-api/types.js'; import { log } from '../../logger.js'; import { ImperativeState } from '../../utils/imperativeState.js'; @@ -13,6 +14,7 @@ import { setDiagramTitle, } from '../common/commonDb.js'; import type { Actor, AddMessageParams, Box, Message, Note } from './types.js'; +import type { ParticipantMetaData } from '../../types.js'; interface SequenceState { prevActor?: string; @@ -107,7 +109,6 @@ export class SequenceDB implements DiagramDB { this.apply = this.apply.bind(this); this.parseBoxData = this.parseBoxData.bind(this); this.parseMessage = this.parseMessage.bind(this); - this.matchAsActorOrParticipant = this.matchAsActorOrParticipant.bind(this); this.clear(); @@ -131,9 +132,22 @@ export class SequenceDB implements DiagramDB { id: string, name: string, description: { text: string; wrap?: boolean | null; type: string }, - type: string + type: string, + metadata?: any ) { let assignedBox = this.state.records.currentBox; + let doc; + if (metadata !== undefined) { + let yamlData; + // detect if shapeData contains a newline character + if (!metadata.includes('\n')) { + yamlData = '{\n' + metadata + '\n}'; + } else { + yamlData = metadata + '\n'; + } + doc = yaml.load(yamlData, { schema: yaml.JSON_SCHEMA }) as ParticipantMetaData; + } + type = doc?.type ?? type; const old = this.state.records.actors.get(id); if (old) { // If already set and trying to set to a new one throw error @@ -342,22 +356,6 @@ export class SequenceDB implements DiagramDB { return message; } - public matchAsActorOrParticipant( - tokenName: string, - tokenType: string, - inputRemainder: string, - lexer: any - ): string { - const arrowLike = /^\s*(->>|-->>|->|-->|<<->>|<<-->>|-x|--x|-\))/; - const colonLike = /^\s*:/; - - if (arrowLike.test(inputRemainder) || colonLike.test(inputRemainder)) { - return 'ACTOR'; - } - lexer.begin('ID'); // used the passed lexer - return tokenType; - } - // We expect the box statement to be color first then description // The color can be rgb,rgba,hsl,hsla, or css code names #hex codes are not supported for now because of the way the char # is handled // We extract first segment as color, the rest of the line is considered as text @@ -546,7 +544,7 @@ export class SequenceDB implements DiagramDB { }); break; case 'addParticipant': - this.addActor(param.actor, param.actor, param.description, param.draw); + this.addActor(param.actor, param.actor, param.description, param.draw, param.config); break; case 'createParticipant': if (this.state.records.actors.has(param.actor)) { diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js index 7bd278c23..8088f3c44 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js +++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js @@ -2043,8 +2043,8 @@ Bob->>Alice:Got it! const actor1 = 'database'; const diagram = await Diagram.fromText(` sequenceDiagram - database Alice - database Bob + participant Alice@{ "type" : "database" } + participant Bob@{ "type" : "database" } Bob->>+Alice: Hi Alice Alice->>+Bob: Hi Bob `); @@ -2057,10 +2057,10 @@ Bob->>Alice:Got it! const diagram = await Diagram.fromText(` sequenceDiagram participant lead - queue dsa + participant dsa@{ "type" : "queue" } API->>+Database: getUserb Database-->>-API: userb - queue --> Database: hello + dsa --> Database: hello `); const messages = diagram.db.getMessages(); @@ -2070,67 +2070,67 @@ Bob->>Alice:Got it! it('should parse boundary participant', async () => { const diagram = await Diagram.fromText(` sequenceDiagram - boundary B as Boundary Box - B->B: test + participant boundary@{ "type" : "boundary" } + boundary->boundary: test `); const actors = diagram.db.getActors(); - expect(actors.get('B').type).toBe('boundary'); - expect(actors.get('B').description).toBe('Boundary Box'); + expect(actors.get('boundary').type).toBe('boundary'); + expect(actors.get('boundary').description).toBe('boundary'); }); it('should parse control participant', async () => { const diagram = await Diagram.fromText(` sequenceDiagram - control C as Controller + participant C@{ "type" : "control" } C->C: test `); const actors = diagram.db.getActors(); expect(actors.get('C').type).toBe('control'); - expect(actors.get('C').description).toBe('Controller'); + expect(actors.get('C').description).toBe('C'); }); it('should parse entity participant', async () => { const diagram = await Diagram.fromText(` sequenceDiagram - entity E as Entity + participant E@{ "type" : "entity" } E->E: test `); const actors = diagram.db.getActors(); expect(actors.get('E').type).toBe('entity'); - expect(actors.get('E').description).toBe('Entity'); + expect(actors.get('E').description).toBe('E'); }); it('should parse database participant', async () => { const diagram = await Diagram.fromText(` sequenceDiagram - database D as Database + participant D@{ "type" : "database" } D->D: test `); const actors = diagram.db.getActors(); expect(actors.get('D').type).toBe('database'); - expect(actors.get('D').description).toBe('Database'); + expect(actors.get('D').description).toBe('D'); }); it('should parse collections participant', async () => { const diagram = await Diagram.fromText(` sequenceDiagram - collections L as List + participant L@{ "type" : "collections" } L->L: test `); const actors = diagram.db.getActors(); expect(actors.get('L').type).toBe('collections'); - expect(actors.get('L').description).toBe('List'); + expect(actors.get('L').description).toBe('L'); }); it('should parse queue participant', async () => { const diagram = await Diagram.fromText(` sequenceDiagram - queue Q as Jobs + participant Q@{ "type" : "queue" } Q->Q: test `); const actors = diagram.db.getActors(); expect(actors.get('Q').type).toBe('queue'); - expect(actors.get('Q').description).toBe('Jobs'); + expect(actors.get('Q').description).toBe('Q'); }); }); @@ -2138,89 +2138,58 @@ Bob->>Alice:Got it! it('should parse actor participant', async () => { const diagram = await Diagram.fromText(` sequenceDiagram - queue A as ActorName + participant A@{ "type" : "queue" } A->A: test `); const actors = diagram.db.getActors(); expect(actors.get('A').type).toBe('queue'); - expect(actors.get('A').description).toBe('ActorName'); + expect(actors.get('A').description).toBe('A'); }); it('should parse participant participant', async () => { const diagram = await Diagram.fromText(` sequenceDiagram - database P as PartName + participant P@{ "type" : "database" } P->P: test `); const actors = diagram.db.getActors(); expect(actors.get('P').type).toBe('database'); - expect(actors.get('P').description).toBe('PartName'); + expect(actors.get('P').description).toBe('P'); }); it('should parse boundary using actor keyword', async () => { const diagram = await Diagram.fromText(` sequenceDiagram - collections B as Boundary - B->B: test + participant Alice@{ "type" : "collections" } + participant Bob@{ "type" : "control" } + Alice->>Bob: Hello Bob, how are you? `); const actors = diagram.db.getActors(); - expect(actors.get('B').type).toBe('collections'); - expect(actors.get('B').description).toBe('Boundary'); + expect(actors.get('Alice').type).toBe('collections'); + expect(actors.get('Bob').type).toBe('control'); + expect(actors.get('Bob').description).toBe('Bob'); }); it('should parse control using participant keyword', async () => { const diagram = await Diagram.fromText(` sequenceDiagram - control C as Controller + participant C@{ "type" : "control" } C->C: test `); const actors = diagram.db.getActors(); expect(actors.get('C').type).toBe('control'); - expect(actors.get('C').description).toBe('Controller'); + expect(actors.get('C').description).toBe('C'); }); it('should parse entity using actor keyword', async () => { const diagram = await Diagram.fromText(` sequenceDiagram - entity E as Entity + participant E@{ "type" : "entity" } E->E: test `); const actors = diagram.db.getActors(); expect(actors.get('E').type).toBe('entity'); - expect(actors.get('E').description).toBe('Entity'); - }); - - it('should parse database using participant keyword', async () => { - const diagram = await Diagram.fromText(` - sequenceDiagram - participant D as Database - D->D: test - `); - const actors = diagram.db.getActors(); - expect(actors.get('D').type).toBe('participant'); - expect(actors.get('D').description).toBe('Database'); - }); - - it('should parse collections using actor keyword', async () => { - const diagram = await Diagram.fromText(` - sequenceDiagram - actor L as List - L->L: test - `); - const actors = diagram.db.getActors(); - expect(actors.get('L').type).toBe('actor'); - expect(actors.get('L').description).toBe('List'); - }); - - it('should parse queue using participant keyword', async () => { - const diagram = await Diagram.fromText(` - sequenceDiagram - participant Q as Jobs - Q->Q: test - `); - const actors = diagram.db.getActors(); - expect(actors.get('Q').type).toBe('participant'); - expect(actors.get('Q').description).toBe('Jobs'); + expect(actors.get('E').description).toBe('E'); }); }); }); diff --git a/packages/mermaid/src/types.ts b/packages/mermaid/src/types.ts index d1394e71b..477fb17b1 100644 --- a/packages/mermaid/src/types.ts +++ b/packages/mermaid/src/types.ts @@ -13,6 +13,18 @@ export interface NodeMetaData { ticket?: string; } +export interface ParticipantMetaData { + type?: + | 'actor' + | 'participant' + | 'boundary' + | 'control' + | 'entity' + | 'database' + | 'collections' + | 'queue'; +} + export interface EdgeMetaData { animation?: 'fast' | 'slow'; animate?: boolean; From bf362673fcb7397289dbdd7b3ce674f3788c7960 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 7 Aug 2025 15:26:12 +0000 Subject: [PATCH 15/23] [autofix.ci] apply automated fixes --- docs/config/setup/mermaid/interfaces/ParseOptions.md | 4 ++-- docs/config/setup/mermaid/interfaces/ParseResult.md | 6 +++--- docs/config/setup/mermaid/interfaces/RenderResult.md | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/config/setup/mermaid/interfaces/ParseOptions.md b/docs/config/setup/mermaid/interfaces/ParseOptions.md index ea96f2706..e068a91fb 100644 --- a/docs/config/setup/mermaid/interfaces/ParseOptions.md +++ b/docs/config/setup/mermaid/interfaces/ParseOptions.md @@ -10,7 +10,7 @@ # Interface: ParseOptions -Defined in: [packages/mermaid/src/types.ts:72](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L72) +Defined in: [packages/mermaid/src/types.ts:84](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L84) ## Properties @@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:72](https://github.com/mermaid-js/mer > `optional` **suppressErrors**: `boolean` -Defined in: [packages/mermaid/src/types.ts:77](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L77) +Defined in: [packages/mermaid/src/types.ts:89](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L89) If `true`, parse will return `false` instead of throwing error when the diagram is invalid. The `parseError` function will not be called. diff --git a/docs/config/setup/mermaid/interfaces/ParseResult.md b/docs/config/setup/mermaid/interfaces/ParseResult.md index 7a5990610..1651a6fa9 100644 --- a/docs/config/setup/mermaid/interfaces/ParseResult.md +++ b/docs/config/setup/mermaid/interfaces/ParseResult.md @@ -10,7 +10,7 @@ # Interface: ParseResult -Defined in: [packages/mermaid/src/types.ts:80](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L80) +Defined in: [packages/mermaid/src/types.ts:92](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L92) ## Properties @@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:80](https://github.com/mermaid-js/mer > **config**: [`MermaidConfig`](MermaidConfig.md) -Defined in: [packages/mermaid/src/types.ts:88](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L88) +Defined in: [packages/mermaid/src/types.ts:100](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L100) The config passed as YAML frontmatter or directives @@ -28,6 +28,6 @@ The config passed as YAML frontmatter or directives > **diagramType**: `string` -Defined in: [packages/mermaid/src/types.ts:84](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L84) +Defined in: [packages/mermaid/src/types.ts:96](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L96) The diagram type, e.g. 'flowchart', 'sequence', etc. diff --git a/docs/config/setup/mermaid/interfaces/RenderResult.md b/docs/config/setup/mermaid/interfaces/RenderResult.md index fc5fac4f5..c0e5496b8 100644 --- a/docs/config/setup/mermaid/interfaces/RenderResult.md +++ b/docs/config/setup/mermaid/interfaces/RenderResult.md @@ -10,7 +10,7 @@ # Interface: RenderResult -Defined in: [packages/mermaid/src/types.ts:98](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L98) +Defined in: [packages/mermaid/src/types.ts:110](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L110) ## Properties @@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:98](https://github.com/mermaid-js/mer > `optional` **bindFunctions**: (`element`) => `void` -Defined in: [packages/mermaid/src/types.ts:116](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L116) +Defined in: [packages/mermaid/src/types.ts:128](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L128) Bind function to be called after the svg has been inserted into the DOM. This is necessary for adding event listeners to the elements in the svg. @@ -45,7 +45,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present. > **diagramType**: `string` -Defined in: [packages/mermaid/src/types.ts:106](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L106) +Defined in: [packages/mermaid/src/types.ts:118](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L118) The diagram type, e.g. 'flowchart', 'sequence', etc. @@ -55,6 +55,6 @@ The diagram type, e.g. 'flowchart', 'sequence', etc. > **svg**: `string` -Defined in: [packages/mermaid/src/types.ts:102](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L102) +Defined in: [packages/mermaid/src/types.ts:114](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L114) The svg code for the rendered graph. From fb6ace73b53df309cd596b6c8c77dcc41d4a8196 Mon Sep 17 00:00:00 2001 From: omkarht Date: Fri, 8 Aug 2025 13:30:00 +0530 Subject: [PATCH 16/23] docs: updated documentation for new syntax on-behalf-of: @Mermaid-Chart --- docs/syntax/sequenceDiagram.md | 180 +++++++++--------- .../src/docs/syntax/sequenceDiagram.md | 96 +++++----- 2 files changed, 138 insertions(+), 138 deletions(-) diff --git a/docs/syntax/sequenceDiagram.md b/docs/syntax/sequenceDiagram.md index b197f402b..32afe5a30 100644 --- a/docs/syntax/sequenceDiagram.md +++ b/docs/syntax/sequenceDiagram.md @@ -76,218 +76,218 @@ sequenceDiagram ### Boundary -If you want to use the boundary symbol for a participant, use the `boundary` statement as shown below. +If you want to use the boundary symbol for a participant, use the JSON configuration syntax as shown below. ```mermaid-example sequenceDiagram - boundary theBoundary + participant Alice@{ "type" : "boundary" } participant Bob - theBoundary->>Bob: Request from boundary - Bob->>theBoundary: Response to boundary + Alice->>Bob: Request from boundary + Bob->>Alice: Response to boundary ``` ```mermaid sequenceDiagram - boundary theBoundary + participant Alice@{ "type" : "boundary" } participant Bob - theBoundary->>Bob: Request from boundary - Bob->>theBoundary: Response to boundary + Alice->>Bob: Request from boundary + Bob->>Alice: Response to boundary ``` ```mermaid-example sequenceDiagram - boundary theBoundary + participant Alice@{ "type" : "boundary" } participant Bob - theBoundary->>Bob: Request from boundary - Bob->>theBoundary: Response to boundary + Alice->>Bob: Request from boundary + Bob->>Alice: Response to boundary ``` ```mermaid sequenceDiagram - boundary theBoundary + participant Alice@{ "type" : "boundary" } participant Bob - theBoundary->>Bob: Request from boundary - Bob->>theBoundary: Response to boundary + Alice->>Bob: Request from boundary + Bob->>Alice: Response to boundary ``` ### Control -If you want to use the control symbol for a participant, use the `control` statement as shown below. +If you want to use the control symbol for a participant, use the JSON configuration syntax as shown below. ```mermaid-example sequenceDiagram - control theControl - participant Alice - theControl->>Alice: Control request - Alice->>theControl: Control response + participant Alice@{ "type" : "control" } + participant Bob + Alice->>Bob: Control request + Bob->>Alice: Control response ``` ```mermaid sequenceDiagram - control theControl - participant Alice - theControl->>Alice: Control request - Alice->>theControl: Control response + participant Alice@{ "type" : "control" } + participant Bob + Alice->>Bob: Control request + Bob->>Alice: Control response ``` ```mermaid-example sequenceDiagram - control theControl - participant Alice - theControl->>Alice: Control request - Alice->>theControl: Control response + participant Alice@{ "type" : "control" } + participant Bob + Alice->>Bob: Control request + Bob->>Alice: Control response ``` ```mermaid sequenceDiagram - control theControl - participant Alice - theControl->>Alice: Control request - Alice->>theControl: Control response + participant Alice@{ "type" : "control" } + participant Bob + Alice->>Bob: Control request + Bob->>Alice: Control response ``` ### Entity -If you want to use the entity symbol for a participant, use the `entity` statement as shown below. +If you want to use the entity symbol for a participant, use the JSON configuration syntax as shown below. ```mermaid-example sequenceDiagram - entity theEntity + participant Alice@{ "type" : "entity" } participant Bob - theEntity->>Bob: Entity request - Bob->>theEntity: Entity response + Alice->>Bob: Entity request + Bob->>Alice: Entity response ``` ```mermaid sequenceDiagram - entity theEntity + participant Alice@{ "type" : "entity" } participant Bob - theEntity->>Bob: Entity request - Bob->>theEntity: Entity response + Alice->>Bob: Entity request + Bob->>Alice: Entity response ``` ```mermaid-example sequenceDiagram - entity theEntity + participant Alice@{ "type" : "entity" } participant Bob - theEntity->>Bob: Entity request - Bob->>theEntity: Entity response + Alice->>Bob: Entity request + Bob->>Alice: Entity response ``` ```mermaid sequenceDiagram - entity theEntity + participant Alice@{ "type" : "entity" } participant Bob - theEntity->>Bob: Entity request - Bob->>theEntity: Entity response + Alice->>Bob: Entity request + Bob->>Alice: Entity response ``` ### Database -If you want to use the database symbol for a participant, use the `database` statement as shown below. +If you want to use the database symbol for a participant, use the JSON configuration syntax as shown below. ```mermaid-example sequenceDiagram - database theDb - participant Alice - theDb->>Alice: DB query - Alice->>theDb: DB result + participant Alice@{ "type" : "database" } + participant Bob + Alice->>Bob: DB query + Bob->>Alice: DB result ``` ```mermaid sequenceDiagram - database theDb - participant Alice - theDb->>Alice: DB query - Alice->>theDb: DB result + participant Alice@{ "type" : "database" } + participant Bob + Alice->>Bob: DB query + Bob->>Alice: DB result ``` ```mermaid-example sequenceDiagram - database theDb - participant Alice - theDb->>Alice: DB query - Alice->>theDb: DB result + participant Alice@{ "type" : "database" } + participant Bob + Alice->>Bob: DB query + Bob->>Alice: DB result ``` ```mermaid sequenceDiagram - database theDb - participant Alice - theDb->>Alice: DB query - Alice->>theDb: DB result + participant Alice@{ "type" : "database" } + participant Bob + Alice->>Bob: DB query + Bob->>Alice: DB result ``` ### Collections -If you want to use the collections symbol for a participant, use the `collections` statement as shown below. +If you want to use the collections symbol for a participant, use the JSON configuration syntax as shown below. ```mermaid-example sequenceDiagram - collections theCollection + participant Alice@{ "type" : "collections" } participant Bob - theCollection->>Bob: Collections request - Bob->>theCollection: Collections response + Alice->>Bob: Collections request + Bob->>Alice: Collections response ``` ```mermaid sequenceDiagram - collections theCollection + participant Alice@{ "type" : "collections" } participant Bob - theCollection->>Bob: Collections request - Bob->>theCollection: Collections response + Alice->>Bob: Collections request + Bob->>Alice: Collections response ``` ```mermaid-example sequenceDiagram - collections theCollection + participant Alice@{ "type" : "collections" } participant Bob - theCollection->>Bob: Collections request - Bob->>theCollection: Collections response + Alice->>Bob: Collections request + Bob->>Alice: Collections response ``` ```mermaid sequenceDiagram - collections theCollection + participant Alice@{ "type" : "collections" } participant Bob - theCollection->>Bob: Collections request - Bob->>theCollection: Collections response + Alice->>Bob: Collections request + Bob->>Alice: Collections response ``` ### Queue -If you want to use the queue symbol for a participant, use the `queue` statement as shown below. +If you want to use the queue symbol for a participant, use the JSON configuration syntax as shown below. ```mermaid-example sequenceDiagram - queue theQueue - participant Alice - theQueue->>Alice: Queue message - Alice->>theQueue: Queue + participant Alice@{ "type" : "queue" } + participant Bob + Alice->>Bob: Queue message + Bob->>Alice: Queue response ``` ```mermaid sequenceDiagram - queue theQueue - participant Alice - theQueue->>Alice: Queue message - Alice->>theQueue: Queue + participant Alice@{ "type" : "queue" } + participant Bob + Alice->>Bob: Queue message + Bob->>Alice: Queue response ``` ```mermaid-example sequenceDiagram - queue theQueue - participant Alice - theQueue->>Alice: Queue message - Alice->>theQueue: Queue + participant Alice@{ "type" : "queue" } + participant Bob + Alice->>Bob: Queue message + Bob->>Alice: Queue response ``` ```mermaid sequenceDiagram - queue theQueue - participant Alice - theQueue->>Alice: Queue message - Alice->>theQueue: Queue + participant Alice@{ "type" : "queue" } + participant Bob + Alice->>Bob: Queue message + Bob->>Alice: Queue response ``` ### Aliases diff --git a/packages/mermaid/src/docs/syntax/sequenceDiagram.md b/packages/mermaid/src/docs/syntax/sequenceDiagram.md index 63c6bfc8f..e2ff65af9 100644 --- a/packages/mermaid/src/docs/syntax/sequenceDiagram.md +++ b/packages/mermaid/src/docs/syntax/sequenceDiagram.md @@ -48,122 +48,122 @@ sequenceDiagram ### Boundary -If you want to use the boundary symbol for a participant, use the `boundary` statement as shown below. +If you want to use the boundary symbol for a participant, use the JSON configuration syntax as shown below. ```mermaid-example sequenceDiagram - boundary theBoundary + participant Alice@{ "type" : "boundary" } participant Bob - theBoundary->>Bob: Request from boundary - Bob->>theBoundary: Response to boundary + Alice->>Bob: Request from boundary + Bob->>Alice: Response to boundary ``` ```mermaid sequenceDiagram - boundary theBoundary + participant Alice@{ "type" : "boundary" } participant Bob - theBoundary->>Bob: Request from boundary - Bob->>theBoundary: Response to boundary + Alice->>Bob: Request from boundary + Bob->>Alice: Response to boundary ``` ### Control -If you want to use the control symbol for a participant, use the `control` statement as shown below. +If you want to use the control symbol for a participant, use the JSON configuration syntax as shown below. ```mermaid-example sequenceDiagram - control theControl - participant Alice - theControl->>Alice: Control request - Alice->>theControl: Control response + participant Alice@{ "type" : "control" } + participant Bob + Alice->>Bob: Control request + Bob->>Alice: Control response ``` ```mermaid sequenceDiagram - control theControl - participant Alice - theControl->>Alice: Control request - Alice->>theControl: Control response + participant Alice@{ "type" : "control" } + participant Bob + Alice->>Bob: Control request + Bob->>Alice: Control response ``` ### Entity -If you want to use the entity symbol for a participant, use the `entity` statement as shown below. +If you want to use the entity symbol for a participant, use the JSON configuration syntax as shown below. ```mermaid-example sequenceDiagram - entity theEntity + participant Alice@{ "type" : "entity" } participant Bob - theEntity->>Bob: Entity request - Bob->>theEntity: Entity response + Alice->>Bob: Entity request + Bob->>Alice: Entity response ``` ```mermaid sequenceDiagram - entity theEntity + participant Alice@{ "type" : "entity" } participant Bob - theEntity->>Bob: Entity request - Bob->>theEntity: Entity response + Alice->>Bob: Entity request + Bob->>Alice: Entity response ``` ### Database -If you want to use the database symbol for a participant, use the `database` statement as shown below. +If you want to use the database symbol for a participant, use the JSON configuration syntax as shown below. ```mermaid-example sequenceDiagram - database theDb - participant Alice - theDb->>Alice: DB query - Alice->>theDb: DB result + participant Alice@{ "type" : "database" } + participant Bob + Alice->>Bob: DB query + Bob->>Alice: DB result ``` ```mermaid sequenceDiagram - database theDb - participant Alice - theDb->>Alice: DB query - Alice->>theDb: DB result + participant Alice@{ "type" : "database" } + participant Bob + Alice->>Bob: DB query + Bob->>Alice: DB result ``` ### Collections -If you want to use the collections symbol for a participant, use the `collections` statement as shown below. +If you want to use the collections symbol for a participant, use the JSON configuration syntax as shown below. ```mermaid-example sequenceDiagram - collections theCollection + participant Alice@{ "type" : "collections" } participant Bob - theCollection->>Bob: Collections request - Bob->>theCollection: Collections response + Alice->>Bob: Collections request + Bob->>Alice: Collections response ``` ```mermaid sequenceDiagram - collections theCollection + participant Alice@{ "type" : "collections" } participant Bob - theCollection->>Bob: Collections request - Bob->>theCollection: Collections response + Alice->>Bob: Collections request + Bob->>Alice: Collections response ``` ### Queue -If you want to use the queue symbol for a participant, use the `queue` statement as shown below. +If you want to use the queue symbol for a participant, use the JSON configuration syntax as shown below. ```mermaid-example sequenceDiagram - queue theQueue - participant Alice - theQueue->>Alice: Queue message - Alice->>theQueue: Queue + participant Alice@{ "type" : "queue" } + participant Bob + Alice->>Bob: Queue message + Bob->>Alice: Queue response ``` ```mermaid sequenceDiagram - queue theQueue - participant Alice - theQueue->>Alice: Queue message - Alice->>theQueue: Queue + participant Alice@{ "type" : "queue" } + participant Bob + Alice->>Bob: Queue message + Bob->>Alice: Queue response ``` ### Aliases From 716548548abe849da299a414e629233dab876913 Mon Sep 17 00:00:00 2001 From: omkarht Date: Fri, 8 Aug 2025 19:31:56 +0530 Subject: [PATCH 17/23] fix: rendering test cases on-behalf-of: @Mermaid-Chart --- .../rendering/sequencediagram-v2.spec.js | 583 +++++++++--------- .../src/diagrams/sequence/sequenceDb.ts | 2 +- 2 files changed, 300 insertions(+), 285 deletions(-) diff --git a/cypress/integration/rendering/sequencediagram-v2.spec.js b/cypress/integration/rendering/sequencediagram-v2.spec.js index 4b7fe88d5..f1c2aafbd 100644 --- a/cypress/integration/rendering/sequencediagram-v2.spec.js +++ b/cypress/integration/rendering/sequencediagram-v2.spec.js @@ -2,49 +2,50 @@ import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts'; const looks = ['classic']; const participantTypes = [ - 'participant', - 'actor', - 'boundary', - 'control', - 'entity', - 'database', - 'collections', - 'queue', + { type: 'participant', display: 'participant' }, + { type: 'actor', display: 'actor' }, + { type: 'boundary', display: 'boundary' }, + { type: 'control', display: 'control' }, + { type: 'entity', display: 'entity' }, + { type: 'database', display: 'database' }, + { type: 'collections', display: 'collections' }, + { type: 'queue', display: 'queue' }, ]; -const interactionTypes = [ - '->>', // Solid arrow with arrowhead - '-->>', // Dotted arrow with arrowhead - '->', // Solid arrow without arrowhead - '-->', // Dotted arrow without arrowhead - '-x', // Solid arrow with cross - '--x', // Dotted arrow with cross - '->>+', // Solid arrow with arrowhead (activate) - '-->>+', // Dotted arrow with arrowhead (activate) -]; +const restrictedTypes = ['boundary', 'control', 'entity', 'database', 'collections', 'queue']; + +const interactionTypes = ['->>', '-->>', '->', '-->', '-x', '--x', '->>+', '-->>+']; const notePositions = ['left of', 'right of', 'over']; +function getParticipantLine(name, type, alias) { + if (restrictedTypes.includes(type)) { + return ` participant ${name}@{ "type" : "${type}" }\n`; + } else if (alias) { + return ` participant ${name}@{ "type" : "${type}" } \n`; + } else { + return ` participant ${name}@{ "type" : "${type}" }\n`; + } +} + looks.forEach((look) => { describe(`Sequence Diagram Tests - ${look} look`, () => { it('should render all participant types', () => { let diagramCode = `sequenceDiagram\n`; - participantTypes.forEach((type, index) => { - diagramCode += ` ${type} ${type}${index} as ${type} ${index}\n`; + participantTypes.forEach((pt, index) => { + const name = `${pt.display}${index}`; + diagramCode += getParticipantLine(name, pt.type); }); - // Add some basic interactions for (let i = 0; i < participantTypes.length - 1; i++) { - diagramCode += ` ${participantTypes[i]}${i} ->> ${participantTypes[i + 1]}${i + 1}: Message ${i}\n`; + diagramCode += ` ${participantTypes[i].display}${i} ->> ${participantTypes[i + 1].display}${i + 1}: Message ${i}\n`; } imgSnapshotTest(diagramCode, { look, sequence: { diagramMarginX: 50, diagramMarginY: 10 } }); }); it('should render all interaction types', () => { let diagramCode = `sequenceDiagram\n`; - // Create two participants - // Add all interaction types - diagramCode += ` participant A\n`; - diagramCode += ` participant B\n`; + diagramCode += getParticipantLine('A', 'actor'); + diagramCode += getParticipantLine('B', 'boundary'); interactionTypes.forEach((interaction, index) => { diagramCode += ` A ${interaction} B: ${interaction} message ${index}\n`; }); @@ -53,13 +54,14 @@ looks.forEach((look) => { it('should render participant creation and destruction', () => { let diagramCode = `sequenceDiagram\n`; - participantTypes.forEach((type, index) => { - diagramCode += ` ${type} A\n`; - diagramCode += ` ${type} B\n`; - diagramCode += ` create ${type} ${type}${index}\n`; - diagramCode += ` A ->> ${type}${index}: Hello ${type}\n`; + participantTypes.forEach((pt, index) => { + const name = `${pt.display}${index}`; + diagramCode += getParticipantLine('A', pt.type); + diagramCode += getParticipantLine('B', pt.type); + diagramCode += ` create participant ${name}@{ "type" : "${pt.type}" }\n`; + diagramCode += ` A ->> ${name}: Hello ${pt.display}\n`; if (index % 2 === 0) { - diagramCode += ` destroy ${type}${index}\n`; + diagramCode += ` destroy ${name}\n`; } }); imgSnapshotTest(diagramCode, { look }); @@ -67,8 +69,8 @@ looks.forEach((look) => { it('should render notes in all positions', () => { let diagramCode = `sequenceDiagram\n`; - diagramCode += ` participant A\n`; - diagramCode += ` participant B\n`; + diagramCode += getParticipantLine('A', 'actor'); + diagramCode += getParticipantLine('B', 'boundary'); notePositions.forEach((position, index) => { diagramCode += ` Note ${position} A: Note ${position} ${index}\n`; }); @@ -78,12 +80,12 @@ looks.forEach((look) => { it('should render parallel interactions', () => { let diagramCode = `sequenceDiagram\n`; - participantTypes.slice(0, 4).forEach((type, index) => { - diagramCode += ` ${type} ${type}${index}\n`; + participantTypes.slice(0, 4).forEach((pt, index) => { + diagramCode += getParticipantLine(`${pt.display}${index}`, pt.type); }); diagramCode += ` par Parallel actions\n`; - for (let i = 0; i < participantTypes.length - 1; i += 2) { - diagramCode += ` ${participantTypes[i]}${i} ->> ${participantTypes[i + 1]}${i + 1}: Message ${i}\n`; + for (let i = 0; i < 3; i += 2) { + diagramCode += ` ${participantTypes[i].display}${i} ->> ${participantTypes[i + 1].display}${i + 1}: Message ${i}\n`; if (i < participantTypes.length - 2) { diagramCode += ` and\n`; } @@ -94,8 +96,8 @@ looks.forEach((look) => { it('should render alternative flows', () => { let diagramCode = `sequenceDiagram\n`; - diagramCode += ` participant A\n`; - diagramCode += ` participant B\n`; + diagramCode += getParticipantLine('A', 'actor'); + diagramCode += getParticipantLine('B', 'boundary'); diagramCode += ` alt Successful case\n`; diagramCode += ` A ->> B: Request\n`; diagramCode += ` B -->> A: Success\n`; @@ -108,12 +110,12 @@ looks.forEach((look) => { it('should render loops', () => { let diagramCode = `sequenceDiagram\n`; - participantTypes.slice(0, 3).forEach((type, index) => { - diagramCode += ` ${type} ${type}${index}\n`; + participantTypes.slice(0, 3).forEach((pt, index) => { + diagramCode += getParticipantLine(`${pt.display}${index}`, pt.type); }); diagramCode += ` loop For each participant\n`; for (let i = 0; i < 3; i++) { - diagramCode += ` ${participantTypes[0]}0 ->> ${participantTypes[1]}1: Message ${i}\n`; + diagramCode += ` ${participantTypes[0].display}0 ->> ${participantTypes[1].display}1: Message ${i}\n`; } diagramCode += ` end\n`; imgSnapshotTest(diagramCode, { look }); @@ -122,27 +124,26 @@ looks.forEach((look) => { it('should render boxes around groups', () => { let diagramCode = `sequenceDiagram\n`; diagramCode += ` box Group 1\n`; - participantTypes.slice(0, 3).forEach((type, index) => { - diagramCode += ` ${type} ${type}${index}\n`; + participantTypes.slice(0, 3).forEach((pt, index) => { + diagramCode += ` ${getParticipantLine(`${pt.display}${index}`, pt.type)}`; }); diagramCode += ` end\n`; diagramCode += ` box rgb(200,220,255) Group 2\n`; - participantTypes.slice(3, 6).forEach((type, index) => { - diagramCode += ` ${type} ${type}${index}\n`; + participantTypes.slice(3, 6).forEach((pt, index) => { + diagramCode += ` ${getParticipantLine(`${pt.display}${index}`, pt.type)}`; }); diagramCode += ` end\n`; - // Add some interactions - diagramCode += ` ${participantTypes[0]}0 ->> ${participantTypes[3]}0: Cross-group message\n`; + diagramCode += ` ${participantTypes[0].display}0 ->> ${participantTypes[3].display}0: Cross-group message\n`; imgSnapshotTest(diagramCode, { look }); }); it('should render with different font settings', () => { let diagramCode = `sequenceDiagram\n`; - participantTypes.slice(0, 3).forEach((type, index) => { - diagramCode += ` ${type} ${type}${index}\n`; + participantTypes.slice(0, 3).forEach((pt, index) => { + diagramCode += getParticipantLine(`${pt.display}${index}`, pt.type); }); - diagramCode += ` ${participantTypes[0]}0 ->> ${participantTypes[1]}1: Regular message\n`; - diagramCode += ` Note right of ${participantTypes[1]}1: Regular note\n`; + diagramCode += ` ${participantTypes[0].display}0 ->> ${participantTypes[1].display}1: Regular message\n`; + diagramCode += ` Note right of ${participantTypes[1].display}1: Regular note\n`; imgSnapshotTest(diagramCode, { look, sequence: { @@ -166,15 +167,15 @@ describe('Sequence Diagram Special Cases', () => { sequenceDiagram box rgb(200,220,255) Authentication actor User - boundary LoginUI - control AuthService - database UserDB + participant LoginUI@{ "type": "boundary" } + participant AuthService@{ "type": "control" } + participant UserDB@{ "type": "database" } end - + box rgb(200,255,220) Order Processing - entity Order - queue OrderQueue - collections AuditLogs + participant Order@{ "type": "entity" } + participant OrderQueue@{ "type": "queue" } + participant AuditLogs@{ "type": "collections" } end User ->> LoginUI: Enter credentials @@ -230,70 +231,87 @@ describe('Sequence Diagram Special Cases', () => { it('should render a sequence diagram with various participant types', () => { imgSnapshotTest( ` - sequenceDiagram - actor User - participant AuthService as Authentication Service - boundary UI - control OrderController - entity Product - database MongoDB - collections Products - queue OrderQueue - User ->> UI: Login request - UI ->> AuthService: Validate credentials - AuthService -->> UI: Authentication token - UI ->> OrderController: Place order - OrderController ->> Product: Check availability - Product -->> OrderController: Available - OrderController ->> MongoDB: Save order - MongoDB -->> OrderController: Order saved - OrderController ->> OrderQueue: Process payment - OrderQueue -->> User: Order confirmation + sequenceDiagram + participant User@{ "type": "actor" } + participant AuthService@{ "type": "control" } + participant UI@{ "type": "boundary" } + participant OrderController@{ "type": "control" } + participant Product@{ "type": "entity" } + participant MongoDB@{ "type": "database" } + participant Products@{ "type": "collections" } + participant OrderQueue@{ "type": "queue" } + User ->> UI: Login request + UI ->> AuthService: Validate credentials + AuthService -->> UI: Authentication token + UI ->> OrderController: Place order + OrderController ->> Product: Check availability + Product -->> OrderController: Available + OrderController ->> MongoDB: Save order + MongoDB -->> OrderController: Order saved + OrderController ->> OrderQueue: Process payment + OrderQueue -->> User: Order confirmation ` ); }); it('should render participant creation and destruction with different types', () => { - imgSnapshotTest( - ` - sequenceDiagram - actor Customer - participant Frontend - boundary PaymentGateway - Customer ->> Frontend: Place order - Frontend ->> OrderProcessor: Process order - create database OrderDB - OrderProcessor ->> OrderDB: Save order - ` - ); + imgSnapshotTest(` + sequenceDiagram + participant Alice@{ "type" : "boundary" } + Alice->>Bob: Hello Bob, how are you ? + Bob->>Alice: Fine, thank you. And you? + create participant Carl@{ "type" : "control" } + Alice->>Carl: Hi Carl! + create actor D as Donald + Carl->>D: Hi! + destroy Carl + Alice-xCarl: We are too many + destroy Bob + Bob->>Alice: I agree + `); }); it('should handle complex interactions between different participant types', () => { imgSnapshotTest( ` - sequenceDiagram - box rgba(200,220,255,0.5) System Components - actor User - boundary WebUI - control API - entity BusinessLogic - database MainDB - end - box rgba(200,255,220,0.5) External Services - queue MessageQueue - database AuditLogs - end + sequenceDiagram + box rgb(200,220,255) Authentication + participant User@{ "type": "actor" } + participant LoginUI@{ "type": "boundary" } + participant AuthService@{ "type": "control" } + participant UserDB@{ "type": "database" } + end - User ->> WebUI: Submit request - WebUI ->> API: Process request - API ->> BusinessLogic: Execute business rules - BusinessLogic ->> MainDB: Query data - MainDB -->> BusinessLogic: Return results - BusinessLogic ->> MessageQueue: Publish event - MessageQueue -->> AuditLogs: Store audit trail - AuditLogs -->> API: Audit complete - API -->> WebUI: Return response - WebUI -->> User: Show results + box rgb(200,255,220) Order Processing + participant Order@{ "type": "entity" } + participant OrderQueue@{ "type": "queue" } + participant AuditLogs@{ "type": "collections" } + end + + User ->> LoginUI: Enter credentials + LoginUI ->> AuthService: Validate + AuthService ->> UserDB: Query user + UserDB -->> AuthService: User data + + alt Valid credentials + AuthService -->> LoginUI: Success + LoginUI -->> User: Welcome + + par Place order + User ->> Order: New order + Order ->> OrderQueue: Process + and + Order ->> AuditLogs: Record + end + + loop Until confirmed + OrderQueue ->> Order: Update status + Order -->> User: Notification + end + else Invalid credentials + AuthService --x LoginUI: Failure + LoginUI --x User: Retry + end `, { sequence: { useMaxWidth: false } } ); @@ -302,15 +320,15 @@ describe('Sequence Diagram Special Cases', () => { it('should render parallel processes with different participant types', () => { imgSnapshotTest( ` - sequenceDiagram - actor Customer - participant Frontend - boundary PaymentService - control InventoryManager - entity Order - database OrdersDB - queue NotificationQueue - + sequenceDiagram + participant Customer@{ "type": "actor" } + participant Frontend@{ "type": "participant" } + participant PaymentService@{ "type": "boundary" } + participant InventoryManager@{ "type": "control" } + participant Order@{ "type": "entity" } + participant OrdersDB@{ "type": "database" } + participant NotificationQueue@{ "type": "queue" } + Customer ->> Frontend: Place order Frontend ->> Order: Create order par Parallel Processing @@ -327,18 +345,18 @@ describe('Sequence Diagram Special Cases', () => { ` ); }); - - it('should render different participant types with notes and loops', () => { - imgSnapshotTest( - ` + }); + it('should render different participant types with notes and loops', () => { + imgSnapshotTest( + ` sequenceDiagram actor Admin participant Dashboard - boundary AuthService - control UserManager - entity UserProfile - database UserDB - database Logs + participant AuthService@{ "type" : "boundary" } + participant UserManager@{ "type" : "control" } + participant UserProfile@{ "type" : "entity" } + participant UserDB@{ "type" : "database" } + participant Logs@{ "type" : "database" } Admin ->> Dashboard: Open user management loop Authentication check @@ -354,217 +372,214 @@ describe('Sequence Diagram Special Cases', () => { UserManager ->> Dashboard: Display users Dashboard ->> Logs: Record access Logs ->> Admin: Audit trail - ` - ); - }); + ` + ); + }); - it('should render different participant types with alternative flows', () => { - imgSnapshotTest( - ` - sequenceDiagram - actor Client - participant MobileApp - boundary CloudService - control DataProcessor - entity Transaction - database TransactionsDB - queue EventBus - - Client ->> MobileApp: Initiate transaction - MobileApp ->> CloudService: Authenticate - alt Authentication successful - CloudService -->> MobileApp: Auth token - MobileApp ->> DataProcessor: Process data - DataProcessor ->> Transaction: Create transaction - Transaction ->> TransactionsDB: Save record - TransactionsDB -->> Transaction: Confirmation - Transaction ->> EventBus: Publish event - EventBus -->> Client: Notification - else Authentication failed - CloudService -->> MobileApp: Error - MobileApp -->> Client: Show error - end + it('should render different participant types with alternative flows', () => { + imgSnapshotTest( ` - ); - }); + sequenceDiagram + actor Client + participant MobileApp + participant CloudService@{ "type" : "boundary" } + participant DataProcessor@{ "type" : "control" } + participant Transaction@{ "type" : "entity" } + participant TransactionsDB@{ "type" : "database" } + participant EventBus@{ "type" : "queue" } + + Client ->> MobileApp: Initiate transaction + MobileApp ->> CloudService: Authenticate + alt Authentication successful + CloudService -->> MobileApp: Auth token + MobileApp ->> DataProcessor: Process data + DataProcessor ->> Transaction: Create transaction + Transaction ->> TransactionsDB: Save record + TransactionsDB -->> Transaction: Confirmation + Transaction ->> EventBus: Publish event + EventBus -->> Client: Notification + else Authentication failed + CloudService -->> MobileApp: Error + MobileApp -->> Client: Show error + end + ` + ); + }); - it('should render different participant types with wrapping text', () => { - imgSnapshotTest( - ` - sequenceDiagram - actor LongNameUser as User With A Very
Long Name - participant FE as Frontend Service
With Long Name - boundary B as Boundary With
Multiline Name - control C as Control With
Multiline Name - entity E as Entity With
Multiline Name - database DB as Database With
Multiline Name - collections COL as Collections With
Multiline Name - queue Q as Queue With
Multiline Name - - LongNameUser ->> FE: This is a very long message that should wrap properly in the diagram - FE ->> B: Another long message
with explicit
line breaks - B -->> FE: Response message that is also quite long and needs to wrap - FE ->> C: Process data - C ->> E: Validate - E -->> C: Validation result - C ->> DB: Save - DB -->> C: Save result - C ->> COL: Log - COL -->> Q: Forward - Q -->> LongNameUser: Final response with confirmation of all actions taken - `, - { sequence: { wrap: true } } - ); - }); - describe('Sequence Diagram - New Participant Types with Long Notes and Messages', () => { - it('should render long notes left of boundary', () => { - imgSnapshotTest( - ` - sequenceDiagram - boundary Alice - actor Bob - Alice->>Bob: Hola - Note left of Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be - Bob->>Alice: I'm short though + it('should render different participant types with wrapping text', () => { + imgSnapshotTest( + ` + sequenceDiagram + participant B@{ "type" : "boundary" } + participant C@{ "type" : "control" } + participant E@{ "type" : "entity" } + participant DB@{ "type" : "database" } + participant COL@{ "type" : "collections" } + participant Q@{ "type" : "queue" } + + FE ->> B: Another long message
with explicit
line breaks + B -->> FE: Response message that is also quite long and needs to wrap + FE ->> C: Process data + C ->> E: Validate + E -->> C: Validation result + C ->> DB: Save + DB -->> C: Save result + C ->> COL: Log + COL -->> Q: Forward + Q -->> LongNameUser: Final response with confirmation of all actions taken `, - {} - ); - }); + { sequence: { wrap: true } } + ); + }); - it('should render wrapped long notes left of control', () => { - imgSnapshotTest( - ` + describe('Sequence Diagram - New Participant Types with Long Notes and Messages', () => { + it('should render long notes left of boundary', () => { + imgSnapshotTest( + ` sequenceDiagram - control Alice + participant Alice@{ "type" : "boundary" } + actor Bob + Alice->>Bob: Hola + Note left of Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be + Bob->>Alice: I'm short though + `, + {} + ); + }); + + it('should render wrapped long notes left of control', () => { + imgSnapshotTest( + ` + sequenceDiagram + participant Alice@{ "type" : "control" } actor Bob Alice->>Bob: Hola Note left of Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be Bob->>Alice: I'm short though `, - {} - ); - }); + {} + ); + }); - it('should render long notes right of entity', () => { - imgSnapshotTest( - ` + it('should render long notes right of entity', () => { + imgSnapshotTest( + ` sequenceDiagram - entity Alice + participant Alice@{ "type" : "entity" } actor Bob Alice->>Bob: Hola Note right of Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be Bob->>Alice: I'm short though `, - {} - ); - }); + {} + ); + }); - it('should render wrapped long notes right of database', () => { - imgSnapshotTest( - ` + it('should render wrapped long notes right of database', () => { + imgSnapshotTest( + ` sequenceDiagram - database Alice + participant Alice@{ "type" : "database" } actor Bob Alice->>Bob: Hola Note right of Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be Bob->>Alice: I'm short though `, - {} - ); - }); + {} + ); + }); - it('should render long notes over collections', () => { - imgSnapshotTest( - ` + it('should render long notes over collections', () => { + imgSnapshotTest( + ` sequenceDiagram - collections Alice + participant Alice@{ "type" : "collections" } actor Bob Alice->>Bob: Hola Note over Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be Bob->>Alice: I'm short though `, - {} - ); - }); + {} + ); + }); - it('should render wrapped long notes over queue', () => { - imgSnapshotTest( - ` + it('should render wrapped long notes over queue', () => { + imgSnapshotTest( + ` sequenceDiagram - queue Alice + participant Alice@{ "type" : "queue" } actor Bob Alice->>Bob: Hola Note over Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be Bob->>Alice: I'm short though `, - {} - ); - }); + {} + ); + }); - it('should render notes over actor and boundary', () => { - imgSnapshotTest( - ` + it('should render notes over actor and boundary', () => { + imgSnapshotTest( + ` sequenceDiagram actor Alice - boundary Charlie + participant Charlie@{ "type" : "boundary" } note over Alice: Some note note over Charlie: Other note `, - {} - ); - }); + {} + ); + }); - it('should render long messages from database to collections', () => { - imgSnapshotTest( - ` + it('should render long messages from database to collections', () => { + imgSnapshotTest( + ` sequenceDiagram - database Alice - collections Bob + participant Alice@{ "type" : "database" } + participant Bob@{ "type" : "collections" } Alice->>Bob: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be Bob->>Alice: I'm short though `, - {} - ); - }); + {} + ); + }); - it('should render wrapped long messages from control to entity', () => { - imgSnapshotTest( - ` + it('should render wrapped long messages from control to entity', () => { + imgSnapshotTest( + ` sequenceDiagram - control Alice - entity Bob + participant Alice@{ "type" : "control" } + participant Bob@{ "type" : "entity" } Alice->>Bob:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be Bob->>Alice: I'm short though `, - {} - ); - }); + {} + ); + }); - it('should render long messages from queue to boundary', () => { - imgSnapshotTest( - ` + it('should render long messages from queue to boundary', () => { + imgSnapshotTest( + ` sequenceDiagram - queue Alice - boundary Bob + participant Alice@{ "type" : "queue" } + participant Bob@{ "type" : "boundary" } Alice->>Bob: I'm short Bob->>Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be `, - {} - ); - }); + {} + ); + }); - it('should render wrapped long messages from actor to database', () => { - imgSnapshotTest( - ` + it('should render wrapped long messages from actor to database', () => { + imgSnapshotTest( + ` sequenceDiagram actor Alice - database Bob + participant Bob@{ "type" : "database" } Alice->>Bob: I'm short Bob->>Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be `, - {} - ); - }); + {} + ); }); }); @@ -574,8 +589,8 @@ describe('Sequence Diagram Special Cases', () => { ` sequenceDiagram actor Alice - boundary Bob - control John as John
Second Line + participant Bob@{ "type" : "boundary" } + participant John@{ "type" : "control" } Alice ->> Bob: Hello Bob, how are you? Bob-->>John: How about you John? Bob--x Alice: I am good thanks! @@ -584,9 +599,9 @@ describe('Sequence Diagram Special Cases', () => { Bob-->Alice: Checking with John... alt either this Alice->>John: Yes - else or this + else or this Alice->>John: No - else or this will happen + else or this will happen Alice->John: Maybe end par this happens in parallel @@ -611,8 +626,8 @@ describe('Sequence Diagram Special Cases', () => { ` sequenceDiagram actor Alice - boundary Bob - control John as John
Second Line + participant Bob@{ "type" : "boundary" } + participant John@{ "type" : "control" } Alice ->> Bob: Hello Bob, how are you? Bob-->>John: How about you John? Bob--x Alice: I am good thanks! @@ -621,9 +636,9 @@ describe('Sequence Diagram Special Cases', () => { Bob-->Alice: Checking with John... alt either this Alice->>John: Yes - else or this + else or this Alice->>John: No - else or this will happen + else or this will happen Alice->John: Maybe end par this happens in parallel diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDb.ts b/packages/mermaid/src/diagrams/sequence/sequenceDb.ts index 4f06a1ebe..67ae19de5 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDb.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceDb.ts @@ -553,7 +553,7 @@ export class SequenceDB implements DiagramDB { ); } this.state.records.lastCreated = param.actor; - this.addActor(param.actor, param.actor, param.description, param.draw); + this.addActor(param.actor, param.actor, param.description, param.draw, param.config); this.state.records.createdActors.set(param.actor, this.state.records.messages.length); break; case 'destroyParticipant': From e4d3aa461000bdb9be1034989d8867d7aa829a97 Mon Sep 17 00:00:00 2001 From: omkarht Date: Fri, 8 Aug 2025 20:18:21 +0530 Subject: [PATCH 18/23] fix: refactored documentation, test cases and sequenceDiagram.jison file on-behalf-of: @Mermaid-Chart --- .../setup/mermaid/interfaces/ParseOptions.md | 4 +- .../setup/mermaid/interfaces/ParseResult.md | 6 +- .../setup/mermaid/interfaces/RenderResult.md | 8 +- docs/syntax/sequenceDiagram.md | 96 ------------------- .../sequence/parser/sequenceDiagram.jison | 2 +- .../diagrams/sequence/sequenceDiagram.spec.js | 6 +- .../src/docs/syntax/sequenceDiagram.md | 48 ---------- 7 files changed, 15 insertions(+), 155 deletions(-) diff --git a/docs/config/setup/mermaid/interfaces/ParseOptions.md b/docs/config/setup/mermaid/interfaces/ParseOptions.md index e068a91fb..0ff3bf004 100644 --- a/docs/config/setup/mermaid/interfaces/ParseOptions.md +++ b/docs/config/setup/mermaid/interfaces/ParseOptions.md @@ -10,7 +10,7 @@ # Interface: ParseOptions -Defined in: [packages/mermaid/src/types.ts:84](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L84) +Defined in: [packages/mermaid/src/types.ts:89](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L89) ## Properties @@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:84](https://github.com/mermaid-js/mer > `optional` **suppressErrors**: `boolean` -Defined in: [packages/mermaid/src/types.ts:89](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L89) +Defined in: [packages/mermaid/src/types.ts:94](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L94) If `true`, parse will return `false` instead of throwing error when the diagram is invalid. The `parseError` function will not be called. diff --git a/docs/config/setup/mermaid/interfaces/ParseResult.md b/docs/config/setup/mermaid/interfaces/ParseResult.md index 1651a6fa9..66e5de194 100644 --- a/docs/config/setup/mermaid/interfaces/ParseResult.md +++ b/docs/config/setup/mermaid/interfaces/ParseResult.md @@ -10,7 +10,7 @@ # Interface: ParseResult -Defined in: [packages/mermaid/src/types.ts:92](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L92) +Defined in: [packages/mermaid/src/types.ts:97](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L97) ## Properties @@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:92](https://github.com/mermaid-js/mer > **config**: [`MermaidConfig`](MermaidConfig.md) -Defined in: [packages/mermaid/src/types.ts:100](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L100) +Defined in: [packages/mermaid/src/types.ts:105](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L105) The config passed as YAML frontmatter or directives @@ -28,6 +28,6 @@ The config passed as YAML frontmatter or directives > **diagramType**: `string` -Defined in: [packages/mermaid/src/types.ts:96](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L96) +Defined in: [packages/mermaid/src/types.ts:101](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L101) The diagram type, e.g. 'flowchart', 'sequence', etc. diff --git a/docs/config/setup/mermaid/interfaces/RenderResult.md b/docs/config/setup/mermaid/interfaces/RenderResult.md index c0e5496b8..9d6771ad1 100644 --- a/docs/config/setup/mermaid/interfaces/RenderResult.md +++ b/docs/config/setup/mermaid/interfaces/RenderResult.md @@ -10,7 +10,7 @@ # Interface: RenderResult -Defined in: [packages/mermaid/src/types.ts:110](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L110) +Defined in: [packages/mermaid/src/types.ts:115](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L115) ## Properties @@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:110](https://github.com/mermaid-js/me > `optional` **bindFunctions**: (`element`) => `void` -Defined in: [packages/mermaid/src/types.ts:128](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L128) +Defined in: [packages/mermaid/src/types.ts:133](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L133) Bind function to be called after the svg has been inserted into the DOM. This is necessary for adding event listeners to the elements in the svg. @@ -45,7 +45,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present. > **diagramType**: `string` -Defined in: [packages/mermaid/src/types.ts:118](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L118) +Defined in: [packages/mermaid/src/types.ts:123](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L123) The diagram type, e.g. 'flowchart', 'sequence', etc. @@ -55,6 +55,6 @@ The diagram type, e.g. 'flowchart', 'sequence', etc. > **svg**: `string` -Defined in: [packages/mermaid/src/types.ts:114](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L114) +Defined in: [packages/mermaid/src/types.ts:119](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L119) The svg code for the rendered graph. diff --git a/docs/syntax/sequenceDiagram.md b/docs/syntax/sequenceDiagram.md index 32afe5a30..a8f502dcd 100644 --- a/docs/syntax/sequenceDiagram.md +++ b/docs/syntax/sequenceDiagram.md @@ -94,22 +94,6 @@ sequenceDiagram Bob->>Alice: Response to boundary ``` -```mermaid-example -sequenceDiagram - participant Alice@{ "type" : "boundary" } - participant Bob - Alice->>Bob: Request from boundary - Bob->>Alice: Response to boundary -``` - -```mermaid -sequenceDiagram - participant Alice@{ "type" : "boundary" } - participant Bob - Alice->>Bob: Request from boundary - Bob->>Alice: Response to boundary -``` - ### Control If you want to use the control symbol for a participant, use the JSON configuration syntax as shown below. @@ -130,22 +114,6 @@ sequenceDiagram Bob->>Alice: Control response ``` -```mermaid-example -sequenceDiagram - participant Alice@{ "type" : "control" } - participant Bob - Alice->>Bob: Control request - Bob->>Alice: Control response -``` - -```mermaid -sequenceDiagram - participant Alice@{ "type" : "control" } - participant Bob - Alice->>Bob: Control request - Bob->>Alice: Control response -``` - ### Entity If you want to use the entity symbol for a participant, use the JSON configuration syntax as shown below. @@ -166,22 +134,6 @@ sequenceDiagram Bob->>Alice: Entity response ``` -```mermaid-example -sequenceDiagram - participant Alice@{ "type" : "entity" } - participant Bob - Alice->>Bob: Entity request - Bob->>Alice: Entity response -``` - -```mermaid -sequenceDiagram - participant Alice@{ "type" : "entity" } - participant Bob - Alice->>Bob: Entity request - Bob->>Alice: Entity response -``` - ### Database If you want to use the database symbol for a participant, use the JSON configuration syntax as shown below. @@ -202,22 +154,6 @@ sequenceDiagram Bob->>Alice: DB result ``` -```mermaid-example -sequenceDiagram - participant Alice@{ "type" : "database" } - participant Bob - Alice->>Bob: DB query - Bob->>Alice: DB result -``` - -```mermaid -sequenceDiagram - participant Alice@{ "type" : "database" } - participant Bob - Alice->>Bob: DB query - Bob->>Alice: DB result -``` - ### Collections If you want to use the collections symbol for a participant, use the JSON configuration syntax as shown below. @@ -238,22 +174,6 @@ sequenceDiagram Bob->>Alice: Collections response ``` -```mermaid-example -sequenceDiagram - participant Alice@{ "type" : "collections" } - participant Bob - Alice->>Bob: Collections request - Bob->>Alice: Collections response -``` - -```mermaid -sequenceDiagram - participant Alice@{ "type" : "collections" } - participant Bob - Alice->>Bob: Collections request - Bob->>Alice: Collections response -``` - ### Queue If you want to use the queue symbol for a participant, use the JSON configuration syntax as shown below. @@ -274,22 +194,6 @@ sequenceDiagram Bob->>Alice: Queue response ``` -```mermaid-example -sequenceDiagram - participant Alice@{ "type" : "queue" } - participant Bob - Alice->>Bob: Queue message - Bob->>Alice: Queue response -``` - -```mermaid -sequenceDiagram - participant Alice@{ "type" : "queue" } - participant Bob - Alice->>Bob: Queue message - Bob->>Alice: Queue response -``` - ### Aliases The actor can have a convenient identifier and a descriptive label. diff --git a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison index 87ef8212f..5938e3247 100644 --- a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison +++ b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison @@ -235,7 +235,7 @@ participant_statement | 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} | 'participant_actor' actor 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant'; $$=$2;} | 'destroy' actor 'NEWLINE' {$2.type='destroyParticipant'; $$=$2;} - | 'participant' actor_with_config 'NEWLINE' { $2.draw = 'participant'; $2.type = 'addParticipant'; $$ = $2;} + | 'participant' actor_with_config 'NEWLINE' {$2.draw='participant'; $2.type='addParticipant'; $$=$2;} ; diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js index 8088f3c44..4820d19ea 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js +++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js @@ -2038,13 +2038,17 @@ Bob->>Alice:Got it! expect(messages[0].from).toBe('Alice'); expect(messages[0].to).toBe('Bob'); }); - describe('when newly parsing messages ', () => { + describe('when parsing extended participant syntax ', () => { it('should parse a message', async () => { const actor1 = 'database'; const diagram = await Diagram.fromText(` sequenceDiagram participant Alice@{ "type" : "database" } participant Bob@{ "type" : "database" } + participant Carl@{ type: "database" } + participant David@{ "type" : 'database' } + participant Eve@{ type: 'database' } + participant Favela@{ "type" : "database" } Bob->>+Alice: Hi Alice Alice->>+Bob: Hi Bob `); diff --git a/packages/mermaid/src/docs/syntax/sequenceDiagram.md b/packages/mermaid/src/docs/syntax/sequenceDiagram.md index e2ff65af9..6e0ac87bf 100644 --- a/packages/mermaid/src/docs/syntax/sequenceDiagram.md +++ b/packages/mermaid/src/docs/syntax/sequenceDiagram.md @@ -58,14 +58,6 @@ sequenceDiagram Bob->>Alice: Response to boundary ``` -```mermaid -sequenceDiagram - participant Alice@{ "type" : "boundary" } - participant Bob - Alice->>Bob: Request from boundary - Bob->>Alice: Response to boundary -``` - ### Control If you want to use the control symbol for a participant, use the JSON configuration syntax as shown below. @@ -78,14 +70,6 @@ sequenceDiagram Bob->>Alice: Control response ``` -```mermaid -sequenceDiagram - participant Alice@{ "type" : "control" } - participant Bob - Alice->>Bob: Control request - Bob->>Alice: Control response -``` - ### Entity If you want to use the entity symbol for a participant, use the JSON configuration syntax as shown below. @@ -98,14 +82,6 @@ sequenceDiagram Bob->>Alice: Entity response ``` -```mermaid -sequenceDiagram - participant Alice@{ "type" : "entity" } - participant Bob - Alice->>Bob: Entity request - Bob->>Alice: Entity response -``` - ### Database If you want to use the database symbol for a participant, use the JSON configuration syntax as shown below. @@ -118,14 +94,6 @@ sequenceDiagram Bob->>Alice: DB result ``` -```mermaid -sequenceDiagram - participant Alice@{ "type" : "database" } - participant Bob - Alice->>Bob: DB query - Bob->>Alice: DB result -``` - ### Collections If you want to use the collections symbol for a participant, use the JSON configuration syntax as shown below. @@ -138,14 +106,6 @@ sequenceDiagram Bob->>Alice: Collections response ``` -```mermaid -sequenceDiagram - participant Alice@{ "type" : "collections" } - participant Bob - Alice->>Bob: Collections request - Bob->>Alice: Collections response -``` - ### Queue If you want to use the queue symbol for a participant, use the JSON configuration syntax as shown below. @@ -158,14 +118,6 @@ sequenceDiagram Bob->>Alice: Queue response ``` -```mermaid -sequenceDiagram - participant Alice@{ "type" : "queue" } - participant Bob - Alice->>Bob: Queue message - Bob->>Alice: Queue response -``` - ### Aliases The actor can have a convenient identifier and a descriptive label. From 769b362005889868daa1317f03fe354a651468e4 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 8 Aug 2025 14:53:47 +0000 Subject: [PATCH 19/23] [autofix.ci] apply automated fixes --- docs/config/setup/mermaid/interfaces/ParseOptions.md | 4 ++-- docs/config/setup/mermaid/interfaces/ParseResult.md | 6 +++--- docs/config/setup/mermaid/interfaces/RenderResult.md | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/config/setup/mermaid/interfaces/ParseOptions.md b/docs/config/setup/mermaid/interfaces/ParseOptions.md index 0ff3bf004..e068a91fb 100644 --- a/docs/config/setup/mermaid/interfaces/ParseOptions.md +++ b/docs/config/setup/mermaid/interfaces/ParseOptions.md @@ -10,7 +10,7 @@ # Interface: ParseOptions -Defined in: [packages/mermaid/src/types.ts:89](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L89) +Defined in: [packages/mermaid/src/types.ts:84](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L84) ## Properties @@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:89](https://github.com/mermaid-js/mer > `optional` **suppressErrors**: `boolean` -Defined in: [packages/mermaid/src/types.ts:94](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L94) +Defined in: [packages/mermaid/src/types.ts:89](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L89) If `true`, parse will return `false` instead of throwing error when the diagram is invalid. The `parseError` function will not be called. diff --git a/docs/config/setup/mermaid/interfaces/ParseResult.md b/docs/config/setup/mermaid/interfaces/ParseResult.md index 66e5de194..1651a6fa9 100644 --- a/docs/config/setup/mermaid/interfaces/ParseResult.md +++ b/docs/config/setup/mermaid/interfaces/ParseResult.md @@ -10,7 +10,7 @@ # Interface: ParseResult -Defined in: [packages/mermaid/src/types.ts:97](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L97) +Defined in: [packages/mermaid/src/types.ts:92](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L92) ## Properties @@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:97](https://github.com/mermaid-js/mer > **config**: [`MermaidConfig`](MermaidConfig.md) -Defined in: [packages/mermaid/src/types.ts:105](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L105) +Defined in: [packages/mermaid/src/types.ts:100](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L100) The config passed as YAML frontmatter or directives @@ -28,6 +28,6 @@ The config passed as YAML frontmatter or directives > **diagramType**: `string` -Defined in: [packages/mermaid/src/types.ts:101](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L101) +Defined in: [packages/mermaid/src/types.ts:96](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L96) The diagram type, e.g. 'flowchart', 'sequence', etc. diff --git a/docs/config/setup/mermaid/interfaces/RenderResult.md b/docs/config/setup/mermaid/interfaces/RenderResult.md index 9d6771ad1..c0e5496b8 100644 --- a/docs/config/setup/mermaid/interfaces/RenderResult.md +++ b/docs/config/setup/mermaid/interfaces/RenderResult.md @@ -10,7 +10,7 @@ # Interface: RenderResult -Defined in: [packages/mermaid/src/types.ts:115](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L115) +Defined in: [packages/mermaid/src/types.ts:110](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L110) ## Properties @@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:115](https://github.com/mermaid-js/me > `optional` **bindFunctions**: (`element`) => `void` -Defined in: [packages/mermaid/src/types.ts:133](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L133) +Defined in: [packages/mermaid/src/types.ts:128](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L128) Bind function to be called after the svg has been inserted into the DOM. This is necessary for adding event listeners to the elements in the svg. @@ -45,7 +45,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present. > **diagramType**: `string` -Defined in: [packages/mermaid/src/types.ts:123](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L123) +Defined in: [packages/mermaid/src/types.ts:118](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L118) The diagram type, e.g. 'flowchart', 'sequence', etc. @@ -55,6 +55,6 @@ The diagram type, e.g. 'flowchart', 'sequence', etc. > **svg**: `string` -Defined in: [packages/mermaid/src/types.ts:119](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L119) +Defined in: [packages/mermaid/src/types.ts:114](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L114) The svg code for the rendered graph. From e7a7ff8a2a44df14c131e44d17f5e5d3f44b5616 Mon Sep 17 00:00:00 2001 From: omkarht Date: Mon, 11 Aug 2025 13:20:49 +0530 Subject: [PATCH 20/23] fix: refactored code on-behalf-of: @Mermaid-Chart --- packages/mermaid/src/diagrams/sequence/svgDraw.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/mermaid/src/diagrams/sequence/svgDraw.js b/packages/mermaid/src/diagrams/sequence/svgDraw.js index f6aa3d34b..95863234d 100644 --- a/packages/mermaid/src/diagrams/sequence/svgDraw.js +++ b/packages/mermaid/src/diagrams/sequence/svgDraw.js @@ -706,10 +706,9 @@ const drawActorTypeControl = function (elem, actor, conf, isFooter) { // Draw looping arrow as arc path actElem - .append('path') - .attr('d', `M ${cx},${cy - r}`) - .attr('stroke-width', 1.5) - .attr('marker-end', 'url(#filled-head-control)'); + .append('line') + .attr('marker-end', 'url(#filled-head-control)') + .attr('transform', `translate(${cx}, ${cy - r})`); const bounds = actElem.node().getBBox(); actor.height = bounds.height + 2 * (conf?.sequence?.labelBoxHeight ?? 0); @@ -775,7 +774,7 @@ const drawActorTypeEntity = function (elem, actor, conf, isFooter) { .attr('stroke-width', 2); const bounds = actElem.node().getBBox(); - actor.height = bounds.height + (conf.sequence.labelBoxHeight ?? 0); + actor.height = bounds.height + (conf?.sequence?.labelBoxHeight ?? 0); if (!isFooter) { actorCnt++; From 9f6ee53382e817a50f3063d9c1a6161ad0123206 Mon Sep 17 00:00:00 2001 From: omkarht Date: Tue, 12 Aug 2025 14:20:47 +0530 Subject: [PATCH 21/23] fix: refactored parsing logic for new syntax on-behalf-of: @Mermaid-Chart --- .../mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison index 43c62a0cc..13e63f3ae 100644 --- a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison +++ b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison @@ -31,7 +31,7 @@ \@\{ { this.begin('CONFIG'); return 'CONFIG_START'; } [^\}]+ { return 'CONFIG_CONTENT'; } \} { this.popState(); this.popState(); return 'CONFIG_END'; } -[^\<->\->:\n,;@]+?([\-]*[^\<->\->:\n,;@]+?)*?(?=\@\{) { yytext = yytext.trim(); return 'ACTOR'; } +[^\<->\->:\n,;@\s]+(?=\@\{) { yytext = yytext.trim(); return 'ACTOR'; } [^\<->\->:\n,;@]+?([\-]*[^\<->\->:\n,;@]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; } "box" { this.begin('LINE'); return 'box'; } "participant" { this.begin('ID'); return 'participant'; } From 71e09bcaefa402dfa2f7b01bb73ea2e868f9352b Mon Sep 17 00:00:00 2001 From: omkarht Date: Mon, 18 Aug 2025 18:21:41 +0530 Subject: [PATCH 22/23] fix: refactored code and adjusted shape, shape label on-behalf-of: @Mermaid-Chart --- .../mermaid/src/diagrams/sequence/svgDraw.js | 43 ++++++++----------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/packages/mermaid/src/diagrams/sequence/svgDraw.js b/packages/mermaid/src/diagrams/sequence/svgDraw.js index 95863234d..85566ab35 100644 --- a/packages/mermaid/src/diagrams/sequence/svgDraw.js +++ b/packages/mermaid/src/diagrams/sequence/svgDraw.js @@ -468,20 +468,18 @@ const drawActorTypeCollections = function (elem, actor, conf, isFooter) { rect.width = actor.width; rect.height = actor.height; rect.class = cssclass; - // rect.rx = 3; - // rect.ry = 3; rect.name = actor.name; - // 🔹 DRAW STACKED RECTANGLES + // DRAW STACKED RECTANGLES const offset = 6; const shadowRect = { ...rect, - x: rect.x + offset, - y: rect.y + (isFooter ? +offset : -offset), + x: rect.x + (isFooter ? -offset : -offset), + y: rect.y + (isFooter ? +offset : +offset), class: 'actor', }; - drawRect(g, shadowRect); const rectElem = drawRect(g, rect); // draw main rectangle on top + drawRect(g, shadowRect); actor.rectData = rect; if (actor.properties?.icon) { @@ -496,8 +494,8 @@ const drawActorTypeCollections = function (elem, actor, conf, isFooter) { _drawTextCandidateFunc(conf, hasKatex(actor.description))( actor.description, g, - rect.x, - rect.y, + rect.x - offset, + rect.y + offset, rect.width, rect.height, { class: `actor ${ACTOR_BOX_CLASS}` }, @@ -686,13 +684,13 @@ const drawActorTypeControl = function (elem, actor, conf, isFooter) { .append('defs') .append('marker') .attr('id', 'filled-head-control') - .attr('refX', 15.5) - .attr('refY', 7) + .attr('refX', 11) + .attr('refY', 5.8) .attr('markerWidth', 20) .attr('markerHeight', 28) - .attr('orient', '180') + .attr('orient', '172.5') .append('path') - .attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z'); + .attr('d', 'M 14.4 5.6 L 7.2 10.4 L 8.8 5.6 L 7.2 0.8 Z'); // Draw the base circle actElem @@ -717,7 +715,7 @@ const drawActorTypeControl = function (elem, actor, conf, isFooter) { actor.description, actElem, rect.x, - rect.y + 30, + rect.y + r + (isFooter ? 5 : 10), rect.width, rect.height, { class: `actor ${ACTOR_MAN_FIGURE_CLASS}` }, @@ -753,7 +751,7 @@ const drawActorTypeEntity = function (elem, actor, conf, isFooter) { rect.class = 'actor'; const cx = actor.x + actor.width / 2; - const cy = actorY + 25; + const cy = actorY + (!isFooter ? 25 : 10); const r = 18; actElem @@ -797,7 +795,7 @@ const drawActorTypeEntity = function (elem, actor, conf, isFooter) { actor.description, actElem, rect.x, - rect.y + (!isFooter ? (cy + r - actorY) / 2 : (cy - actorY) / 2 + r + 2), + rect.y + (!isFooter ? (cy + r - actorY) / 2 : (cy - actorY + r - 5) / 2), rect.width, rect.height, { class: `actor ${ACTOR_MAN_FIGURE_CLASS}` }, @@ -871,7 +869,7 @@ const drawActorTypeDatabase = function (elem, actor, conf, isFooter) { rect.x = actor.x; rect.y = actorY; const w = rect.width / 4; - const h = rect.height * 0.8; + const h = rect.width / 4; const rx = w / 2; const ry = rx / (2.5 + w / 50); @@ -896,7 +894,7 @@ const drawActorTypeDatabase = function (elem, actor, conf, isFooter) { .attr('class', cssclass); if (!isFooter) { - cylinderGroup.attr('transform', `translate(${w * 1.5}, ${rect.height / 4 - 2 * ry})`); + cylinderGroup.attr('transform', `translate(${w * 1.5}, ${(rect.height + ry) / 4})`); } else { cylinderGroup.attr('transform', `translate(${w * 1.5}, ${rect.height / 4 - 2 * ry})`); } @@ -905,7 +903,7 @@ const drawActorTypeDatabase = function (elem, actor, conf, isFooter) { actor.description, g, rect.x, - rect.y + (!isFooter ? rect.height / 2 : h / 2 + ry), + rect.y + (!isFooter ? (rect.height + ry) / 2 : (rect.height + h) / 4), rect.width, rect.height, { class: `actor ${ACTOR_BOX_CLASS}` }, @@ -961,8 +959,6 @@ const drawActorTypeBoundary = function (elem, actor, conf, isFooter) { rect.width = actor.width; rect.height = actor.height; rect.class = 'actor'; - rect.rx = 3; - rect.ry = 3; actElem .append('line') @@ -984,8 +980,7 @@ const drawActorTypeBoundary = function (elem, actor, conf, isFooter) { .append('circle') .attr('cx', actor.x + actor.width / 2) .attr('cy', actorY + 10) - .attr('r', radius) - .attr('width', actor.width); + .attr('r', radius); const bounds = actElem.node().getBBox(); actor.height = bounds.height + (conf.sequence.labelBoxHeight ?? 0); @@ -994,7 +989,7 @@ const drawActorTypeBoundary = function (elem, actor, conf, isFooter) { actor.description, actElem, rect.x, - rect.y + (!isFooter ? radius / 2 : radius / 2), + rect.y + (!isFooter ? radius / 2 + 3 : radius / 2 - 4), rect.width, rect.height, { class: `actor ${ACTOR_MAN_FIGURE_CLASS}` }, @@ -1007,8 +1002,6 @@ const drawActorTypeBoundary = function (elem, actor, conf, isFooter) { actElem.attr('transform', `translate(0,${radius / 2 + 7})`); } - // actElem.attr('transform', `translate(${rect.width / 2}, ${actorY + rect.height / 2})`); - return actor.height; }; From d86e46b705c2a1d4c4853fbf953e56dd6e6b44a6 Mon Sep 17 00:00:00 2001 From: omkarht Date: Tue, 19 Aug 2025 13:44:11 +0530 Subject: [PATCH 23/23] fix: refactored test cases on-behalf-of: @Mermaid-Chart --- .../diagrams/sequence/sequenceDiagram.spec.js | 142 ++++++++++++++++-- 1 file changed, 126 insertions(+), 16 deletions(-) diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js index 61738ae5e..f01ff9ec7 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js +++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js @@ -2058,26 +2058,52 @@ Bob->>Alice:Got it! expect(messages[0].from).toBe('Alice'); expect(messages[0].to).toBe('Bob'); }); - describe('when parsing extended participant syntax ', () => { - it('should parse a message', async () => { - const actor1 = 'database'; + describe('when parsing extended participant syntax', () => { + it('should parse participants with different quote styles and whitespace', async () => { const diagram = await Diagram.fromText(` - sequenceDiagram - participant Alice@{ "type" : "database" } - participant Bob@{ "type" : "database" } - participant Carl@{ type: "database" } - participant David@{ "type" : 'database' } - participant Eve@{ type: 'database' } - participant Favela@{ "type" : "database" } - Bob->>+Alice: Hi Alice - Alice->>+Bob: Hi Bob - `); + sequenceDiagram + participant Alice@{ "type" : "database" } + participant Bob@{ "type" : "database" } + participant Carl@{ type: "database" } + participant David@{ "type" : 'database' } + participant Eve@{ type: 'database' } + participant Favela@{ "type" : "database" } + Bob->>+Alice: Hi Alice + Alice->>+Bob: Hi Bob + `); + const actors = diagram.db.getActors(); + + expect(actors.get('Alice').type).toBe('database'); + expect(actors.get('Alice').description).toBe('Alice'); + + expect(actors.get('Bob').type).toBe('database'); + expect(actors.get('Bob').description).toBe('Bob'); + + expect(actors.get('Carl').type).toBe('database'); + expect(actors.get('Carl').description).toBe('Carl'); + + expect(actors.get('David').type).toBe('database'); + expect(actors.get('David').description).toBe('David'); + + expect(actors.get('Eve').type).toBe('database'); + expect(actors.get('Eve').description).toBe('Eve'); + + expect(actors.get('Favela').type).toBe('database'); + expect(actors.get('Favela').description).toBe('Favela'); + + // Verify messages were parsed correctly const messages = diagram.db.getMessages(); + expect(messages.length).toBe(4); // 2 messages + 2 activation messages + expect(messages[0].from).toBe('Bob'); + expect(messages[0].to).toBe('Alice'); + expect(messages[0].message).toBe('Hi Alice'); + expect(messages[2].from).toBe('Alice'); // Second message (index 2 due to activation) + expect(messages[2].to).toBe('Bob'); + expect(messages[2].message).toBe('Hi Bob'); }); - it('should parse a message', async () => { - const actor1 = 'database'; + it('should parse mixed participant types with extended syntax', async () => { const diagram = await Diagram.fromText(` sequenceDiagram participant lead @@ -2085,9 +2111,93 @@ Bob->>Alice:Got it! API->>+Database: getUserb Database-->>-API: userb dsa --> Database: hello - `); +`); + // Verify actors were created + const actors = diagram.db.getActors(); + + expect(actors.get('lead').type).toBe('participant'); + expect(actors.get('lead').description).toBe('lead'); + + // Participant with extended syntax + expect(actors.get('dsa').type).toBe('queue'); + expect(actors.get('dsa').description).toBe('dsa'); + + // Implicitly created actors (from messages) + expect(actors.get('API').type).toBe('participant'); + expect(actors.get('API').description).toBe('API'); + + expect(actors.get('Database').type).toBe('participant'); + expect(actors.get('Database').description).toBe('Database'); + + // Verify messages were parsed correctly const messages = diagram.db.getMessages(); + expect(messages.length).toBe(5); // 3 messages + 2 activation messages + + // First message with activation + expect(messages[0].from).toBe('API'); + expect(messages[0].to).toBe('Database'); + expect(messages[0].message).toBe('getUserb'); + expect(messages[0].activate).toBe(true); + + // Second message with deactivation + expect(messages[2].from).toBe('Database'); + expect(messages[2].to).toBe('API'); + expect(messages[2].message).toBe('userb'); + + // Third message + expect(messages[4].from).toBe('dsa'); + expect(messages[4].to).toBe('Database'); + expect(messages[4].message).toBe('hello'); + }); + + it('should fail for malformed JSON in participant definition', async () => { + const invalidDiagram = ` + sequenceDiagram + participant D@{ "type: "entity" } + participant E@{ "type": "dat + abase } + `; + + let error = false; + try { + await mermaidAPI.parse(invalidDiagram); + } catch (e) { + error = true; + } + expect(error).toBe(true); + }); + + it('should fail for missing colon separator', async () => { + const invalidDiagram = ` + sequenceDiagram + participant C@{ "type" "control" } + C ->> C: action + `; + + let error = false; + try { + await mermaidAPI.parse(invalidDiagram); + } catch (e) { + error = true; + } + expect(error).toBe(true); + }); + + it('should fail for missing closing brace', async () => { + const invalidDiagram = ` + sequenceDiagram + participant E@{ "type": "entity" + E ->> E: process + `; + + let error = false; + try { + await mermaidAPI.parse(invalidDiagram); + } catch (e) { + error = true; + } + expect(error).toBe(true); }); }); describe('participant type parsing', () => {