diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 885868c16..c27b9decd 120000 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1 +1 @@ -./packages/mermaid/src/docs/community/contributing.md \ No newline at end of file +./packages/mermaid/src/docs/community/contributing.md diff --git a/cypress/integration/rendering/classDiagram-elk-v3.spec.js b/cypress/integration/rendering/classDiagram-elk-v3.spec.js index ee6ca0b2b..54b6c4280 100644 --- a/cypress/integration/rendering/classDiagram-elk-v3.spec.js +++ b/cypress/integration/rendering/classDiagram-elk-v3.spec.js @@ -650,12 +650,12 @@ class Class10 { logLevel: 1, htmlLabels: true, layout: 'elk' } ); }); - it('ELK: should render a class with a text label, members and annotation', () => { + it('ELK: should render a class with a text label, attribute and annotation', () => { imgSnapshotTest( `classDiagram class C1["Class 1 with text label"] { <<interface>> - +member1 + +attribute1 } C1 --> C2`, { logLevel: 1, htmlLabels: true, layout: 'elk' } diff --git a/cypress/integration/rendering/classDiagram-handDrawn-v3.spec.js b/cypress/integration/rendering/classDiagram-handDrawn-v3.spec.js index 32a82c089..ce4a9d9ce 100644 --- a/cypress/integration/rendering/classDiagram-handDrawn-v3.spec.js +++ b/cypress/integration/rendering/classDiagram-handDrawn-v3.spec.js @@ -650,12 +650,12 @@ class Class10 { logLevel: 1, htmlLabels: true, look: 'handDrawn' } ); }); - it('HD: should render a class with a text label, members and annotation', () => { + it('HD: should render a class with a text label, attribute and annotation', () => { imgSnapshotTest( `classDiagram class C1["Class 1 with text label"] { <<interface>> - +member1 + +attribute1 } C1 --> C2`, { logLevel: 1, htmlLabels: true, look: 'handDrawn' } diff --git a/cypress/integration/rendering/classDiagram-v2.spec.js b/cypress/integration/rendering/classDiagram-v2.spec.js index 0c5dbc04b..da7fc58c2 100644 --- a/cypress/integration/rendering/classDiagram-v2.spec.js +++ b/cypress/integration/rendering/classDiagram-v2.spec.js @@ -500,12 +500,12 @@ class Class10 C1 --> C2` ); }); - it('should render a class with a text label, members and annotation', () => { + it('should render a class with a text label, attribute and annotation', () => { imgSnapshotTest( `classDiagram class C1["Class 1 with text label"] { <<interface>> - +member1 + +attribute1 } C1 --> C2` ); diff --git a/cypress/integration/rendering/classDiagram-v3.spec.js b/cypress/integration/rendering/classDiagram-v3.spec.js index 626d6fcea..18047dee4 100644 --- a/cypress/integration/rendering/classDiagram-v3.spec.js +++ b/cypress/integration/rendering/classDiagram-v3.spec.js @@ -647,12 +647,12 @@ class Class10 C1 --> C2` ); }); - it('should render a class with a text label, members and annotation', () => { + it('should render a class with a text label, attribute and annotation', () => { imgSnapshotTest( `classDiagram class C1["Class 1 with text label"] { <<interface>> - +member1 + +attribute1 } C1 --> C2` ); diff --git a/cypress/integration/rendering/classDiagram.spec.js b/cypress/integration/rendering/classDiagram.spec.js index a98a359ed..7c68553bf 100644 --- a/cypress/integration/rendering/classDiagram.spec.js +++ b/cypress/integration/rendering/classDiagram.spec.js @@ -430,7 +430,7 @@ describe('Class diagram', () => { class \`This\nTitle\nHas\nMany\nNewlines\` { +String Also -Stirng Many - #int Members + #int attribute +And() -Many() #Methods() @@ -444,7 +444,7 @@ describe('Class diagram', () => { class \`This\nTitle\nHas\nMany\nNewlines\` { +String Also -Stirng Many - #int Members + #int attribute +And() -Many() #Methods() @@ -460,7 +460,7 @@ describe('Class diagram', () => { class \`This\nTitle\nHas\nMany\nNewlines\` { +String Also -Stirng Many - #int Members + #int attribute +And() -Many() #Methods() diff --git a/packages/mermaid-layout-elk/src/render.ts b/packages/mermaid-layout-elk/src/render.ts index 59b97c557..61b3083c2 100644 --- a/packages/mermaid-layout-elk/src/render.ts +++ b/packages/mermaid-layout-elk/src/render.ts @@ -15,6 +15,7 @@ interface LabelData { interface NodeWithVertex extends Omit { children?: unknown[]; labelData?: LabelData; + domId?: Node['domId'] | SVGGroup | d3.Selection; } diff --git a/packages/mermaid/src/dagre-wrapper/nodes.js b/packages/mermaid/src/dagre-wrapper/nodes.js index 2677fd785..0ad6c1863 100644 --- a/packages/mermaid/src/dagre-wrapper/nodes.js +++ b/packages/mermaid/src/dagre-wrapper/nodes.js @@ -950,8 +950,8 @@ const class_box = (parent, node) => { maxWidth = classTitleBBox.width; } const classAttributes = []; - node.classData.members.forEach((member) => { - const parsedInfo = member.getDisplayDetails(); + node.classData.attributes.forEach((attribute) => { + const parsedInfo = attribute.getDisplayDetails(); let parsedText = parsedInfo.displayText; if (getConfig().flowchart.htmlLabels) { parsedText = parsedText.replace(//g, '>'); @@ -984,8 +984,8 @@ const class_box = (parent, node) => { maxHeight += lineHeight; const classMethods = []; - node.classData.methods.forEach((member) => { - const parsedInfo = member.getDisplayDetails(); + node.classData.methods.forEach((method) => { + const parsedInfo = method.getDisplayDetails(); let displayText = parsedInfo.displayText; if (getConfig().flowchart.htmlLabels) { displayText = displayText.replace(//g, '>'); @@ -1059,9 +1059,9 @@ const class_box = (parent, node) => { ((-1 * maxHeight) / 2 + verticalPos + lineHeight / 2) + ')' ); - //get the height of the bounding box of each member if exists - const memberBBox = lbl?.getBBox(); - verticalPos += (memberBBox?.height ?? 0) + rowPadding; + //get the height of the bounding box of each attribute if exists + const fieldBBox = lbl?.getBBox(); + verticalPos += (fieldBBox?.height ?? 0) + rowPadding; }); verticalPos += lineHeight; @@ -1079,8 +1079,8 @@ const class_box = (parent, node) => { 'transform', 'translate( ' + -maxWidth / 2 + ', ' + ((-1 * maxHeight) / 2 + verticalPos) + ')' ); - const memberBBox = lbl?.getBBox(); - verticalPos += (memberBBox?.height ?? 0) + rowPadding; + const methodBBox = lbl?.getBBox(); + verticalPos += (methodBBox?.height ?? 0) + rowPadding; }); rect diff --git a/packages/mermaid/src/diagrams/class/classDb.ts b/packages/mermaid/src/diagrams/class/classDb.ts index 569943736..74053c4b1 100644 --- a/packages/mermaid/src/diagrams/class/classDb.ts +++ b/packages/mermaid/src/diagrams/class/classDb.ts @@ -40,30 +40,18 @@ let functions: any[] = []; const sanitizeText = (txt: string) => common.sanitizeText(txt, getConfig()); -const splitClassNameAndType = function (_id: string) { - const id = common.sanitizeText(_id, getConfig()); +const splitClassIdAndType = function (_id: string) { + const id = sanitizeText(_id); let genericType = ''; - let className = id; + let classId = id; if (id.indexOf('~') > 0) { const split = id.split('~'); - className = sanitizeText(split[0]); + classId = sanitizeText(split[0]); genericType = sanitizeText(split[1]); } - return { className: className, type: genericType }; -}; - -export const setClassLabel = function (_id: string, label: string) { - const id = common.sanitizeText(_id, getConfig()); - if (label) { - label = sanitizeText(label); - } - - const { className } = splitClassNameAndType(id); - classes.get(className)!.label = label; - classes.get(className)!.text = - `${label}${classes.get(className)!.type ? `<${classes.get(className)!.type}>` : ''}`; + return { classId: classId, type: genericType }; }; /** @@ -72,28 +60,33 @@ export const setClassLabel = function (_id: string, label: string) { * @param id - Id of the class to add * @public */ -export const addClass = function (_id: string) { - const id = common.sanitizeText(_id, getConfig()); - const { className, type } = splitClassNameAndType(id); - // Only add class if not exists - if (classes.has(className)) { +export const addClass = function (_id: string, label?: string) { + const id = sanitizeText(_id); + const { classId, type } = splitClassIdAndType(id); + let newLabel = classId; + + if (classes.has(classId)) { return; } - // alert('Adding class: ' + className); - const name = common.sanitizeText(className, getConfig()); - // alert('Adding class after: ' + name); - classes.set(name, { - id: name, + + if (label) { + newLabel = sanitizeText(label); + } + + const text = `${newLabel}${type ? `<${type}>` : ''}`; + + classes.set(classId, { + id: classId, type: type, - label: name, - text: `${name}${type ? `<${type}>` : ''}`, + label: newLabel, + text: text, shape: 'classBox', cssClasses: 'default', methods: [], - members: [], + attributes: [], annotations: [], styles: [], - domId: MERMAID_DOM_ID_PREFIX + name + '-' + classCounter, + domId: MERMAID_DOM_ID_PREFIX + classId + '-' + classCounter, } as ClassNode); classCounter++; @@ -116,7 +109,7 @@ const addInterface = function (label: string, classId: string) { * @public */ export const lookUpDomId = function (_id: string): string { - const id = common.sanitizeText(_id, getConfig()); + const id = sanitizeText(_id); if (classes.has(id)) { return classes.get(id)!.domId; } @@ -182,18 +175,12 @@ export const addRelation = function (classRelation: ClassRelation) { addClass(classRelation.id2); } - classRelation.id1 = splitClassNameAndType(classRelation.id1).className; - classRelation.id2 = splitClassNameAndType(classRelation.id2).className; + classRelation.id1 = splitClassIdAndType(classRelation.id1).classId; + classRelation.id2 = splitClassIdAndType(classRelation.id2).classId; - classRelation.relationTitle1 = common.sanitizeText( - classRelation.relationTitle1.trim(), - getConfig() - ); + classRelation.relationTitle1 = sanitizeText(classRelation.relationTitle1.trim()); - classRelation.relationTitle2 = common.sanitizeText( - classRelation.relationTitle2.trim(), - getConfig() - ); + classRelation.relationTitle2 = sanitizeText(classRelation.relationTitle2.trim()); relations.push(classRelation); }; @@ -202,29 +189,29 @@ export const addRelation = function (classRelation: ClassRelation) { * Adds an annotation to the specified class Annotations mark special properties of the given type * (like 'interface' or 'service') * - * @param className - The class name + * @param classId - The class name * @param annotation - The name of the annotation without any brackets * @public */ export const addAnnotation = function (className: string, annotation: string) { - const validatedClassName = splitClassNameAndType(className).className; + const validatedClassName = splitClassIdAndType(className).classId; classes.get(validatedClassName)!.annotations.push(annotation); }; /** * Adds a member to the specified class * - * @param className - The class name + * @param classId - The class name * @param member - The full name of the member. If the member is enclosed in `<>` it is * treated as an annotation If the member is ending with a closing bracket ) it is treated as a * method Otherwise the member will be treated as a normal property * @public */ -export const addMember = function (className: string, member: string) { - addClass(className); +export const addMember = function (classId: string, member: string) { + addClass(classId); - const validatedClassName = splitClassNameAndType(className).className; - const theClass = classes.get(validatedClassName)!; + const validatedClassId = splitClassIdAndType(classId).classId; + const theClass = classes.get(validatedClassId)!; if (typeof member === 'string') { // Member can contain white spaces, we trim them out @@ -237,7 +224,7 @@ export const addMember = function (className: string, member: string) { //its a method theClass.methods.push(new ClassMember(memberString, 'method')); } else if (memberString) { - theClass.members.push(new ClassMember(memberString, 'attribute')); + theClass.attributes.push(new ClassMember(memberString, 'attribute')); } } }; @@ -377,7 +364,7 @@ export const setClickEvent = function (ids: string, functionName: string, functi }; const setClickFunc = function (_domId: string, functionName: string, functionArgs: string) { - const domId = common.sanitizeText(_domId, getConfig()); + const domId = sanitizeText(_domId); const config = getConfig(); if (config.securityLevel !== 'loose') { return; @@ -520,17 +507,15 @@ const getNamespaces = function (): NamespaceMap { * Function called by parser when a namespace definition has been found. * * @param id - Id of the namespace to add - * @param classNames - Ids of the class to add + * @param classIds - Ids of the class to add * @public */ -export const addClassesToNamespace = function (id: string, classNames: string[]) { - if (!namespaces.has(id)) { - return; - } - for (const name of classNames) { - const { className } = splitClassNameAndType(name); - classes.get(className)!.parent = id; - namespaces.get(id)!.classes.set(className, classes.get(className)!); +export const addClassesToNamespace = function (_id: string, classIds: string[]) { + addNamespace(_id); + for (const id of classIds) { + const { classId } = splitClassIdAndType(id); + classes.get(classId)!.parent = _id; + namespaces.get(_id)!.classes.set(classId, classes.get(classId)!); } }; @@ -726,7 +711,6 @@ export default { lookUpDomId, setDiagramTitle, getDiagramTitle, - setClassLabel, addNamespace, addClassesToNamespace, getNamespace, diff --git a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts index 40027f27e..bc9bcaa56 100644 --- a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts +++ b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts @@ -212,29 +212,29 @@ describe('given a basic class diagram, ', function () { expect(c2.label).toBe('Class 2 with chars @?'); }); - it('should parse a class with a text label and member', () => { - const str = 'classDiagram\n' + 'class C1["Class 1 with text label"]\n' + 'C1: member1'; + it('should parse a class with a text label and attribute', () => { + const str = 'classDiagram\n' + 'class C1["Class 1 with text label"]\n' + 'C1: attribute1'; parser.parse(str); const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); - expect(c1.members.length).toBe(1); - expect(c1.members[0].getDisplayDetails().displayText).toBe('member1'); + expect(c1.attributes.length).toBe(1); + expect(c1.attributes[0].getDisplayDetails().displayText).toBe('attribute1'); }); - it('should parse a class with a text label, member and annotation', () => { + it('should parse a class with a text label, attribute and annotation', () => { const str = 'classDiagram\n' + 'class C1["Class 1 with text label"]\n' + '<> C1\n' + - 'C1 : int member1'; + 'C1 : int attribute1'; parser.parse(str); const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); - expect(c1.members.length).toBe(1); - expect(c1.members[0].getDisplayDetails().displayText).toBe('int member1'); + expect(c1.attributes.length).toBe(1); + expect(c1.attributes[0].getDisplayDetails().displayText).toBe('int attribute1'); expect(c1.annotations.length).toBe(1); expect(c1.annotations[0]).toBe('interface'); }); @@ -253,14 +253,14 @@ describe('given a basic class diagram, ', function () { const str = 'classDiagram\n' + 'class C1["Class 1 with text label"]\n' + - 'C1 : int member1\n' + + 'C1 : int attribute1\n' + 'cssClass "C1" styleClass'; parser.parse(str); const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); - expect(c1.members[0].getDisplayDetails().displayText).toBe('int member1'); + expect(c1.attributes[0].getDisplayDetails().displayText).toBe('int attribute1'); expect(c1.cssClasses).toBe('default styleClass'); }); @@ -268,7 +268,7 @@ describe('given a basic class diagram, ', function () { const str = 'classDiagram\n' + 'class C1["Class 1 with text label"]\n' + - 'C1 : int member1\n' + + 'C1 : int attribute1\n' + 'class C2["Long long long long long long long long long long label"]\n' + 'cssClass "C1,C2" styleClass'; @@ -486,7 +486,7 @@ class C13["With Città foreign language"] expect(studentClass).toMatchObject({ id: 'Student', label: 'Student', - members: [ + attributes: [ expect.objectContaining({ id: 'idCard : IdCard', visibility: '-', @@ -500,11 +500,7 @@ class C13["With Città foreign language"] expect(classDb.getClasses().get('Student')).toMatchInlineSnapshot(` { "annotations": [], - "cssClasses": "default", - "domId": "classId-Student-141", - "id": "Student", - "label": "Student", - "members": [ + "attributes": [ ClassMember { "classifier": "", "id": "idCard : IdCard", @@ -513,6 +509,10 @@ class C13["With Città foreign language"] "visibility": "-", }, ], + "cssClasses": "default", + "domId": "classId-Student-141", + "id": "Student", + "label": "Student", "methods": [], "shape": "classBox", "styles": [], @@ -569,7 +569,7 @@ class C13["With Città foreign language"] parser.yy = classDb; }); - it('should handle member definitions', function () { + it('should handle attribute definitions', function () { const str = 'classDiagram\n' + 'class Car{\n' + '+int wheels\n' + '}'; parser.parse(str); @@ -588,7 +588,7 @@ class C13["With Città foreign language"] parser.parse(str); }); - it('should handle member and method definitions', () => { + it('should handle attribute and method definitions', () => { const str = 'classDiagram\n' + 'class Dummy_Class {\n' + 'String data\n' + 'void methods()\n' + '}'; @@ -611,45 +611,46 @@ class C13["With Città foreign language"] const str = 'classDiagram\n' + 'class Class1 {\n' + - 'int testMember\n' + + 'int testAttribute\n' + 'test()\n' + - 'string fooMember\n' + + 'string fooAttribute\n' + 'foo()\n' + '}'; parser.parse(str); const actual = parser.yy.getClass('Class1'); - expect(actual.members.length).toBe(2); + expect(actual.attributes.length).toBe(2); expect(actual.methods.length).toBe(2); - expect(actual.members[0].getDisplayDetails().displayText).toBe('int testMember'); - expect(actual.members[1].getDisplayDetails().displayText).toBe('string fooMember'); + expect(actual.attributes[0].getDisplayDetails().displayText).toBe('int testAttribute'); + expect(actual.attributes[1].getDisplayDetails().displayText).toBe('string fooAttribute'); expect(actual.methods[0].getDisplayDetails().displayText).toBe('test()'); expect(actual.methods[1].getDisplayDetails().displayText).toBe('foo()'); }); - it('should parse a class with a text label and members', () => { - const str = 'classDiagram\n' + 'class C1["Class 1 with text label"] {\n' + '+member1\n' + '}'; + it('should parse a class with a text label and attribute', () => { + const str = + 'classDiagram\n' + 'class C1["Class 1 with text label"] {\n' + '+attributes1\n' + '}'; parser.parse(str); const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); - expect(c1.members.length).toBe(1); - expect(c1.members[0].getDisplayDetails().displayText).toBe('+member1'); + expect(c1.attributes.length).toBe(1); + expect(c1.attributes[0].getDisplayDetails().displayText).toBe('+attributes1'); }); - it('should parse a class with a text label, members and annotation', () => { + it('should parse a class with a text label, attribute and annotation', () => { const str = 'classDiagram\n' + 'class C1["Class 1 with text label"] {\n' + '<>\n' + - '+member1\n' + + '+attribute1\n' + '}'; parser.parse(str); const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); - expect(c1.members.length).toBe(1); - expect(c1.members[0].getDisplayDetails().displayText).toBe('+member1'); + expect(c1.attributes.length).toBe(1); + expect(c1.attributes[0].getDisplayDetails().displayText).toBe('+attribute1'); expect(c1.annotations.length).toBe(1); expect(c1.annotations[0]).toBe('interface'); }); @@ -868,12 +869,12 @@ foo() const actual = parser.yy.getClass('Class1'); expect(actual.annotations.length).toBe(1); - expect(actual.members.length).toBe(0); - expect(actual.methods.length).toBe(0); + expect(actual.attributes.length).toBe(0); + expect(actual.attributes.length).toBe(0); expect(actual.annotations[0]).toBe('interface'); }); - it('should handle class annotations with members and methods', function () { + it('should handle class annotations with attributes and methods', function () { const str = 'classDiagram\n' + 'class Class1\n' + @@ -884,7 +885,7 @@ foo() const actual = parser.yy.getClass('Class1'); expect(actual.annotations.length).toBe(1); - expect(actual.members.length).toBe(1); + expect(actual.attributes.length).toBe(1); expect(actual.methods.length).toBe(1); expect(actual.annotations[0]).toBe('interface'); }); @@ -895,12 +896,12 @@ foo() const actual = parser.yy.getClass('Class1'); expect(actual.annotations.length).toBe(1); - expect(actual.members.length).toBe(0); + expect(actual.attributes.length).toBe(0); expect(actual.methods.length).toBe(0); expect(actual.annotations[0]).toBe('interface'); }); - it('should handle class annotations in brackets with members and methods', function () { + it('should handle class annotations in brackets with attributes and methods', function () { const str = 'classDiagram\n' + 'class Class1 {\n' + @@ -912,41 +913,41 @@ foo() const actual = parser.yy.getClass('Class1'); expect(actual.annotations.length).toBe(1); - expect(actual.members.length).toBe(1); + expect(actual.attributes.length).toBe(1); expect(actual.methods.length).toBe(1); expect(actual.annotations[0]).toBe('interface'); }); }); }); -describe('given a class diagram with members and methods ', function () { - describe('when parsing members', function () { +describe('given a class diagram with attributes and methods ', function () { + describe('when parsing attributes', function () { beforeEach(function () { classDb.clear(); parser.yy = classDb; }); - it('should handle simple member declaration', function () { + it('should handle simple attribute declaration', function () { const str = 'classDiagram\n' + 'class Car\n' + 'Car : wheels'; parser.parse(str); }); - it('should handle direct member declaration', function () { + it('should handle direct attribute declaration', function () { parser.parse('classDiagram\n' + 'Car : wheels'); const car = classDb.getClass('Car'); - expect(car.members.length).toBe(1); - expect(car.members[0].id).toBe('wheels'); + expect(car.attributes.length).toBe(1); + expect(car.attributes[0].id).toBe('wheels'); }); - it('should handle direct member declaration with type', function () { + it('should handle direct attribute declaration with type', function () { parser.parse('classDiagram\n' + 'Car : int wheels'); const car = classDb.getClass('Car'); - expect(car.members.length).toBe(1); - expect(car.members[0].id).toBe('int wheels'); + expect(car.attributes.length).toBe(1); + expect(car.attributes[0].id).toBe('int wheels'); }); - it('should handle simple member declaration with type', function () { + it('should handle simple attribute declaration with type', function () { const str = 'classDiagram\n' + 'class Car\n' + 'Car : int wheels'; parser.parse(str); @@ -956,20 +957,20 @@ describe('given a class diagram with members and methods ', function () { const str = 'classDiagram\n' + 'class actual\n' + - 'actual : -int privateMember\n' + - 'actual : +int publicMember\n' + - 'actual : #int protectedMember\n' + + 'actual : -int privateAttribute\n' + + 'actual : +int publicAttribute\n' + + 'actual : #int protectedAttribute\n' + 'actual : ~int privatePackage'; parser.parse(str); const actual = parser.yy.getClass('actual'); - expect(actual.members.length).toBe(4); + expect(actual.attributes.length).toBe(4); expect(actual.methods.length).toBe(0); - expect(actual.members[0].getDisplayDetails().displayText).toBe('-int privateMember'); - expect(actual.members[1].getDisplayDetails().displayText).toBe('+int publicMember'); - expect(actual.members[2].getDisplayDetails().displayText).toBe('#int protectedMember'); - expect(actual.members[3].getDisplayDetails().displayText).toBe('~int privatePackage'); + expect(actual.attributes[0].getDisplayDetails().displayText).toBe('-int privateAttribute'); + expect(actual.attributes[1].getDisplayDetails().displayText).toBe('+int publicAttribute'); + expect(actual.attributes[2].getDisplayDetails().displayText).toBe('#int protectedAttribute'); + expect(actual.attributes[3].getDisplayDetails().displayText).toBe('~int privatePackage'); }); it('should handle generic types', function () { @@ -1020,7 +1021,7 @@ describe('given a class diagram with members and methods ', function () { const actual = parser.yy.getClass('Class1'); expect(actual.annotations.length).toBe(0); - expect(actual.members.length).toBe(0); + expect(actual.attributes.length).toBe(0); expect(actual.methods.length).toBe(1); const method = actual.methods[0]; expect(method.getDisplayDetails().displayText).toBe('someMethod()'); @@ -1033,7 +1034,7 @@ describe('given a class diagram with members and methods ', function () { const actual = parser.yy.getClass('Class1'); expect(actual.annotations.length).toBe(0); - expect(actual.members.length).toBe(0); + expect(actual.attributes.length).toBe(0); expect(actual.methods.length).toBe(1); const method = actual.methods[0]; expect(method.getDisplayDetails().displayText).toBe('someMethod()'); @@ -1051,7 +1052,7 @@ describe('given a class diagram with members and methods ', function () { parser.parse(str); }); - it('should handle generic types in members in class with brackets', function () { + it('should handle generic types in attributes in class with brackets', function () { const str = 'classDiagram\n' + 'class Car {\n' + @@ -1385,12 +1386,12 @@ describe('given a class diagram with relationships, ', function () { const testClass = parser.yy.getClass('Class1'); expect(testClass.annotations.length).toBe(1); - expect(testClass.members.length).toBe(0); + expect(testClass.attributes.length).toBe(0); expect(testClass.methods.length).toBe(0); expect(testClass.annotations[0]).toBe('interface'); }); - it('should handle class annotations with members and methods', function () { + it('should handle class annotations with attributes and methods', function () { const str = 'classDiagram\n' + 'class Class1\n' + @@ -1401,7 +1402,7 @@ describe('given a class diagram with relationships, ', function () { const testClass = parser.yy.getClass('Class1'); expect(testClass.annotations.length).toBe(1); - expect(testClass.members.length).toBe(1); + expect(testClass.attributes.length).toBe(1); expect(testClass.methods.length).toBe(1); expect(testClass.annotations[0]).toBe('interface'); }); @@ -1412,12 +1413,12 @@ describe('given a class diagram with relationships, ', function () { const testClass = parser.yy.getClass('Class1'); expect(testClass.annotations.length).toBe(1); - expect(testClass.members.length).toBe(0); + expect(testClass.attributes.length).toBe(0); expect(testClass.methods.length).toBe(0); expect(testClass.annotations[0]).toBe('interface'); }); - it('should handle class annotations in brackets with members and methods', function () { + it('should handle class annotations in brackets with attributes and methods', function () { const str = 'classDiagram\n' + 'class Class1 {\n' + @@ -1429,7 +1430,7 @@ describe('given a class diagram with relationships, ', function () { const testClass = parser.yy.getClass('Class1'); expect(testClass.annotations.length).toBe(1); - expect(testClass.members.length).toBe(1); + expect(testClass.attributes.length).toBe(1); expect(testClass.methods.length).toBe(1); expect(testClass.annotations[0]).toBe('interface'); }); @@ -1446,10 +1447,10 @@ describe('given a class diagram with relationships, ', function () { parser.parse(str); const testClass = parser.yy.getClass('Class1'); - expect(testClass.members.length).toBe(2); + expect(testClass.attributes.length).toBe(2); expect(testClass.methods.length).toBe(2); - expect(testClass.members[0].getDisplayDetails().displayText).toBe('int : test'); - expect(testClass.members[1].getDisplayDetails().displayText).toBe('string : foo'); + expect(testClass.attributes[0].getDisplayDetails().displayText).toBe('int : test'); + expect(testClass.attributes[1].getDisplayDetails().displayText).toBe('string : foo'); expect(testClass.methods[0].getDisplayDetails().displayText).toBe('test()'); expect(testClass.methods[1].getDisplayDetails().displayText).toBe('foo()'); }); @@ -1460,7 +1461,7 @@ describe('given a class diagram with relationships, ', function () { const testClass = parser.yy.getClass('Class1'); expect(testClass.annotations.length).toBe(0); - expect(testClass.members.length).toBe(0); + expect(testClass.attributes.length).toBe(0); expect(testClass.methods.length).toBe(1); const method = testClass.methods[0]; expect(method.getDisplayDetails().displayText).toBe('someMethod()'); @@ -1473,7 +1474,7 @@ describe('given a class diagram with relationships, ', function () { const testClass = parser.yy.getClass('Class1'); expect(testClass.annotations.length).toBe(0); - expect(testClass.members.length).toBe(0); + expect(testClass.attributes.length).toBe(0); expect(testClass.methods.length).toBe(1); const method = testClass.methods[0]; expect(method.getDisplayDetails().displayText).toBe('someMethod()'); @@ -1688,17 +1689,17 @@ class Class2 const testClasses = parser.yy.getClasses(); const testRelations = parser.yy.getRelations(); expect(testNamespaceA.classes.size).toBe(2); - expect(testNamespaceA.classes.get('A1').members[0].getDisplayDetails().displayText).toBe( + expect(testNamespaceA.classes.get('A1').attributes[0].getDisplayDetails().displayText).toBe( '+foo : string' ); - expect(testNamespaceA.classes.get('A2').members[0].getDisplayDetails().displayText).toBe( + expect(testNamespaceA.classes.get('A2').attributes[0].getDisplayDetails().displayText).toBe( '+bar : int' ); expect(testNamespaceB.classes.size).toBe(2); - expect(testNamespaceB.classes.get('B1').members[0].getDisplayDetails().displayText).toBe( + expect(testNamespaceB.classes.get('B1').attributes[0].getDisplayDetails().displayText).toBe( '+foo : bool' ); - expect(testNamespaceB.classes.get('B2').members[0].getDisplayDetails().displayText).toBe( + expect(testNamespaceB.classes.get('B2').attributes[0].getDisplayDetails().displayText).toBe( '+bar : float' ); expect(testClasses.size).toBe(4); @@ -1742,37 +1743,37 @@ class Class2 expect(c2.label).toBe('Class 2 with chars @?'); }); - it('should parse a class with a text label and members', () => { + it('should parse a class with a text label and attributes', () => { parser.parse(`classDiagram class C1["Class 1 with text label"] { - +member1 + +attribute1 } C1 --> C2 `); const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); - expect(c1.members.length).toBe(1); - const member = c1.members[0]; - expect(member.getDisplayDetails().displayText).toBe('+member1'); + expect(c1.attributes.length).toBe(1); + const attribute = c1.attributes[0]; + expect(attribute.getDisplayDetails().displayText).toBe('+attribute1'); const c2 = classDb.getClass('C2'); expect(c2.label).toBe('C2'); }); - it('should parse a class with a text label, members and annotation', () => { + it('should parse a class with a text label, attributes and annotation', () => { parser.parse(`classDiagram class C1["Class 1 with text label"] { <> - +member1 + +attribute1 } C1 --> C2 `); const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); - expect(c1.members.length).toBe(1); + expect(c1.attributes.length).toBe(1); expect(c1.annotations.length).toBe(1); expect(c1.annotations[0]).toBe('interface'); - const member = c1.members[0]; - expect(member.getDisplayDetails().displayText).toBe('+member1'); + const attribute = c1.attributes[0]; + expect(attribute.getDisplayDetails().displayText).toBe('+attribute1'); const c2 = classDb.getClass('C2'); expect(c2.label).toBe('C2'); @@ -1781,7 +1782,7 @@ class Class2 it('should parse a class with text label and css class shorthand', () => { parser.parse(`classDiagram class C1["Class 1 with text label"]:::styleClass { - +member1 + +attribute1 } C1 --> C2 `); @@ -1789,14 +1790,14 @@ C1 --> C2 const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); expect(c1.cssClasses).toBe('default styleClass'); - const member = c1.members[0]; - expect(member.getDisplayDetails().displayText).toBe('+member1'); + const attribute = c1.attributes[0]; + expect(attribute.getDisplayDetails().displayText).toBe('+attribute1'); }); it('should parse a class with text label and css class', () => { parser.parse(`classDiagram class C1["Class 1 with text label"] { - +member1 + +attribute1 } C1 --> C2 cssClass "C1" styleClass @@ -1805,14 +1806,14 @@ cssClass "C1" styleClass const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); expect(c1.cssClasses).toBe('default styleClass'); - const member = c1.members[0]; - expect(member.getDisplayDetails().displayText).toBe('+member1'); + const attribute = c1.attributes[0]; + expect(attribute.getDisplayDetails().displayText).toBe('+attribute1'); }); it('should parse two classes with text labels and css classes', () => { parser.parse(`classDiagram class C1["Class 1 with text label"] { - +member1 + +attribute1 } class C2["Long long long long long long long long long long label"] C1 --> C2 @@ -1831,7 +1832,7 @@ cssClass "C1,C2" styleClass it('should parse two classes with text labels and css class shorthands', () => { parser.parse(`classDiagram class C1["Class 1 with text label"]:::styleClass1 { - +member1 + +attribute1 } class C2["Class 2 !@#$%^&*() label"]:::styleClass2 C1 --> C2 diff --git a/packages/mermaid/src/diagrams/class/classTypes.ts b/packages/mermaid/src/diagrams/class/classTypes.ts index 9d0d47569..c10b984da 100644 --- a/packages/mermaid/src/diagrams/class/classTypes.ts +++ b/packages/mermaid/src/diagrams/class/classTypes.ts @@ -3,13 +3,13 @@ import { parseGenericTypes, sanitizeText } from '../common/common.js'; export interface ClassNode { id: string; - type: string; - label: string; + type?: string; + label?: string; shape: string; - text: string; + text?: string; cssClasses: string; methods: ClassMember[]; - members: ClassMember[]; + attributes: ClassMember[]; annotations: string[]; domId: string; styles: string[]; diff --git a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison index 83d9bd48e..b28f33482 100644 --- a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison +++ b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison @@ -297,7 +297,7 @@ classStatement classIdentifier : CLASS className {$$=$2; yy.addClass($2);} - | CLASS className classLabel {$$=$2; yy.addClass($2);yy.setClassLabel($2, $3);} + | CLASS className classLabel {$$=$2; yy.addClass($2, $3);} ; annotationStatement diff --git a/packages/mermaid/src/diagrams/class/shapeUtil.ts b/packages/mermaid/src/diagrams/class/shapeUtil.ts index 94c8f817a..9d4af2a81 100644 --- a/packages/mermaid/src/diagrams/class/shapeUtil.ts +++ b/packages/mermaid/src/diagrams/class/shapeUtil.ts @@ -27,12 +27,12 @@ export async function textHelper( let annotationGroup = null; let labelGroup = null; - let membersGroup = null; + let attributeGroup = null; let methodsGroup = null; let annotationGroupHeight = 0; let labelGroupHeight = 0; - let membersGroupHeight = 0; + let attributeGroupHeight = 0; annotationGroup = shapeSvg.insert('g').attr('class', 'annotation-group text'); if (node.annotations.length > 0) { @@ -48,15 +48,15 @@ export async function textHelper( const labelGroupBBox = labelGroup.node()!.getBBox(); labelGroupHeight = labelGroupBBox.height; - membersGroup = shapeSvg.insert('g').attr('class', 'members-group text'); + attributeGroup = shapeSvg.insert('g').attr('class', 'attribute-group text'); let yOffset = 0; - for (const member of node.members) { - const height = await addText(membersGroup, member, yOffset, [member.parseClassifier()]); + for (const attribute of node.attributes) { + const height = await addText(attributeGroup, attribute, yOffset, [attribute.parseClassifier()]); yOffset += height + TEXT_PADDING; } - membersGroupHeight = membersGroup.node()!.getBBox().height; - if (membersGroupHeight <= 0) { - membersGroupHeight = GAP / 2; + attributeGroupHeight = attributeGroup.node()!.getBBox().height; + if (attributeGroupHeight <= 0) { + attributeGroupHeight = GAP / 2; } methodsGroup = shapeSvg.insert('g').attr('class', 'methods-group text'); @@ -79,14 +79,14 @@ export async function textHelper( bbox = shapeSvg.node()!.getBBox(); - membersGroup.attr( + attributeGroup.attr( 'transform', `translate(${0}, ${annotationGroupHeight + labelGroupHeight + GAP * 2})` ); bbox = shapeSvg.node()!.getBBox(); methodsGroup.attr( 'transform', - `translate(${0}, ${annotationGroupHeight + labelGroupHeight + (membersGroupHeight ? membersGroupHeight + GAP * 4 : GAP * 2)})` + `translate(${0}, ${annotationGroupHeight + labelGroupHeight + (attributeGroupHeight ? attributeGroupHeight + GAP * 4 : GAP * 2)})` ); bbox = shapeSvg.node()!.getBBox(); diff --git a/packages/mermaid/src/diagrams/class/svgDraw.js b/packages/mermaid/src/diagrams/class/svgDraw.js index 73cf97aeb..10bb42a8b 100644 --- a/packages/mermaid/src/diagrams/class/svgDraw.js +++ b/packages/mermaid/src/diagrams/class/svgDraw.js @@ -190,8 +190,8 @@ export const drawClass = function (elem, classDef, conf, diagObj) { // add annotations let isFirst = true; - classDef.annotations.forEach(function (member) { - const titleText2 = title.append('tspan').text('«' + member + '»'); + classDef.annotations.forEach(function (annotation) { + const titleText2 = title.append('tspan').text('«' + annotation + '»'); if (!isFirst) { titleText2.attr('dy', conf.textHeight); } @@ -208,19 +208,19 @@ export const drawClass = function (elem, classDef, conf, diagObj) { } const titleHeight = title.node().getBBox().height; - let membersLine; - let membersBox; + let attributesLine; + let attributesBox; let methodsLine; - // don't draw box if no members - if (classDef.members.length > 0) { - membersLine = g + // don't draw box if no attributes + if (classDef.attributes.length > 0) { + attributesLine = g .append('line') // text label for the x axis .attr('x1', 0) .attr('y1', conf.padding + titleHeight + conf.dividerMargin / 2) .attr('y2', conf.padding + titleHeight + conf.dividerMargin / 2); - const members = g + const attributes = g .append('text') // text label for the x axis .attr('x', conf.padding) .attr('y', titleHeight + conf.dividerMargin + conf.textHeight) @@ -228,12 +228,12 @@ export const drawClass = function (elem, classDef, conf, diagObj) { .attr('class', 'classText'); isFirst = true; - classDef.members.forEach(function (member) { - addTspan(members, member, isFirst, conf); + classDef.attributes.forEach(function (attribute) { + addTspan(attributes, attribute, isFirst, conf); isFirst = false; }); - membersBox = members.node().getBBox(); + attributesBox = attributes.node().getBBox(); } // don't draw box if no methods @@ -241,13 +241,13 @@ export const drawClass = function (elem, classDef, conf, diagObj) { methodsLine = g .append('line') // text label for the x axis .attr('x1', 0) - .attr('y1', conf.padding + titleHeight + conf.dividerMargin + membersBox.height) - .attr('y2', conf.padding + titleHeight + conf.dividerMargin + membersBox.height); + .attr('y1', conf.padding + titleHeight + conf.dividerMargin + attributesBox.height) + .attr('y2', conf.padding + titleHeight + conf.dividerMargin + attributesBox.height); const methods = g .append('text') // text label for the x axis .attr('x', conf.padding) - .attr('y', titleHeight + 2 * conf.dividerMargin + membersBox.height + conf.textHeight) + .attr('y', titleHeight + 2 * conf.dividerMargin + attributesBox.height + conf.textHeight) .attr('fill', 'white') .attr('class', 'classText'); @@ -286,8 +286,8 @@ export const drawClass = function (elem, classDef, conf, diagObj) { title.insert('title').text(classDef.tooltip); } - if (membersLine) { - membersLine.attr('x2', rectWidth); + if (attributesLine) { + attributesLine.attr('x2', rectWidth); } if (methodsLine) { methodsLine.attr('x2', rectWidth); diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/classBox.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/classBox.ts index e35ee94ab..1caf2ce47 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/classBox.ts +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/classBox.ts @@ -18,7 +18,7 @@ export async function classBox(parent: D3Selection // Treat node as classNode const classNode = node as unknown as ClassNode; classNode.annotations = classNode.annotations ?? []; - classNode.members = classNode.members ?? []; + classNode.attributes = classNode.attributes ?? []; classNode.methods = classNode.methods ?? []; const { shapeSvg, bbox } = await textHelper(parent, node, config, useHtmlLabels, GAP); @@ -35,7 +35,7 @@ export async function classBox(parent: D3Selection } const renderExtraBox = - classNode.members.length === 0 && + classNode.attributes.length === 0 && classNode.methods.length === 0 && !config.class?.hideEmptyMembersBox; @@ -51,9 +51,9 @@ export async function classBox(parent: D3Selection const w = bbox.width; let h = bbox.height; - if (classNode.members.length === 0 && classNode.methods.length === 0) { + if (classNode.attributes.length === 0 && classNode.methods.length === 0) { h += GAP; - } else if (classNode.members.length > 0 && classNode.methods.length === 0) { + } else if (classNode.attributes.length > 0 && classNode.methods.length === 0) { h += GAP * 2; } const x = -w / 2; @@ -66,7 +66,7 @@ export async function classBox(parent: D3Selection PADDING - (renderExtraBox ? PADDING - : classNode.members.length === 0 && classNode.methods.length === 0 + : classNode.attributes.length === 0 && classNode.methods.length === 0 ? -PADDING / 2 : 0), w + 2 * PADDING, @@ -74,7 +74,7 @@ export async function classBox(parent: D3Selection 2 * PADDING + (renderExtraBox ? PADDING * 2 - : classNode.members.length === 0 && classNode.methods.length === 0 + : classNode.attributes.length === 0 && classNode.methods.length === 0 ? -PADDING : 0), options @@ -107,7 +107,7 @@ export async function classBox(parent: D3Selection PADDING - (renderExtraBox ? PADDING - : classNode.members.length === 0 && classNode.methods.length === 0 + : classNode.attributes.length === 0 && classNode.methods.length === 0 ? -PADDING / 2 : 0); if (!useHtmlLabels) { @@ -138,11 +138,11 @@ export async function classBox(parent: D3Selection const labelGroupHeight = (shapeSvg.select('.label-group').node() as SVGGraphicsElement).getBBox().height - (renderExtraBox ? PADDING / 2 : 0) || 0; - const membersGroupHeight = - (shapeSvg.select('.members-group').node() as SVGGraphicsElement).getBBox().height - + const attributeGroupHeight = + (shapeSvg.select('.attribute-group').node() as SVGGraphicsElement).getBBox().height - (renderExtraBox ? PADDING / 2 : 0) || 0; // First line (under label) - if (classNode.members.length > 0 || classNode.methods.length > 0 || renderExtraBox) { + if (classNode.attributes.length > 0 || classNode.methods.length > 0 || renderExtraBox) { const roughLine = rc.line( rectBBox.x, annotationGroupHeight + labelGroupHeight + y + PADDING, @@ -154,13 +154,13 @@ export async function classBox(parent: D3Selection line.attr('class', 'divider').attr('style', styles); } - // Second line (under members) - if (renderExtraBox || classNode.members.length > 0 || classNode.methods.length > 0) { + // Second line (under attributes) + if (renderExtraBox || classNode.attributes.length > 0 || classNode.methods.length > 0) { const roughLine = rc.line( rectBBox.x, - annotationGroupHeight + labelGroupHeight + membersGroupHeight + y + GAP * 2 + PADDING, + annotationGroupHeight + labelGroupHeight + attributeGroupHeight + y + GAP * 2 + PADDING, rectBBox.x + rectBBox.width, - annotationGroupHeight + labelGroupHeight + membersGroupHeight + y + PADDING + GAP * 2, + annotationGroupHeight + labelGroupHeight + attributeGroupHeight + y + PADDING + GAP * 2, options ); const line = shapeSvg.insert(() => roughLine);