update classes to handle , in generic

This commit is contained in:
Justin Greywolf
2023-06-12 11:31:29 -07:00
parent f4ffd5c965
commit 164605b442
10 changed files with 645 additions and 613 deletions

View File

@@ -80,6 +80,7 @@
Class01 : #size() Class01 : #size()
Class01 : -int chimp Class01 : -int chimp
Class01 : +int gorilla Class01 : +int gorilla
Class01 : +abstractAttribute string*
class Class10~T~ { class Class10~T~ {
<<service>> <<service>>
int id int id
@@ -122,6 +123,8 @@
classDiagram classDiagram
direction LR direction LR
Animal ()-- Dog Animal ()-- Dog
Animal ()-- Cat
note for Cat "should have no members area"
Dog : bark() Dog : bark()
Dog : species() Dog : species()
</pre> </pre>
@@ -151,6 +154,7 @@
~InternalProperty : string ~InternalProperty : string
~AnotherInternalProperty : List~List~string~~ ~AnotherInternalProperty : List~List~string~~
} }
class People~List~Person~~
</pre> </pre>
<hr /> <hr />

View File

@@ -5,7 +5,6 @@ import { getConfig } from '../config.js';
import intersect from './intersect/index.js'; import intersect from './intersect/index.js';
import createLabel from './createLabel.js'; import createLabel from './createLabel.js';
import note from './shapes/note.js'; import note from './shapes/note.js';
import { parseMember } from '../diagrams/class/svgDraw.js';
import { evaluate } from '../diagrams/common/common.js'; import { evaluate } from '../diagrams/common/common.js';
const question = async (parent, node) => { const question = async (parent, node) => {
@@ -806,8 +805,8 @@ const class_box = (parent, node) => {
maxWidth = classTitleBBox.width; maxWidth = classTitleBBox.width;
} }
const classAttributes = []; const classAttributes = [];
node.classData.members.forEach((str) => { node.classData.members.forEach((member) => {
const parsedInfo = parseMember(str); const parsedInfo = member.getDisplayDetails();
let parsedText = parsedInfo.displayText; let parsedText = parsedInfo.displayText;
if (getConfig().flowchart.htmlLabels) { if (getConfig().flowchart.htmlLabels) {
parsedText = parsedText.replace(/</g, '&lt;').replace(/>/g, '&gt;'); parsedText = parsedText.replace(/</g, '&lt;').replace(/>/g, '&gt;');
@@ -840,8 +839,8 @@ const class_box = (parent, node) => {
maxHeight += lineHeight; maxHeight += lineHeight;
const classMethods = []; const classMethods = [];
node.classData.methods.forEach((str) => { node.classData.methods.forEach((member) => {
const parsedInfo = parseMember(str); const parsedInfo = member.getDisplayDetails();
let displayText = parsedInfo.displayText; let displayText = parsedInfo.displayText;
if (getConfig().flowchart.htmlLabels) { if (getConfig().flowchart.htmlLabels) {
displayText = displayText.replace(/</g, '&lt;').replace(/>/g, '&gt;'); displayText = displayText.replace(/</g, '&lt;').replace(/>/g, '&gt;');

View File

@@ -4,6 +4,9 @@ import classParser from './classParser.js';
import { vi, describe, it, expect } from 'vitest'; import { vi, describe, it, expect } from 'vitest';
const spyOn = vi.spyOn; const spyOn = vi.spyOn;
const staticCssStyle = 'text-decoration:underline;';
const abstractCssStyle = 'font-style:italic;';
describe('given a basic class diagram, ', function () { describe('given a basic class diagram, ', function () {
describe('when parsing class definition', function () { describe('when parsing class definition', function () {
beforeEach(function () { beforeEach(function () {
@@ -127,7 +130,7 @@ describe('given a basic class diagram, ', function () {
const c1 = classParser.getClass('C1'); const c1 = classParser.getClass('C1');
expect(c1.label).toBe('Class 1 with text label'); expect(c1.label).toBe('Class 1 with text label');
expect(c1.members.length).toBe(1); expect(c1.members.length).toBe(1);
expect(c1.members[0]).toBe('member1'); expect(c1.members[0].getDisplayDetails().displayText).toBe('member1');
}); });
it('should parse a class with a text label, member and annotation', () => { it('should parse a class with a text label, member and annotation', () => {
@@ -142,7 +145,7 @@ describe('given a basic class diagram, ', function () {
const c1 = classParser.getClass('C1'); const c1 = classParser.getClass('C1');
expect(c1.label).toBe('Class 1 with text label'); expect(c1.label).toBe('Class 1 with text label');
expect(c1.members.length).toBe(1); expect(c1.members.length).toBe(1);
expect(c1.members[0]).toBe('int member1'); expect(c1.members[0].getDisplayDetails().displayText).toBe('int member1');
expect(c1.annotations.length).toBe(1); expect(c1.annotations.length).toBe(1);
expect(c1.annotations[0]).toBe('interface'); expect(c1.annotations[0]).toBe('interface');
}); });
@@ -168,7 +171,7 @@ describe('given a basic class diagram, ', function () {
const c1 = classParser.getClass('C1'); const c1 = classParser.getClass('C1');
expect(c1.label).toBe('Class 1 with text label'); expect(c1.label).toBe('Class 1 with text label');
expect(c1.members[0]).toBe('int member1'); expect(c1.members[0].getDisplayDetails().displayText).toBe('int member1');
expect(c1.cssClasses[0]).toBe('styleClass'); expect(c1.cssClasses[0]).toBe('styleClass');
}); });
@@ -315,8 +318,8 @@ class C13["With Città foreign language"]
'classDiagram\n' + 'classDiagram\n' +
'class Class1 {\n' + 'class Class1 {\n' +
'int testMember\n' + 'int testMember\n' +
'string fooMember\n' +
'test()\n' + 'test()\n' +
'string fooMember\n' +
'foo()\n' + 'foo()\n' +
'}'; '}';
parser.parse(str); parser.parse(str);
@@ -324,10 +327,10 @@ class C13["With Città foreign language"]
const actual = parser.yy.getClass('Class1'); const actual = parser.yy.getClass('Class1');
expect(actual.members.length).toBe(2); expect(actual.members.length).toBe(2);
expect(actual.methods.length).toBe(2); expect(actual.methods.length).toBe(2);
expect(actual.members[0]).toBe('int testMember'); expect(actual.members[0].getDisplayDetails().displayText).toBe('int testMember');
expect(actual.members[1]).toBe('string fooMember'); expect(actual.members[1].getDisplayDetails().displayText).toBe('string fooMember');
expect(actual.methods[0]).toBe('test()'); expect(actual.methods[0].getDisplayDetails().displayText).toBe('test()');
expect(actual.methods[1]).toBe('foo()'); expect(actual.methods[1].getDisplayDetails().displayText).toBe('foo()');
}); });
it('should parse a class with a text label and members', () => { it('should parse a class with a text label and members', () => {
@@ -337,7 +340,7 @@ class C13["With Città foreign language"]
const c1 = classParser.getClass('C1'); const c1 = classParser.getClass('C1');
expect(c1.label).toBe('Class 1 with text label'); expect(c1.label).toBe('Class 1 with text label');
expect(c1.members.length).toBe(1); expect(c1.members.length).toBe(1);
expect(c1.members[0]).toBe('+member1'); expect(c1.members[0].getDisplayDetails().displayText).toBe('+member1');
}); });
it('should parse a class with a text label, members and annotation', () => { it('should parse a class with a text label, members and annotation', () => {
@@ -352,7 +355,7 @@ class C13["With Città foreign language"]
const c1 = classParser.getClass('C1'); const c1 = classParser.getClass('C1');
expect(c1.label).toBe('Class 1 with text label'); expect(c1.label).toBe('Class 1 with text label');
expect(c1.members.length).toBe(1); expect(c1.members.length).toBe(1);
expect(c1.members[0]).toBe('+member1'); expect(c1.members[0].getDisplayDetails().displayText).toBe('+member1');
expect(c1.annotations.length).toBe(1); expect(c1.annotations.length).toBe(1);
expect(c1.annotations[0]).toBe('interface'); expect(c1.annotations[0]).toBe('interface');
}); });
@@ -655,10 +658,10 @@ describe('given a class diagram with members and methods ', function () {
const actual = parser.yy.getClass('actual'); const actual = parser.yy.getClass('actual');
expect(actual.members.length).toBe(4); expect(actual.members.length).toBe(4);
expect(actual.methods.length).toBe(0); expect(actual.methods.length).toBe(0);
expect(actual.members[0]).toBe('-int privateMember'); expect(actual.members[0].getDisplayDetails().displayText).toBe('-int privateMember');
expect(actual.members[1]).toBe('+int publicMember'); expect(actual.members[1].getDisplayDetails().displayText).toBe('+int publicMember');
expect(actual.members[2]).toBe('#int protectedMember'); expect(actual.members[2].getDisplayDetails().displayText).toBe('#int protectedMember');
expect(actual.members[3]).toBe('~int privatePackage'); expect(actual.members[3].getDisplayDetails().displayText).toBe('~int privatePackage');
}); });
it('should handle generic types', function () { it('should handle generic types', function () {
@@ -711,7 +714,9 @@ describe('given a class diagram with members and methods ', function () {
expect(actual.annotations.length).toBe(0); expect(actual.annotations.length).toBe(0);
expect(actual.members.length).toBe(0); expect(actual.members.length).toBe(0);
expect(actual.methods.length).toBe(1); expect(actual.methods.length).toBe(1);
expect(actual.methods[0]).toBe('someMethod()*'); const method = actual.methods[0];
expect(method.getDisplayDetails().displayText).toBe('someMethod()');
expect(method.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
}); });
it('should handle static methods', function () { it('should handle static methods', function () {
@@ -722,7 +727,9 @@ describe('given a class diagram with members and methods ', function () {
expect(actual.annotations.length).toBe(0); expect(actual.annotations.length).toBe(0);
expect(actual.members.length).toBe(0); expect(actual.members.length).toBe(0);
expect(actual.methods.length).toBe(1); expect(actual.methods.length).toBe(1);
expect(actual.methods[0]).toBe('someMethod()$'); const method = actual.methods[0];
expect(method.getDisplayDetails().displayText).toBe('someMethod()');
expect(method.getDisplayDetails().cssStyle).toBe(staticCssStyle);
}); });
it('should handle generic types in arguments', function () { it('should handle generic types in arguments', function () {
@@ -1167,10 +1174,10 @@ describe('given a class diagram with relationships, ', function () {
const testClass = parser.yy.getClass('Class1'); const testClass = parser.yy.getClass('Class1');
expect(testClass.members.length).toBe(2); expect(testClass.members.length).toBe(2);
expect(testClass.methods.length).toBe(2); expect(testClass.methods.length).toBe(2);
expect(testClass.members[0]).toBe('int : test'); expect(testClass.members[0].getDisplayDetails().displayText).toBe('int : test');
expect(testClass.members[1]).toBe('string : foo'); expect(testClass.members[1].getDisplayDetails().displayText).toBe('string : foo');
expect(testClass.methods[0]).toBe('test()'); expect(testClass.methods[0].getDisplayDetails().displayText).toBe('test()');
expect(testClass.methods[1]).toBe('foo()'); expect(testClass.methods[1].getDisplayDetails().displayText).toBe('foo()');
}); });
it('should handle abstract methods', function () { it('should handle abstract methods', function () {
@@ -1181,7 +1188,9 @@ describe('given a class diagram with relationships, ', function () {
expect(testClass.annotations.length).toBe(0); expect(testClass.annotations.length).toBe(0);
expect(testClass.members.length).toBe(0); expect(testClass.members.length).toBe(0);
expect(testClass.methods.length).toBe(1); expect(testClass.methods.length).toBe(1);
expect(testClass.methods[0]).toBe('someMethod()*'); const method = testClass.methods[0];
expect(method.getDisplayDetails().displayText).toBe('someMethod()');
expect(method.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
}); });
it('should handle static methods', function () { it('should handle static methods', function () {
@@ -1192,7 +1201,9 @@ describe('given a class diagram with relationships, ', function () {
expect(testClass.annotations.length).toBe(0); expect(testClass.annotations.length).toBe(0);
expect(testClass.members.length).toBe(0); expect(testClass.members.length).toBe(0);
expect(testClass.methods.length).toBe(1); expect(testClass.methods.length).toBe(1);
expect(testClass.methods[0]).toBe('someMethod()$'); const method = testClass.methods[0];
expect(method.getDisplayDetails().displayText).toBe('someMethod()');
expect(method.getDisplayDetails().cssStyle).toBe(staticCssStyle);
}); });
it('should associate link and css appropriately', function () { it('should associate link and css appropriately', function () {
@@ -1418,8 +1429,8 @@ class Class2
const c1 = classParser.getClass('C1'); const c1 = classParser.getClass('C1');
expect(c1.label).toBe('Class 1 with text label'); expect(c1.label).toBe('Class 1 with text label');
expect(c1.members.length).toBe(1); expect(c1.members.length).toBe(1);
expect(c1.members[0]).toBe('+member1'); const member = c1.members[0];
expect(member.getDisplayDetails().displayText).toBe('+member1');
const c2 = classParser.getClass('C2'); const c2 = classParser.getClass('C2');
expect(c2.label).toBe('C2'); expect(c2.label).toBe('C2');
}); });
@@ -1435,9 +1446,10 @@ class Class2
const c1 = classParser.getClass('C1'); const c1 = classParser.getClass('C1');
expect(c1.label).toBe('Class 1 with text label'); expect(c1.label).toBe('Class 1 with text label');
expect(c1.members.length).toBe(1); expect(c1.members.length).toBe(1);
expect(c1.members[0]).toBe('+member1');
expect(c1.annotations.length).toBe(1); expect(c1.annotations.length).toBe(1);
expect(c1.annotations[0]).toBe('interface'); expect(c1.annotations[0]).toBe('interface');
const member = c1.members[0];
expect(member.getDisplayDetails().displayText).toBe('+member1');
const c2 = classParser.getClass('C2'); const c2 = classParser.getClass('C2');
expect(c2.label).toBe('C2'); expect(c2.label).toBe('C2');
@@ -1454,8 +1466,9 @@ C1 --> C2
const c1 = classParser.getClass('C1'); const c1 = classParser.getClass('C1');
expect(c1.label).toBe('Class 1 with text label'); expect(c1.label).toBe('Class 1 with text label');
expect(c1.cssClasses.length).toBe(1); expect(c1.cssClasses.length).toBe(1);
expect(c1.members[0]).toBe('+member1');
expect(c1.cssClasses[0]).toBe('styleClass'); expect(c1.cssClasses[0]).toBe('styleClass');
const member = c1.members[0];
expect(member.getDisplayDetails().displayText).toBe('+member1');
}); });
it('should parse a class with text label and css class', () => { it('should parse a class with text label and css class', () => {
@@ -1470,8 +1483,9 @@ cssClass "C1" styleClass
const c1 = classParser.getClass('C1'); const c1 = classParser.getClass('C1');
expect(c1.label).toBe('Class 1 with text label'); expect(c1.label).toBe('Class 1 with text label');
expect(c1.cssClasses.length).toBe(1); expect(c1.cssClasses.length).toBe(1);
expect(c1.members[0]).toBe('+member1');
expect(c1.cssClasses[0]).toBe('styleClass'); expect(c1.cssClasses[0]).toBe('styleClass');
const member = c1.members[0];
expect(member.getDisplayDetails().displayText).toBe('+member1');
}); });
it('should parse two classes with text labels and css classes', () => { it('should parse two classes with text labels and css classes', () => {

View File

@@ -39,7 +39,12 @@ describe('when parsing class diagram', function () {
"id": "Student", "id": "Student",
"label": "Student", "label": "Student",
"members": [ "members": [
"-idCard : IdCard", ClassMember {
"classifier": "",
"id": "idCard : IdCard",
"memberType": "attribute",
"visibility": "-",
},
], ],
"methods": [], "methods": [],
"type": "", "type": "",

View File

@@ -21,6 +21,7 @@ import {
ClassMap, ClassMap,
NamespaceMap, NamespaceMap,
NamespaceNode, NamespaceNode,
ClassMember,
} from './classTypes.js'; } from './classTypes.js';
const MERMAID_DOM_ID_PREFIX = 'classId-'; const MERMAID_DOM_ID_PREFIX = 'classId-';
@@ -186,9 +187,9 @@ export const addMember = function (className: string, member: string) {
theClass.annotations.push(sanitizeText(memberString.substring(2, memberString.length - 2))); theClass.annotations.push(sanitizeText(memberString.substring(2, memberString.length - 2)));
} else if (memberString.indexOf(')') > 0) { } else if (memberString.indexOf(')') > 0) {
//its a method //its a method
theClass.methods.push(sanitizeText(memberString)); theClass.methods.push(new ClassMember(memberString, 'method'));
} else if (memberString) { } else if (memberString) {
theClass.members.push(sanitizeText(memberString)); theClass.members.push(new ClassMember(memberString, 'attribute'));
} }
} }
}; };

File diff suppressed because it is too large Load Diff

View File

@@ -26,13 +26,18 @@ export class ClassMember {
constructor(input: string, memberType: string) { constructor(input: string, memberType: string) {
this.memberType = memberType; this.memberType = memberType;
this.visibility = '';
this.classifier = '';
this.parseMember(input); this.parseMember(input);
} }
getDisplayDetails() { getDisplayDetails() {
let displayText = this.visibility + parseGenericTypes(this.id); let displayText = this.visibility + parseGenericTypes(this.id);
if (this.memberType === 'method') { if (this.memberType === 'method') {
displayText += '(' + parseGenericTypes(this.parameters.trim()) + ')' + ' ' + this.returnType; displayText += '(' + parseGenericTypes(this.parameters.trim()) + ')';
if (this.returnType) {
displayText += ' : ' + parseGenericTypes(this.returnType);
}
} }
displayText = displayText.trim(); displayText = displayText.trim();

View File

@@ -1,6 +1,7 @@
import { line, curveBasis } from 'd3'; import { line, curveBasis } from 'd3';
import utils from '../../utils.js'; import utils from '../../utils.js';
import { log } from '../../logger.js'; import { log } from '../../logger.js';
import { parseGenericTypes } from '../common/common.js';
let edgeCount = 0; let edgeCount = 0;
export const drawEdge = function (elem, path, relation, conf, diagObj) { export const drawEdge = function (elem, path, relation, conf, diagObj) {
@@ -302,7 +303,7 @@ export const getClassTitleString = function (classDef) {
let classTitleString = classDef.id; let classTitleString = classDef.id;
if (classDef.type) { if (classDef.type) {
classTitleString += '<' + classDef.type + '>'; classTitleString += '<' + parseGenericTypes(classDef.type) + '>';
} }
return classTitleString; return classTitleString;

View File

@@ -1,4 +1,5 @@
import svgDraw from './svgDraw.js'; import svgDraw from './svgDraw.js';
import { JSDOM } from 'jsdom';
describe('given a string representing a class, ', function () { describe('given a string representing a class, ', function () {
describe('when class name includes generic, ', function () { describe('when class name includes generic, ', function () {
@@ -15,7 +16,7 @@ describe('given a string representing a class, ', function () {
it('should return correct text for nested generics', function () { it('should return correct text for nested generics', function () {
const classDef = { const classDef = {
id: 'Car', id: 'Car',
type: 'T~TT~', type: 'T~T~',
label: 'Car', label: 'Car',
}; };
@@ -23,12 +24,4 @@ describe('given a string representing a class, ', function () {
expect(actual).toBe('Car<T<T>>'); expect(actual).toBe('Car<T<T>>');
}); });
}); });
describe('when class has no members, ', function () {
it('should have no members', function () {
const str = 'class Class10';
let actual = svgDraw.drawClass(str);
expect(actual.displayText).toBe('');
});
});
}); });

View File

@@ -189,7 +189,7 @@ export const parseGenericTypes = function (text: string): string {
do { do {
cleanedText = newCleanedText; cleanedText = newCleanedText;
newCleanedText = cleanedText.replace(/~([^\s,:;]+)~/, '<$1>'); newCleanedText = cleanedText.replace(/~([^:;]+)~/, '<$1>');
} while (newCleanedText != cleanedText); } while (newCleanedText != cleanedText);
return parseGenericTypes(newCleanedText); return parseGenericTypes(newCleanedText);