Skip to content

fixed cross-diagram linking and extend to class/sequence diagrams#242

Closed
AAmbuj wants to merge 5 commits into
eclipse-score:mainfrom
AAmbuj:amsh_plantuml_linker_cross_diagram_linking
Closed

fixed cross-diagram linking and extend to class/sequence diagrams#242
AAmbuj wants to merge 5 commits into
eclipse-score:mainfrom
AAmbuj:amsh_plantuml_linker_cross_diagram_linking

Conversation

@AAmbuj

@AAmbuj AAmbuj commented May 22, 2026

Copy link
Copy Markdown
Contributor
  • Fix class_serializer to prepend actual source filename to source_files vector, enabling the Sphinx extension to match links by .puml filename
  • Extend linker to parse class (CLSD) and sequence (SEQD) FlatBuffers, extracting entities and participants for cross-diagram linking
  • Register diagram names as virtual top-level aliases for title-based linking
  • Extend clickable_plantuml regex to accept hyphens/dots in element identifiers
  • Add linker README with architecture docs and usage instructions

@AAmbuj AAmbuj force-pushed the amsh_plantuml_linker_cross_diagram_linking branch 4 times, most recently from 9d8717a to 563c263 Compare May 29, 2026 05:44
@hoe-jo hoe-jo force-pushed the amsh_plantuml_linker_cross_diagram_linking branch from 563c263 to 87e45ac Compare June 8, 2026 09:23
AAmbuj added 5 commits June 11, 2026 09:38
- Add 'collections' to component_kind in PEG grammar
- Add Collections variant to ElementType enum and serializer mapping
- Add file_identifier "COMP" to component.fbs schema
- Write file identifier in component_serializer for type detection
Prepend the actual source .puml filename to the source_files vector
in ClassSerializer::serialize(), enabling the linker and Sphinx
extension to correlate class diagrams with their source file.
- Add class_fbs and sequence_fbs dependencies to linker BUILD
- Grant linker visibility to class_fbs and sequence_fbs libraries
- Detect diagram type via 4-byte file identifier (COMP/CLSD/SEQD)
- Add parse_class_diagram: extract entities, FQN IDs, relationships
- Add parse_sequence_diagram: extract unique participants recursively
- Register diagram names as virtual top-level aliases for title-based linking
- Register component FQN (id) as additional index keys
- Extract relation targets as linkable elements
- Deduplicate relation targets with HashSet
- Add _ALIAS_EXTENDED_RE to accept hyphens/dots in identifiers
  (e.g. pkg.Class, my-component)
- Add _ALIAS_QUOTED_RE for names with spaces (wrapped in quotes)
- Refactor _format_alias_part for safe alias formatting
- Strip leading/trailing whitespace before quoting aliases
- Add {_top} window target for correct navigation in embedded SVGs
@AAmbuj AAmbuj force-pushed the amsh_plantuml_linker_cross_diagram_linking branch from 87e45ac to d05ee7a Compare June 11, 2026 04:08
@hoe-jo

hoe-jo commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Obsolete due to this PR: #290

@AAmbuj

AAmbuj commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

PlantUML Diagram Linking — Review & Refactor Plan (score_tooling)

Context

S-CORE PlantUML diagrams should be clickable: a component referenced in an overview should link to the diagram that details it, a sequence participant to its component, etc. All of that logic lives in the score_tooling module (local checkout /home/jochen/git/tooling), consumed by communication over Bazel. This document reviews the full chain end-to-end and proposes a refactor.

The chain as built

.puml ─parser(Rust)─▶ .fbs.bin ─linker(Rust)─▶ plantuml_links.json ─clickable_plantuml.py─▶ url-of injected ─sphinxcontrib.plantuml─▶ SVG
Stage Where What it does
Parse / metadata plantuml/parser/ (pest PEG, puml_cli) .puml → .fbs.bin. Extracts alias, name, FQN id, parent_id, relations, stereotype. Also .lobster.
FTA metadata plantuml/fta_metamodel.puml $BasicEvent($name,$alias,$conn)usecase "$name" as $alias; $alias is the dotted failure-mode id (Communication.ServiceNotFound). Parses as a component diagram.
Wiring architectural_design.bzl Runs parser per .puml, then linker on all .fbs.binplantuml_links.json; ships it + RST wrappers via SphinxSourcesInfo.
Link match linker/src/main.rs Indexes top-level aliases/FQN/diagram-name, matches every alias occurrence in A against top-level entries in B → {source_file, source_id, target_file}; dedups to one target per (file,alias).
Inject clickable_plantuml.py builder-inited load JSON; doctree-read map puml→docname(+anchor); doctree-resolved inject url of <alias> is [[url{}{_top}]] before @enduml.
Render sphinxcontrib.plantuml (svg_obj) Renders annotated UML to SVG with <a href> regions.

Review findings

Id Severity Bug Fix folded into
F1 🔴 Fuzzy alias string-match → false links at scale (main.rs:441) Phase 2 (definition role + proximity)
F2 🟠 "One target per alias, first alphabetically" arbitrary (main.rs:507) Phase 2 (proximity tiebreak)
F3 🟠 Basename-only identity → "last wins" collisions (clickable_plantuml.py:210, root at cli:181) Phase 1 (carry rel-path) + Phase 2 (match in-ext)
F4 🟠 Hardcoded ../ URL assumes _images/ one level down; README says get_relative_uri, code uses get_target_uri+../ (:267) Phase 2 (builder-aware URL + README sync)
F5 🟡 Dead branch: elif startswith(prefixes) and else are identical (:279-282) Phase 2 (collapse)
F6 🟡 Rich structure (children/elaboration) unused for role detection Core of new model
F7 🟡 Version skew (local da77fa3 vs pinned 7ae1155); linker label aliases parser:linker while source is plantuml/linker Phase 3 (verify)
F8 🟢 URL not escaped beyond ]] guard (:138) Phase 2 (escape)
F0 clickable_plantuml not registered in communication conf Out of scope (communication, separate task)

