Skip to content

Commit

Permalink
Merge pull request #5849 from ReneLombard/bug/5487-add-support-for-ne…
Browse files Browse the repository at this point in the history
…sted-classes

Updated the code to include nested namespaces for class diagrams
  • Loading branch information
sidharthv96 authored Oct 3, 2024
2 parents a5559c6 + 07d7704 commit f6adca9
Show file tree
Hide file tree
Showing 5 changed files with 256 additions and 37 deletions.
17 changes: 17 additions & 0 deletions .changeset/add-nested-namespaces.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
'@mermaid': patch
---

Fixed an issue when the mermaid classdiagram crashes when adding a . to the namespace.
Forexample

```mermaid
classDiagram
namespace Company.Project.Module {
class GenericClass~T~ {
+addItem(item: T)
+getItem() T
}
}
```
59 changes: 59 additions & 0 deletions cypress/integration/rendering/classDiagram-v2.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -581,4 +581,63 @@ class C13["With Città foreign language"]
{ logLevel: 1, flowchart: { htmlLabels: false } }
);
});

it('renders a class diagram with a generic class in a namespace', () => {
const diagramDefinition = `
classDiagram-v2
namespace Company.Project.Module {
class GenericClass~T~ {
+addItem(item: T)
+getItem() T
}
}
`;

imgSnapshotTest(diagramDefinition);
});

it('renders a class diagram with nested namespaces and relationships', () => {
const diagramDefinition = `
classDiagram-v2
namespace Company.Project.Module.SubModule {
class Report {
+generatePDF(data: List)
+generateCSV(data: List)
}
}
namespace Company.Project.Module {
class Admin {
+generateReport()
}
}
Admin --> Report : generates
`;

imgSnapshotTest(diagramDefinition);
});

it('renders a class diagram with multiple classes and relationships in a namespace', () => {
const diagramDefinition = `
classDiagram-v2
namespace Company.Project.Module {
class User {
+login(username: String, password: String)
+logout()
}
class Admin {
+addUser(user: User)
+removeUser(user: User)
+generateReport()
}
class Report {
+generatePDF(reportData: List)
+generateCSV(reportData: List)
}
}
Admin --> User : manages
Admin --> Report : generates
`;

imgSnapshotTest(diagramDefinition);
});
});
85 changes: 71 additions & 14 deletions demos/classchart.html
Original file line number Diff line number Diff line change
Expand Up @@ -159,30 +159,87 @@ <h1>Class diagram demos</h1>
class People List~List~Person~~
</pre>
<hr />

