Skip to content

fix(class): support nested generics in class names (#7648)#7957

Open
Develop-KIM wants to merge 2 commits into
mermaid-js:developfrom
Develop-KIM:fix/class-nested-generics
Open

fix(class): support nested generics in class names (#7648)#7957
Develop-KIM wants to merge 2 commits into
mermaid-js:developfrom
Develop-KIM:fix/class-nested-generics

Conversation

@Develop-KIM

@Develop-KIM Develop-KIM commented Jul 10, 2026

Copy link
Copy Markdown

Summary

Class definitions that use a nested generic in the class name — e.g. class People List~List~Person~~ or class List~List~Person~~ — currently fail with "Syntax error in text".

Nested generics in attribute and method positions (inside { }) already work, because those lines are consumed whole as MEMBER tokens. The bug is specific to the class-name / shorthand position, which flows through the lexer's generic start-condition.

Closes #7648
Closes #7480

Root cause

  1. Lexer (classDiagram.jison): the generic start-condition was flat — <generic>[~] popped on the first ~, so it could not balance nested ~ delimiters. List~List~Person~~ tokenized as ALPHA GENERICTYPE ALPHA …, which no className rule can assemble.
  2. splitClassNameAndType (classDb.ts): kept only id.split('~')[1], so even once parsed the nested inner type was dropped.

Fix

  • The generic lexer state now tracks nesting depth and captures the full balanced generic as a single GENERICTYPE token. A ~ immediately followed by a word character opens a nested level; the matching ~ closes it.
  • splitClassNameAndType now takes everything between the first and last ~ and converts nested generics to angle-bracket notation, e.g. List~List~Person~~List<List<Person>> (emitted as &lt;/&gt; to match how class labels are escaped elsewhere in the file).

Single-level generics (Class~T~) are unchanged.

Before / after

Input Before After
class List~List~Person~~ ❌ Syntax error List<List<Person>>
class Container~Map~String,List~Int~~~ ❌ Syntax error Container<Map<String,List<Int>>>
class Class1~T~ Class1<T> Class1<T> (unchanged)

Tests

Added 4 regression tests in classDiagram.spec.ts (nested class name, deeply nested, literal name, and the exact sample from #7648). They fail on develop and pass with this change; the full class-diagram suite (495 tests) stays green. Added a changeset.

Summary

  • Fixed class diagram parsing for nested generic types in class names and shorthand definitions.
  • Updated the lexer to track nested ~ delimiters and emit balanced GENERICTYPE tokens.
  • Updated generic formatting to preserve nested types as escaped angle brackets, such as List<List<Person>>.
  • Added four regression tests covering nested, deeply nested, backticked, and larger generic definitions.
  • Added a fix(class) changeset referencing issues #7648 and #7480.

Class definitions using a nested generic type in the class name, e.g.
`class People List~List~Person~~` or `class List~List~Person~~`, threw
"Syntax error in text". The class-diagram lexer's `generic` state was flat
and could not balance nested `~` delimiters, and `splitClassNameAndType`
only kept the first `~`-separated segment, dropping the nested type.

The lexer now tracks generic nesting depth and emits the full balanced
generic type as a single GENERICTYPE token, and `splitClassNameAndType`
converts nested generics to angle-bracket notation
(`List~List~Person~~` -> `List<List<Person>>`). Nested generics in
attribute/method positions were already handled and are unaffected.

Fixes mermaid-js#7648
Fixes mermaid-js#7480

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@netlify

netlify Bot commented Jul 10, 2026

Copy link
Copy Markdown

Deploy Preview for mermaid-js ready!

Name Link
🔨 Latest commit 3d4671d
🔍 Latest deploy log https://app.netlify.com/projects/mermaid-js/deploys/6a508aaa64007e0008233475
😎 Deploy Preview https://deploy-preview-7957--mermaid-js.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@changeset-bot

changeset-bot Bot commented Jul 10, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 3d4671d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
mermaid Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added the Type: Bug / Error Something isn't working or is incorrect label Jul 10, 2026
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Class diagram generic parsing now tracks nested tilde delimiters, and class type formatting recursively renders nested generics as escaped angle brackets. New tests cover nested, deeply nested, backticked, and fixture-based class definitions.

Changes

Nested generic class diagrams

Layer / File(s) Summary
Depth-aware generic lexing
packages/mermaid/src/diagrams/class/parser/classDiagram.jison
The generic lexer state accumulates text, tracks nesting depth, and emits GENERICTYPE after balanced delimiters.
Generic rendering and validation
packages/mermaid/src/diagrams/class/classDb.ts, packages/mermaid/src/diagrams/class/classDiagram.spec.ts, .changeset/class-nested-generics.md
Class type parsing recursively formats nested generics with escaped angle brackets; tests cover nested variants and the changeset records the fix.

Estimated code review effort: 2 (Simple) | ~15 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: nested generics support in class names.
Description check ✅ Passed The description covers the summary, implementation details, examples, and tests, but it does not follow the template headings exactly.
Linked Issues check ✅ Passed The changes address the nested-generic parsing bug and add regression tests for the reported classDiagram cases in #7648 and #7480.
Out of Scope Changes check ✅ Passed The PR stays focused on nested generic parsing, formatting, tests, and a changeset, with no clear unrelated code changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Jul 10, 2026

Copy link
Copy Markdown

Open in StackBlitz

@mermaid-js/examples

npm i https://pkg.pr.new/@mermaid-js/examples@7957

mermaid

npm i https://pkg.pr.new/mermaid@7957

@mermaid-js/layout-elk

npm i https://pkg.pr.new/@mermaid-js/layout-elk@7957

@mermaid-js/layout-tidy-tree

npm i https://pkg.pr.new/@mermaid-js/layout-tidy-tree@7957

@mermaid-js/mermaid-zenuml

npm i https://pkg.pr.new/@mermaid-js/mermaid-zenuml@7957

@mermaid-js/parser

npm i https://pkg.pr.new/@mermaid-js/parser@7957

@mermaid-js/tiny

npm i https://pkg.pr.new/@mermaid-js/tiny@7957

commit: 3d4671d

@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.00000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.78%. Comparing base (f3dea58) to head (3d4671d).

Files with missing lines Patch % Lines
...rmaid/src/diagrams/class/parser/classDiagram.jison 33.33% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop    #7957      +/-   ##
===========================================
- Coverage    77.51%   74.78%   -2.74%     
===========================================
  Files          564      543      -21     
  Lines        74906    67400    -7506     
  Branches     14617    12539    -2078     
===========================================
- Hits         58065    50405    -7660     
- Misses       15842    16947    +1105     
+ Partials       999       48     -951     
Flag Coverage Δ
e2e 40.72% <73.33%> (-29.91%) ⬇️
unit 74.83% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
packages/mermaid/src/diagrams/class/classDb.ts 76.94% <100.00%> (-2.45%) ⬇️
...rmaid/src/diagrams/class/parser/classDiagram.jison 37.85% <33.33%> (-1.64%) ⬇️

... and 182 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/mermaid/src/diagrams/class/classDb.ts`:
- Around line 103-110: Update ClassDB.formatGenericType to parse generic
delimiters with a depth-aware scan, matching each opening tilde with its
corresponding closing tilde instead of using the first and last tilde; preserve
sibling generic arguments and recursively format each nested type. Add a
regression test covering Map<List<Person>,Set<Animal>>.

In `@packages/mermaid/src/diagrams/class/classDiagram.spec.ts`:
- Around line 1474-1483: Update the test case in `classDiagram.spec.ts` to
assert that parsing the nested-generics sample preserves `Person`’s inline
member generic, checking the stored or rendered member text for
`List~List~string~~` rather than only asserting that parsing does not throw.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e855beac-12d9-47fc-b269-dc96c922eecd

📥 Commits

Reviewing files that changed from the base of the PR and between f3dea58 and a7da36e.

📒 Files selected for processing (4)
  • .changeset/class-nested-generics.md
  • packages/mermaid/src/diagrams/class/classDb.ts
  • packages/mermaid/src/diagrams/class/classDiagram.spec.ts
  • packages/mermaid/src/diagrams/class/parser/classDiagram.jison

Comment on lines +103 to +110
private formatGenericType(type: string): string {
const firstTilde = type.indexOf('~');
if (firstTilde === -1) {
return type;
}
const name = type.substring(0, firstTilde);
const inner = type.substring(firstTilde + 1, type.lastIndexOf('~'));
return `${name}&lt;${this.formatGenericType(inner)}&gt;`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Match nested delimiters by depth, not only first and last tilde.

formatGenericType mis-associates sibling nested generics. For example, class Container~Map~List~Person~,Set~Animal~~~ is rendered incorrectly because the recursive call consumes the first tilde and the final tilde, treating ,Set~Animal as part of Person.

Use a depth-aware scan matching the lexer’s delimiter rules, and add a regression test for Map<List<Person>,Set<Animal>>.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/mermaid/src/diagrams/class/classDb.ts` around lines 103 - 110,
Update ClassDB.formatGenericType to parse generic delimiters with a depth-aware
scan, matching each opening tilde with its corresponding closing tilde instead
of using the first and last tilde; preserve sibling generic arguments and
recursively format each nested type. Add a regression test covering
Map<List<Person>,Set<Animal>>.

Comment on lines +1474 to +1483
it('should handle the full nested-generics sample from #7648', function () {
const str =
'classDiagram\n' +
'class Person {\n' +
' ~AnotherInternalProperty : List~List~string~~\n' +
'}\n' +
'class People List~List~Person~~';

expect(() => parser.parse(str)).not.toThrow();
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Assert that the inline member generic is preserved.

The fixture contains List~List~string~~, but not.toThrow() does not verify the member’s stored or rendered text. Assert that Person retains the expected member so regressions in member-level generic handling are detected.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/mermaid/src/diagrams/class/classDiagram.spec.ts` around lines 1474 -
1483, Update the test case in `classDiagram.spec.ts` to assert that parsing the
nested-generics sample preserves `Person`’s inline member generic, checking the
stored or rendered member text for `List~List~string~~` rather than only
asserting that parsing does not throw.

@argos-ci

argos-ci Bot commented Jul 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Argos notifications ↗︎

Build Status Details Updated (UTC)
default (Inspect) 📭 Waiting for screenshots - Jul 10, 2026, 6:13 AM

…rics

Address review feedback on mermaid-js#7957. `formatGenericType` previously used the
first and last tilde, which mis-associated sibling nested generics such as
`Map~List~K~,List~V~~` (rendered `List<K<,List>>` instead of
`List<K>,List<V>`). It now walks the string and, like the lexer, treats a
`~` as an opening bracket when followed by a word character and a closing
bracket otherwise. Added a sibling-generics regression test and an
assertion that inline member generics are preserved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Type: Bug / Error Something isn't working or is incorrect

Projects

None yet

1 participant