Refactor plan

New UID & role model — no sphinx-needs, no new tags

Everything below is derived from structure the author already writes.

Roles from elaboration, not from top-level:

  • An element is a definition in a diagram if it is elaborated there — it has children (child_elements_by_parent[id] non-empty), or it is a class entity with members, or the diagram's @startuml <name> is that element, or (FTA) it is the tree's top event (a relation sink that is never a relation source).
  • An element is a reference if it appears as a leaf mention (no children) and/or as a relation endpoint.

This fixes F6 and inverts the broken "top-level = definition" rule: an overview's top-level leaf boxes are now correctly sources, not targets.

Match key from existing identifiers (no new tags):

  • Build a definition index keyed by both the local name/alias and the full FQN id, mapping → the set of diagrams that elaborate that element. This set is naturally sparse (only elaborated elements), which is what makes plain-name matching safe here.
  • For each reference of A in D, look up A in the definition index by FQN first, then by local name/alias. Emit D → D′ for the unique definer D′ ≠ D.
  • Ambiguity (>1 definer) → proximity tiebreak, not alphabetical (fixes F1/F2): pick the definer sharing the longest common workspace-relative path prefix (and/or longest common FQN scope) with D. If still tied → log a warning and emit no link (safe over wrong).

FTA: dotted aliases (Communication.X) are already globally unique and a $TransferInGate references another tree's top-event alias — these match cleanly under the same rules with the top-event-as-definition signal.

Worked example (tag-free roles)

' overview.puml  (Proxy = leaf ⇒ SOURCE)
@startuml
[Gateway] --> [Proxy]
@enduml

' proxy_detail.puml (Proxy has child ⇒ DEFINITION)
@startuml
package Proxy { [RequestHandler] }
@enduml

Proxy references → elaborated in proxy_detail ⇒ link overview→proxy_detail. Gateway (leaf, elaborated nowhere) and RequestHandler (elaborated nowhere) ⇒ no link. The earlier false-split (proxy vs mw_com.proxy) now links because the reference's local name Proxy hits the definition index keyed by local name.

Phase 1 — Parser: stable source identity + ID manifest (Rust)

  1. Fix F3 at the root: add --source-name <relpath> to puml_cli; the Bazel rule sets it from puml_file.short_path (workspace-relative, stable). Store that instead of path.file_name() so diagram identity is path-unique, not basename.
  2. Emit <stem>.idmap.json via an emitter mirroring puml_lobster — a projection of the already-resolved model, no new parsing:
{ "source": "score/mw/com/.../proxy_detail.puml",
  "defines":    [ { "alias": "Proxy", "id": "mw_com.Proxy", "elaborated": true } ],
  "references": [ { "alias": "Gateway", "id": "Gateway" } ] }

defines = elements with children / class-with-members / diagram-name / FTA top-event; references = leaf mentions + relation endpoints + sequence participants. alias kept for injection (PlantUML url of needs it); id for matching.

  1. Wire --idmap-output-dir alongside the existing --lobster-output-dir.

Phase 2 — Matching moves into the Sphinx extension (clickable_plantuml.py)

  • builder-inited: load all *.idmap.json; build the definition index (name/alias/FQN → {definer source paths}).
  • doctree-read (unchanged): record puml→docname(+section anchor). Because identity is now a path, the F3 basename-collision warning becomes a non-issue.
  • doctree-resolved: for each reference (alias,id) in a diagram, resolve the unique definer (proximity tiebreak), map definer→docname→URL, inject url of <alias> is [[url{}{_top}]].
  • Fix F4: derive the URL via the builder (or assert+document the _images/ invariant); sync the README, which currently claims get_relative_uri().
  • Fix F5: collapse the identical elif/else URL branch.
  • Fix F8: escape PlantUML-significant chars in the injected URL, not just guard ]].

Perf: index built once (O(total elements)); per-reference resolution O(1) + a short proximity scan only on the rare multi-definer case. Rendering still dominates.

Phase 3 — Decommission the linker

Delete plantuml/linker/ + its BUILD; drop the linker action and plantuml_links.json from architectural_design.bzl (_architectural_design_impl, ~L128-151). Ship .idmap.json sidecars via SphinxSourcesInfo instead. The fbs schema crates stay (other consumers).

F7: while here, verify/clean the @score_tooling//plantuml/parser:linker label indirection vs the plantuml/linker source location.

Verification

  • Phase 1: bazel run //plantuml/parser -- --file <x.puml> --source-name a/b/x.puml --idmap-output-dir /tmp → inspect /tmp/x.idmap.json: correct defines/references, elaborated flags, FQN ids, and the relative source.
  • Role correctness (unit): overview-with-top-level-leaves fixture → those elements are references, not defines; the elaborated detail diagram is the sole define.
  • Ambiguity (unit): same id elaborated in two diagrams at different paths → link resolves to the nearest by path prefix; equal-distance tie → no link + one warning (guards F1/F2).
  • False-split (unit): shallow [Proxy] as proxy reference resolves to the deep package "mw::com" { component Proxy } definition via local-name index.
  • End-to-end: build docs on a small fixture set; grep generated SVG for <a href tags; click-test overview↔detail and an FTA $TransferInGate↔$TopEvent pair.
  • No-collision: a bare generic Proxy elaborated nowhere produces zero links.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

2 participants