feat: support multiple stereotypes in class diagrams

This commit is contained in:
Justin Greywolf
2025-10-21 14:31:14 -07:00
parent fed8a523a4
commit b6b666d705
10 changed files with 198 additions and 35 deletions

View File

@@ -18,6 +18,7 @@
%x acc_descr_multiline
%x class
%x class-body
%x class-body-annotation
%x namespace
%x namespace-body
%%
@@ -82,9 +83,16 @@ Function arguments are optional: 'call <callback_name>()' simply executes 'callb
<class-body><<EOF>> return "EOF_IN_STRUCT";
<class-body>"[*]" { return 'EDGE_STATE';}
<class-body>[{] return "OPEN_IN_STRUCT";
<class-body>"<<" { this.begin("class-body-annotation"); return 'ANNOTATION_START';}
<class-body>[\n] /* nothing */
<class-body>[ \t]+ /* skip whitespace in class body */
<class-body>[^{}\n]* { return "MEMBER";}
<class-body-annotation>">>" { this.popState(); return 'ANNOTATION_END';}
<class-body-annotation>[0-9]+ return 'NUM';
<class-body-annotation>\w+ return 'ALPHA';
<class-body-annotation>[\s]+ /* ignore whitespace */
<*>"cssClass" return 'CSSCLASS';
<*>"callback" return 'CALLBACK';
<*>"link" return 'LINK';
@@ -294,12 +302,26 @@ classStatement
| classIdentifier STYLE_SEPARATOR alphaNumToken {yy.setCssClass($1, $3);}
| classIdentifier STRUCT_START members STRUCT_STOP {yy.addMembers($1,$3);}
| classIdentifier STRUCT_START STRUCT_STOP {}
| classIdentifier STRUCT_START NEWLINE members STRUCT_STOP {yy.addMembers($1,$4);}
| classIdentifier STRUCT_START annotationList members STRUCT_STOP {for(const annotation of $3) { yy.addAnnotation($1, annotation); } yy.addMembers($1,$4);}
| classIdentifier STRUCT_START annotationList STRUCT_STOP {for(const annotation of $3) { yy.addAnnotation($1, annotation); }}
| classIdentifier STRUCT_START NEWLINE annotationList members STRUCT_STOP {for(const annotation of $4) { yy.addAnnotation($1, annotation); } yy.addMembers($1,$5);}
| classIdentifier STRUCT_START NEWLINE annotationList STRUCT_STOP {for(const annotation of $4) { yy.addAnnotation($1, annotation); }}
| classIdentifier STYLE_SEPARATOR alphaNumToken STRUCT_START members STRUCT_STOP {yy.setCssClass($1, $3);yy.addMembers($1,$5);}
| classIdentifier STYLE_SEPARATOR alphaNumToken STRUCT_START NEWLINE members STRUCT_STOP {yy.setCssClass($1, $3);yy.addMembers($1,$6);}
| classIdentifier STYLE_SEPARATOR alphaNumToken STRUCT_START annotationList members STRUCT_STOP {yy.setCssClass($1, $3); for(const annotation of $5) { yy.addAnnotation($1, annotation); } yy.addMembers($1,$6);}
| classIdentifier STYLE_SEPARATOR alphaNumToken STRUCT_START annotationList STRUCT_STOP {yy.setCssClass($1, $3); for(const annotation of $5) { yy.addAnnotation($1, annotation); }}
| classIdentifier STYLE_SEPARATOR alphaNumToken STRUCT_START NEWLINE annotationList members STRUCT_STOP {yy.setCssClass($1, $3); for(const annotation of $6) { yy.addAnnotation($1, annotation); } yy.addMembers($1,$7);}
| classIdentifier STYLE_SEPARATOR alphaNumToken STRUCT_START NEWLINE annotationList STRUCT_STOP {yy.setCssClass($1, $3); for(const annotation of $6) { yy.addAnnotation($1, annotation); }}
;
classIdentifier
: CLASS className {$$=$2; yy.addClass($2);}
| CLASS className ANNOTATION_START alphaNumToken ANNOTATION_END {$$=$2; yy.addClass($2); yy.addAnnotation($2,$4);}
| CLASS className annotationList {$$=$2; yy.addClass($2); for(const annotation of $3) { yy.addAnnotation($2, annotation); }}
| CLASS className classLabel {$$=$2; yy.addClass($2);yy.setClassLabel($2, $3);}
| CLASS className classLabel ANNOTATION_START alphaNumToken ANNOTATION_END {$$=$2; yy.addClass($2);yy.setClassLabel($2, $3); yy.addAnnotation($2,$5);}
| CLASS className classLabel annotationList {$$=$2; yy.addClass($2);yy.setClassLabel($2, $3); for(const annotation of $4) { yy.addAnnotation($2, annotation); }}
;
@@ -311,6 +333,12 @@ emptyBody
annotationStatement
: ANNOTATION_START alphaNumToken ANNOTATION_END className { yy.addAnnotation($4,$2); }
| annotationList className { for(const annotation of $1) { yy.addAnnotation($2, annotation); } }
;
annotationList
: ANNOTATION_START alphaNumToken ANNOTATION_END { $$ = [$2]; }
| annotationList ANNOTATION_START alphaNumToken ANNOTATION_END { $1.push($3); $$ = $1; }
;
members

View File

@@ -36,8 +36,8 @@ export async function textHelper<T extends SVGGraphicsElement>(
annotationGroup = shapeSvg.insert('g').attr('class', 'annotation-group text');
if (node.annotations.length > 0) {
const annotation = node.annotations[0];
await addText(annotationGroup, { text: `«${annotation}»` } as unknown as ClassMember, 0);
const annotationText = node.annotations.map((a: string) => `«${a}»`).join('\n');
await addText(annotationGroup, { text: annotationText } as unknown as ClassMember, 0);
const annotationGroupBBox = annotationGroup.node()!.getBBox();
annotationGroupHeight = annotationGroupBBox.height;

View File

@@ -358,17 +358,19 @@ It is possible to annotate classes with markers to provide additional metadata a
- `<<Service>>` To represent a service class
- `<<Enumeration>>` To represent an enum
Annotations are defined within the opening `<<` and closing `>>`. There are two ways to add an annotation to a class, and either way the output will be same:
Annotations are defined within the opening `<<` and closing `>>`. There are multiple ways to add an annotation to a class, which all result in the same output, and you can add multiple annotations by adding others on the same line:
> **Tip:**
> In Mermaid class diagrams, annotations like `<<interface>>` can be attached in two ways:
>
> - **Inline with the class definition** (Recommended for consistency):
>
> ```mermaid-example
> classDiagram
> class Shape <<interface>>
> ```
> ```mermaid-example
> classDiagram
> class Shape <<interface>> <<injected>>
> ```
>
> - **Separate line after the class definition**:
>
@@ -378,25 +380,18 @@ Annotations are defined within the opening `<<` and closing `>>`. There are two
> <<interface>> Shape
> ```
>
> Both methods are fully supported and produce identical diagrams.
> However, it is recommended to use the **inline style** for better readability and consistent formatting across diagrams.
> ```mermaid-example
> classDiagram
> class Shape
> <<interface>> <<injected>> Shape
> ```
- In a **_separate line_** after a class is defined:
```mermaid-example
classDiagram
class Shape
<<interface>> Shape
Shape : noOfVertices
Shape : draw()
```
- In a **_nested structure_** along with the class definition:
> In a **_nested structure_** along with the class definition:
```mermaid-example
classDiagram
class Shape{
<<interface>>
<<interface>> <<injected>>
noOfVertices
draw()
}