<pre class="mermaid">
classDiagram
A1 --> B1
namespace A {
class A1 {
+foo : string
namespace Company.Project.Module {
class GenericClass~T~ {
+addItem(item: T)
+getItem() T
}
}
</pre>
<hr />
<pre class="mermaid">
classDiagram
namespace Company.Project.Module.SubModule {
class Report {
+generatePDF(data: List)
+generateCSV(data: List)
}
class A2 {
+bar : int
}
namespace Company.Project.Module {
class Admin {
+generateReport()
}
}
namespace B {
class B1 {
+foo : bool
Admin --> Report : generates
</pre>
<pre class="mermaid">
classDiagram
namespace Company.Project.Module {
class User {
+login(username: String, password: String)
+logout()
}
class Admin {
+addUser(user: User)
+removeUser(user: User)
+generateReport()
}
class B2 {
+bar : float
class Report {
+generatePDF(reportData: List)
+generateCSV(reportData: List)
}
}
A2 --> B2
Admin --> User : manages
Admin --> Report : generates
</pre>
<hr />

<pre class="mermaid">
classDiagram
namespace Shapes {
class Shape {
+calculateArea() double
}
class Circle {
+double radius
}
class Square {
+double side
}
}

Shape <|-- Circle
Shape <|-- Square

namespace Vehicles {
class Vehicle {
+String brand
}
class Car {
+int horsepower
}
class Bike {
+boolean hasGears
}
}

Vehicle <|-- Car
Vehicle <|-- Bike
Car --> Circle : "Logo Shape"
Bike --> Square : "Logo Shape"

</pre>
<script type="module">
import mermaid from './mermaid.esm.mjs';
mermaid.initialize({
Expand Down
124 changes: 104 additions & 20 deletions packages/mermaid/src/diagrams/class/classDiagram.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,78 @@ describe('given a basic class diagram, ', function () {
classDb.clear();
parser.yy = classDb;
});
it('should handle classes within namespaces', () => {
const str = `classDiagram
namespace Company.Project {
class User {
+login(username: String, password: String)
+logout()
}
}
namespace Company.Project.Module {
class Admin {
+addUser(user: User)
+removeUser(user: User)
}
}`;

parser.parse(str);

const user = classDb.getClass('User');
const admin = classDb.getClass('Admin');

// Check if the classes are correctly registered under their respective namespaces
expect(user.parent).toBe('Company.Project');
expect(admin.parent).toBe('Company.Project.Module');
expect(user.methods.length).toBe(2);
expect(admin.methods.length).toBe(2);
});

it('should handle generic classes within namespaces', () => {
const str = `classDiagram
namespace Company.Project.Module {
class GenericClass~T~ {
+addItem(item: T)
+getItem() T
}
}`;

parser.parse(str);

const genericClass = classDb.getClass('GenericClass');
expect(genericClass.type).toBe('T');
expect(genericClass.methods[0].getDisplayDetails().displayText).toBe('+addItem(item: T)');
expect(genericClass.methods[1].getDisplayDetails().displayText).toBe('+getItem() : T');
});

it('should handle nested namespaces and relationships', () => {
const str = ` classDiagram
namespace Company.Project.Module.SubModule {
class Report {
+generatePDF(data: List)
+generateCSV(data: List)
}
}
namespace Company.Project.Module {
class Admin {
+generateReport()
}
}
Admin --> Report : generates`;

parser.parse(str);

const report = classDb.getClass('Report');
const admin = classDb.getClass('Admin');
const relations = classDb.getRelations();

expect(report.parent).toBe('Company.Project.Module.SubModule');
expect(admin.parent).toBe('Company.Project.Module');
expect(relations[0].id1).toBe('Admin');
expect(relations[0].id2).toBe('Report');
expect(relations[0].title).toBe('generates');
});

it('should handle accTitle and accDescr', function () {
const str = `classDiagram
accTitle: My Title
Expand Down Expand Up @@ -48,6 +120,22 @@ describe('given a basic class diagram, ', function () {
}
});

it('should handle fully qualified class names in relationships', () => {
const str = `classDiagram
namespace Company.Project.Module {
class User
class Admin
}
Admin --> User : manages`;

parser.parse(str);

const relations = classDb.getRelations();
expect(relations[0].id1).toBe('Admin');
expect(relations[0].id2).toBe('User');
expect(relations[0].title).toBe('manages');
});

it('should handle backquoted class names', function () {
const str = 'classDiagram\n' + 'class `Car`';

Expand Down Expand Up @@ -393,27 +481,23 @@ class C13["With Città foreign language"]
Student "1" --o "1" IdCard : carries
Student "1" --o "1" Bike : rides`);

const studentClass = classDb.getClasses().get('Student');
// Check that the important properties match, but ignore the domId
expect(studentClass).toMatchObject({
id: 'Student',
label: 'Student',
members: [
expect.objectContaining({
id: 'idCard : IdCard',
visibility: '-',
}),
],
methods: [],
annotations: [],
cssClasses: [],
});

expect(classDb.getClasses().size).toBe(3);
expect(classDb.getClasses().get('Student')).toMatchInlineSnapshot(`
{
"annotations": [],
"cssClasses": [],
"domId": "classId-Student-134",
"id": "Student",
"label": "Student",
"members": [
ClassMember {
"classifier": "",
"id": "idCard : IdCard",
"memberType": "attribute",
"visibility": "-",
},
],
"methods": [],
"styles": [],
"type": "",
}
`);
expect(classDb.getRelations().length).toBe(2);
expect(classDb.getRelations()).toMatchInlineSnapshot(`
[
Expand Down
8 changes: 5 additions & 3 deletions packages/mermaid/src/diagrams/class/parser/classDiagram.jison
Original file line number Diff line number Diff line change
Expand Up @@ -241,11 +241,13 @@ classLabel

namespaceName
: alphaNumToken { $$=$1; }
| alphaNumToken DOT namespaceName { $$=$1+'.'+$3; }
| alphaNumToken namespaceName { $$=$1+$2; }
;

className
: alphaNumToken { $$=$1; }
| alphaNumToken DOT className { $$=$1+'.'+$3; }
| classLiteralName { $$=$1; }
| alphaNumToken className { $$=$1+$2; }
| alphaNumToken GENERICTYPE { $$=$1+'~'+$2+'~'; }
Expand All @@ -270,12 +272,12 @@ statement
;

namespaceStatement
: namespaceIdentifier STRUCT_START classStatements STRUCT_STOP {yy.addClassesToNamespace($1, $3);}
| namespaceIdentifier STRUCT_START NEWLINE classStatements STRUCT_STOP {yy.addClassesToNamespace($1, $4);}
: namespaceIdentifier STRUCT_START classStatements STRUCT_STOP { yy.addClassesToNamespace($1, $3); }
| namespaceIdentifier STRUCT_START NEWLINE classStatements STRUCT_STOP { yy.addClassesToNamespace($1, $4); }
;

namespaceIdentifier
: NAMESPACE namespaceName {$$=$2; yy.addNamespace($2);}
: NAMESPACE namespaceName { $$=$2; yy.addNamespace($2); }
;

classStatements
Expand Down

0 comments on commit f6adca9

Please sign in to comment.