From 62c797c1c76374885bad8ff66b925d103c0be9a9 Mon Sep 17 00:00:00 2001 From: Malcolm Nixon Date: Mon, 6 Jul 2026 09:40:39 -0400 Subject: [PATCH 1/6] Implement view-scoped rendering (render/expose/filter body statements) Fixes the bug where every declared SysML view rendered the identical full-workspace GeneralView diagram, ignoring any render ;, expose <...>;, or filter []; statements in the view body. - SysmlViewNode gains RenderTargetName, ExposedNames, FilterExpressionText. - AstBuilder now visits view def bodies for render/filter members, and adds a new VisitViewUsage handling expose for view usages (shared import-name extraction helper reused from VisitImportRule). - New SysmlEdgeKind.Render/.Expose edge kinds; ReferenceResolver resolves render targets and exposed names into edges, emitting an unresolved-reference diagnostic when a render target does not resolve (this is the fix for the reported bug: a bogus render target now surfaces a visible diagnostic instead of silently rendering everything). - ViewContext carries the SysmlViewNode; GeneralViewLayoutStrategy scopes rendering to the render target's containment subtree (Q/Q:: prefix match), unions in exposed subtrees, falls back to full-workspace rendering when the render target is unresolved (diagnostic already surfaced upstream), and emits a LayoutWarnings warning when a filter expression is present (parsed but not yet evaluated). Views with no render statement (including --auto-synthesized views) render byte-identical to before. - Other layout strategies (Interconnection, StateTransition, ActionFlow, Sequence, Grid, Browser) are intentionally left unchanged in this pass; ROADMAP.md tracks extending render-target preference to them, plus full filter expression evaluation, as follow-up items. - Adds/extends tests in WorkspaceLoaderTests, GeneralViewLayoutStrategyTests, LayoutWarningsTests, RenderingTests, and an end-to-end RenderSubsystemTests case reproducing the reported bug. - Updates reqstream requirements, design docs, verification docs, README.md, docs/user_guide/introduction.md, and ROADMAP.md accordingly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 4 + ROADMAP.md | 47 ++++ .../internal/general-view-layout-strategy.md | 45 +++- .../layout/internal/layout-warnings.md | 16 +- docs/design/sysml2-tools-core/rendering.md | 17 +- .../rendering/diagram-renderer.md | 8 +- .../semantic/model/ast-builder.md | 36 ++++ .../semantic/model/reference-resolver.md | 14 +- .../semantic/model/sysml-edge.md | 17 +- .../semantic/model/sysml-node.md | 35 ++- .../general-view-layout-strategy.yaml | 50 +++++ .../layout/internal/layout-warnings.yaml | 14 ++ .../sysml2-tools-core/rendering.yaml | 16 ++ .../semantic/model/ast-builder.yaml | 31 +++ .../semantic/model/reference-resolver.yaml | 20 ++ .../semantic/model/sysml-node.yaml | 18 ++ docs/user_guide/introduction.md | 27 +++ .../internal/general-view-layout-strategy.md | 25 +++ .../layout/internal/layout-warnings.md | 10 +- .../sysml2-tools-core/rendering.md | 12 ++ .../rendering/diagram-renderer.md | 6 + .../semantic/model/ast-builder.md | 9 + .../semantic/model/reference-resolver.md | 9 + .../semantic/model/sysml-edge.md | 6 + .../semantic/model/sysml-node.md | 7 + docs/verification/sysml2-tools-tool/render.md | 16 ++ .../Internal/GeneralViewLayoutStrategy.cs | 78 ++++++- .../Layout/Internal/LayoutWarnings.cs | 25 +++ .../Rendering/DiagramRenderer.cs | 2 +- .../Rendering/ILayoutStrategy.cs | 11 +- .../Semantic/Model/AstBuilder.cs | 201 ++++++++++++++++-- .../Semantic/Model/ReferenceResolver.cs | 46 ++++ .../Semantic/Model/SysmlEdge.cs | 19 ++ .../Semantic/Model/SysmlNode.cs | 32 ++- .../Resources/stdlib.json.gz | Bin 463162 -> 463248 bytes .../Layout/GeneralViewLayoutStrategyTests.cs | 178 ++++++++++++++++ .../Layout/LayoutWarningsTests.cs | 22 ++ .../Rendering/RenderingTests.cs | 21 ++ .../Semantic/WorkspaceLoaderTests.cs | 197 +++++++++++++++++ .../Render/RenderSubsystemTests.cs | 73 +++++++ 40 files changed, 1365 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 04513486..aa315fed 100644 --- a/README.md +++ b/README.md @@ -225,6 +225,10 @@ section above). | Multiple views, `--view ` | Render only the named view | | `--view ` names a view that does not exist | Error: lists available view names and exits non-zero | +A view's body `render ;` and `expose <...>;` statements now scope the rendered diagram +to that target's containment subtree (plus any exposed names), instead of always rendering the +full workspace — see the *Introduction* user guide for details. + ## NuGet Packages | Package | Description | diff --git a/ROADMAP.md b/ROADMAP.md index c1155f2d..94ca0d33 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -69,6 +69,53 @@ primitives (bar, diamond, pentagon, note). `LayoutActivation`/`LayoutBand` alrea **Visual gate:** sequence shows activation bars + a fragment; action flow shows a fork/join and a decision/merge with correct shapes. +### View `filter [];` expression evaluation + +`GeneralViewLayoutStrategy` now scopes a rendered diagram to a view's `render ;`/ +`expose <...>;` subject subtree, but a view's `filter [];` body statement is only parsed +into `SysmlViewNode.FilterExpressionText` (raw source text) — it is never evaluated, and a +layout warning ("parsed but not yet evaluated") is emitted in its place. SysML v2 view filtering +is part of the standard's view/viewpoint mechanism for selectively including/excluding elements +from a rendered diagram by predicate, beyond simple subject-subtree containment scoping (for +example, "only elements satisfying a given requirement" or "only elements with a given +stereotype"); without evaluation, a modeler's `filter` statement is silently ineffective beyond +the warning. + +- Design and implement an expression evaluator for the bracketed filter expression grammar + (boolean/membership predicates over the resolved scope's elements), reusing/aligning with + existing expression-parsing infrastructure where practical. +- Apply the evaluated predicate as an additional filter over the resolved (render/expose) + scope in `GeneralViewLayoutStrategy`, removing the "not yet evaluated" warning once a filter + expression is present and successfully evaluated. +- Surface a diagnostic for filter expressions that fail to parse or evaluate, mirroring the + unresolved-render-target diagnostic pattern. + +**Scope:** `AstBuilder`/`SysmlViewNode` (expression AST capture, if warranted, beyond raw text); +new expression-evaluation component; `GeneralViewLayoutStrategy` filter application. +**Visual gate:** a view with a `filter [];` statement renders only the elements +satisfying the predicate, with no "not yet evaluated" warning. + +### Render-target scoping for the remaining layout strategies + +`GeneralViewLayoutStrategy` implements render-target subject-scoping (containment-subtree +filtering driven by a view's `render`/`expose` body statements), but `InterconnectionView`, +`StateTransitionView`, `ActionFlowView`, `SequenceView`, `GridView`, and `BrowserView` layout +strategies do not yet honor `ViewContext.ViewNode`'s `Render`/`Expose` edges and continue to +render their full applicable scope regardless of a view's declared render target. + +- Extend the same `ResolveSubjectScope`/`IsInSubjectScope` containment-subtree idiom (or a + shared helper extracted from `GeneralViewLayoutStrategy`) to each of the six remaining layout + strategies, respecting the "no `Render` edge → render everything unchanged" fallback used by + `GeneralViewLayoutStrategy`. +- Add regression tests per strategy mirroring `GeneralViewLayoutStrategyTests`'s scoping, + expose-union, unresolved-fallback, and no-render-statement-regression scenarios. + +**Scope:** `InterconnectionViewLayoutStrategy`, `StateTransitionViewLayoutStrategy`, +`ActionFlowViewLayoutStrategy`, `SequenceViewLayoutStrategy`, `GridViewLayoutStrategy`, +`BrowserViewLayoutStrategy`; corresponding test files. +**Visual gate:** each of the six views renders a scoped diagram when its view declares a +`render ;` statement naming a resolvable target, unchanged when it does not. + --- ## Release & packaging diff --git a/docs/design/sysml2-tools-core/layout/internal/general-view-layout-strategy.md b/docs/design/sysml2-tools-core/layout/internal/general-view-layout-strategy.md index 925e3973..fefd3938 100644 --- a/docs/design/sysml2-tools-core/layout/internal/general-view-layout-strategy.md +++ b/docs/design/sysml2-tools-core/layout/internal/general-view-layout-strategy.md @@ -34,24 +34,47 @@ from the structural relationships. ###### `BuildLayout(ViewContext context, RenderOptions options)` -Entry point. Calls `CollectDefinitions` to gather user definitions; returns a minimal 200×100 empty -`LayoutTree` when none are found. Otherwise groups the definitions by package with `GroupByPackage`, -resolves the specialization/membership/attribute-typing relationships into qualified-name edges with +Entry point. First resolves the view's render-target subject scope via `ResolveSubjectScope`, +then calls `CollectDefinitions` to gather user definitions restricted to that scope (or every +definition when no scope applies); returns a minimal 200×100 empty `LayoutTree` when none are +found. Otherwise groups the definitions by package with `GroupByPackage`, resolves the +specialization/membership/attribute-typing relationships into qualified-name edges with `BuildModelEdges`, builds the single input `LayoutGraph` with `BuildGraph`, and places the whole graph with one `HierarchicalLayoutAlgorithm().Apply(graph, LayoutOptions.ForAlgorithm("containment"))` call — passing the desired root-scope leaf algorithm through the options parameter (not `graph.Set(CoreOptions.Algorithm, …)`) so a caller going through `LayoutEngine.Layout(graph)` later is never misled into skipping the hierarchical engine. When any package folder was depth-truncated, `DecorateTruncatedFolders` stamps each truncated folder's "+N more…" ellipsis label onto its placed -box before returning. +box. Finally, when `context.ViewNode?.FilterExpressionText` is non-null, attaches the +"parsed but not yet evaluated" warning (from `LayoutWarnings.ForUnevaluatedFilter`) to the returned +tree's `Warnings` via the `LayoutTree with { Warnings = … }` record-copy idiom, leaving the +resolved (unfiltered) scope's content unchanged. -###### `CollectDefinitions(workspace, theme)` +###### `ResolveSubjectScope(SysmlViewNode? viewNode)` + +Resolves the render-target subject scope a view's `render`/`expose` statements restrict the +diagram to. Returns `null` — meaning "render everything", byte-identical to the pre-scoping +behavior — in three equivalent cases: a `null` `viewNode` (the `--auto` synthesized view, which +never carries render/expose/filter data), a view with no `render` statement, and a view whose +`render` target failed to resolve (`ReferenceResolver` never adds a `Render` edge for an +unresolvable target; the diagnostic informing the user is emitted there, not here). When a +`Render` edge is present, returns a list starting with its resolved target qualified name, +followed by the resolved target qualified name of every `Expose` edge on the same view — i.e. the +render subject plus each exposed name, additively. + +###### `IsInSubjectScope(qualifiedName, subjects)` + +Returns `true` when `qualifiedName` equals one of `subjects` or lies within one of their +containment subtrees (a `"{subject}::"` prefix match) — the same qualified-name-prefix idiom +`StdlibFilter.IsStdlibElement` already uses for stdlib-prefix matching. + +###### `CollectDefinitions(workspace, theme, scope)` Iterates `workspace.Declarations`, keeping each `SysmlDefinitionNode` that is not a -standard-library element (per `StdlibFilter.IsStdlibElement`). For each kept definition it builds -the compartments from the owned usage features (grouped by keyword, each formatted as a -`name : Type [n]` row), collects the typed memberships, and computes the box size from the title and -the longest compartment row. +standard-library element (per `StdlibFilter.IsStdlibElement`) and, when `scope` is non-null, is +within `scope` per `IsInSubjectScope`. For each kept definition it builds the compartments from +the owned usage features (grouped by keyword, each formatted as a `name : Type [n]` row), collects +the typed memberships, and computes the box size from the title and the longest compartment row. ###### `GroupByPackage(defs)` @@ -128,6 +151,10 @@ produces valid geometry, so no crossing warnings are emitted. - `BoxMetrics` (`DemaConsulting.Rendering.Abstractions`) — box title-area and folder-tab geometry. - `StdlibFilter` (Rendering Internal subsystem) — standard-library exclusion. - `SysmlWorkspace`, `SysmlDefinitionNode`, `SysmlFeatureNode` (Semantic subsystem) — model input. +- `SysmlViewNode`, `SysmlEdge`, `SysmlEdgeKind` (Semantic subsystem) — a view's resolved + render/expose data, read by `ResolveSubjectScope`. +- `LayoutWarnings` (Layout Internal subsystem) — `ForUnevaluatedFilter` supplies the + "parsed but not yet evaluated" filter-expression warning text. - The `LayoutTree`, `LayoutBox`, `LayoutCompartment`, `LayoutLine`, `LayoutLabel`, and `Point2D` data types (`DemaConsulting.Rendering`). - `FeatureMembership` (private record) — carries the keyword and type reference of one owned feature. diff --git a/docs/design/sysml2-tools-core/layout/internal/layout-warnings.md b/docs/design/sysml2-tools-core/layout/internal/layout-warnings.md index a1c04737..6f46ca28 100644 --- a/docs/design/sysml2-tools-core/layout/internal/layout-warnings.md +++ b/docs/design/sysml2-tools-core/layout/internal/layout-warnings.md @@ -23,6 +23,18 @@ Returns the warnings for a view: is rendered in singular form for a count of one and plural form otherwise, and the count is formatted with the invariant culture. +###### `ForUnevaluatedFilter(viewName, filterExpressionText)` + +Returns the warnings for a view's declared filter expression: + +1. When `filterExpressionText` is `null` (the view has no `filter [];` member), an empty + list is returned. +2. Otherwise a single warning string is produced naming the view: `"View '{viewName}' declares a + filter expression, which is parsed but not yet evaluated; all elements in the resolved scope + are rendered unfiltered."` The raw expression text itself is not interpolated into the message + (only its presence matters) — full filter expression evaluation is deferred future work (see + ROADMAP.md). + ##### Error Handling N/A - the method performs no validation and does not throw; a non-positive count simply yields an @@ -36,4 +48,6 @@ empty list and any string view name is accepted. ##### Callers View layout strategies that route connectors call `LayoutWarnings.ForCrossings` to attach -crossing warnings to the `LayoutTree` they produce. +crossing warnings to the `LayoutTree` they produce. `GeneralViewLayoutStrategy` calls +`LayoutWarnings.ForUnevaluatedFilter` to attach the "not yet evaluated" filter warning when a +view's `FilterExpressionText` is non-null. diff --git a/docs/design/sysml2-tools-core/rendering.md b/docs/design/sysml2-tools-core/rendering.md index 81a07471..e498a2d8 100644 --- a/docs/design/sysml2-tools-core/rendering.md +++ b/docs/design/sysml2-tools-core/rendering.md @@ -53,7 +53,11 @@ flowchart TD - *Type*: Sealed record. - *Role*: Data transfer object. -- *Contract*: `string ViewName`, `SysmlWorkspace Workspace`. +- *Contract*: `string ViewName`, `SysmlWorkspace Workspace`, `SysmlViewNode? ViewNode = null`. + `ViewNode` is the view's resolved AST node, giving a layout strategy access to the view's + declared `render`/`expose`/`filter` body statements (`RenderTargetName`, `ExposedNames`, + `FilterExpressionText`, and their `ResolvedEdges`); it is `null` for the `--auto` synthesized + view, which carries no AST node of its own. **Theme**: Visual configuration record. @@ -127,9 +131,13 @@ flowchart TD that tree and the options to `IRenderer.Render`. Each rendered stream is wrapped in a `RenderOutput` and collected into the return list. -2. `ILayoutStrategy.BuildLayout` receives a `ViewContext` containing the workspace and the view - name, plus `RenderOptions` for size and scale hints. It produces a fully resolved - `LayoutTree` with all waypoints in absolute canvas coordinates. +2. `ILayoutStrategy.BuildLayout` receives a `ViewContext` containing the workspace, the view + name, and (when available) the view's resolved AST node, plus `RenderOptions` for size and + scale hints. It produces a fully resolved `LayoutTree` with all waypoints in absolute canvas + coordinates. `GeneralViewLayoutStrategy` is the only strategy that currently reads + `ViewContext.ViewNode` to scope its diagram to a declared render target's subtree; every + other strategy ignores it and renders as before (see the Layout subsystem's + `general-view-layout-strategy` design doc for the scoping algorithm). 3. `IRenderer.Render` receives the `LayoutTree` and `RenderOptions` and writes all rendered bytes to the supplied `Stream`. It must not perform any layout computation; it only reads @@ -160,6 +168,7 @@ flowchart TD | SysML2Tools-Core-Rendering-ThemeDepthWrap | `Theme.DepthFillColors` with modulo indexing documented in `Theme` | | SysML2Tools-Core-Rendering-RenderOptions | `RenderOptions` record with default values | | SysML2Tools-Core-Rendering-ILayoutStrategy | `ILayoutStrategy` interface and `ViewContext` record | +| SysML2Tools-Core-Rendering-ViewContextViewNode | `ViewContext.ViewNode` flows to `DiagramRenderer.RenderWorkspace` | | SysML2Tools-Core-Rendering-DiagramRenderer | `DiagramRenderer.RenderWorkspace`; `DiagramTypeRouter`; `StdlibFilter` | | SysML2Tools-Core-Rendering-RenderOutput | `RenderOutput` record | | SysML2Tools-Core-Rendering-BuiltinThemes | `Themes.Light`, `Themes.Dark`, `Themes.Print` | diff --git a/docs/design/sysml2-tools-core/rendering/diagram-renderer.md b/docs/design/sysml2-tools-core/rendering/diagram-renderer.md index a621bc01..514a6c4c 100644 --- a/docs/design/sysml2-tools-core/rendering/diagram-renderer.md +++ b/docs/design/sysml2-tools-core/rendering/diagram-renderer.md @@ -20,9 +20,11 @@ off-the-shelf `RenderOptions` and `RenderOutput` records from `DemaConsulting.Re For each declaration in the workspace it skips non-view nodes and standard-library views (via `StdlibFilter`), routes the view to an `ILayoutStrategy` (via `DiagramTypeRouter`), skips views with -no supporting strategy or that do not match `viewFilter`, builds the `LayoutTree`, renders it to an -in-memory stream, and collects a `RenderOutput` with a sanitized file name and the layout warnings. -Returns an empty list when the workspace declares no renderable views. +no supporting strategy or that do not match `viewFilter`, constructs a `ViewContext` carrying the +view's own `SysmlViewNode` (so a strategy such as `GeneralViewLayoutStrategy` can read its resolved +`render`/`expose`/`filter` data), builds the `LayoutTree`, renders it to an in-memory stream, and +collects a `RenderOutput` with a sanitized file name and the layout warnings. Returns an empty list +when the workspace declares no renderable views. ##### `GetViewNames(workspace)` diff --git a/docs/design/sysml2-tools-language/semantic/model/ast-builder.md b/docs/design/sysml2-tools-language/semantic/model/ast-builder.md index 7f2ef8f8..2454b869 100644 --- a/docs/design/sysml2-tools-language/semantic/model/ast-builder.md +++ b/docs/design/sysml2-tools-language/semantic/model/ast-builder.md @@ -22,6 +22,7 @@ stack with `::` to form the fully-qualified name. | `VisitAttributeDefinition` | `AttributeDefinitionContext` | `SysmlDefinitionNode` | | `VisitItemDefinition` | `ItemDefinitionContext` | `SysmlDefinitionNode` | | `VisitViewDefinition` | `ViewDefinitionContext` | `SysmlViewNode` | +| `VisitViewUsage` | `ViewUsageContext` | `SysmlViewNode` | | `VisitViewpointDefinition` | `ViewpointDefinitionContext` | `SysmlViewpointNode` | | `VisitAllocationUsage` | `AllocationUsageContext` | `SysmlConnectionNode` (`ConnectionKeyword = "allocation"`) | | `VisitSatisfyRequirementUsage` | `SatisfyRequirementUsageContext` | `SysmlSatisfyNode` | @@ -88,6 +89,41 @@ satisfied requirement's raw reference text is taken from `ownedReferenceSubsetti text comes from `satisfactionSubjectMember()` (the `by ` clause), or is `null` when absent. +`VisitViewDefinition` builds a `SysmlViewNode` for `view def` definitions, additionally scanning +`context.viewDefinitionBody()?.viewDefinitionBodyItem()` via the shared +`ExtractViewRenderAndFilter` helper (see below) to populate `RenderTargetName` and +`FilterExpressionText`. `VisitViewUsage` builds a `SysmlViewNode` for named `view` usages (the +only body form that may additionally contain `expose` members) the same way, plus +`ExtractExposedNames` to populate `ExposedNames`. Unnamed view usages are skipped (no declared +name), mirroring the existing anonymous-element convention. + +`ExtractViewRenderAndFilter(IEnumerable bodyItems)` is a single generic helper +shared by both `VisitViewDefinition` (`ViewDefinitionBodyItemContext`) and `VisitViewUsage` +(`ViewBodyItemContext`) — the two context types are unrelated in the generated parser's type +hierarchy but expose identically-shaped `viewRenderingMember()`/`elementFilterMember()` +accessors, so a type-switch pattern inside two small private helpers +(`GetViewRenderingMember`/`GetElementFilterMember`) lets one generic method serve both body-item +types without duplicating the scan loop. The first `render` member wins if more than one +appears (a defensive tie-break, not a validated SysML constraint). `ExtractRenderTargetName` +follows the same two-form fallback pattern `VisitSatisfyRequirementUsage` uses: the direct +reference form (`ownedReferenceSubsetting()`), falling back to the typed-placeholder form's +feature typing (`ExtractFeatureTyping`), falling back to the raw usage text. The filter +expression's raw source text is taken verbatim from +`elementFilterMember().ownedExpression()?.GetText()` — never evaluated. + +`ExtractExposedNames(IEnumerable bodyItems)` collects the raw reference +text of every `expose ;` member in source order, reusing the shared `ExtractImportTarget` +helper (see below) against each `expose` member's wrapped `namespaceImport()`/ +`membershipImport()` — the identical grammar shape `import` uses. + +`ExtractImportTarget(NamespaceImportContext?, MembershipImportContext?)` is a shared helper +extracted from `VisitImportRule`'s previously inline logic, returning the extracted +qualified/dotted name text and whether the reference is a wildcard, for either the +namespace-import form (`qualifiedName::*`, always a wildcard) or the membership-import form +(`qualifiedName`, optionally `::**`). `VisitImportRule` and `ExtractExposedNames` both call this +one helper rather than duplicating the extraction logic, per the Copy-Paste Programming +anti-pattern guidance in coding-principles.md. + `VisitRequirementUsage` performs a minimal capture (name/qualified-name only, so named requirement usages become resolvable symbols) and additionally invokes `FindVerificationMembers` against its own `requirementBody()` (when present) to populate `VerifiedRequirementNames` — diff --git a/docs/design/sysml2-tools-language/semantic/model/reference-resolver.md b/docs/design/sysml2-tools-language/semantic/model/reference-resolver.md index 578b2fca..df363273 100644 --- a/docs/design/sysml2-tools-language/semantic/model/reference-resolver.md +++ b/docs/design/sysml2-tools-language/semantic/model/reference-resolver.md @@ -132,6 +132,17 @@ no edge): = resolved first end, `Target` = resolved second end) only when both ends resolve, using the identical both-sides-must-resolve contract as `Satisfy`. Regular `"connection"`/`"message"` keyword variants remain intentionally unresolved (out of scope for this unit). +- **`SysmlViewNode` (Render/Expose)** — resolves `RenderTargetName` and each `ExposedNames` + entry **independently of one another** (unlike `Satisfy`/`Allocate`'s two-sided contract, + since `render`/`expose` are functionally unrelated statements): a resolved `RenderTargetName` + produces one `SysmlEdgeKind.Render` edge sourced from `node.QualifiedName`, and each resolved + `ExposedNames` entry produces its own `SysmlEdgeKind.Expose` edge, independently. An + unresolvable `RenderTargetName` (e.g. a typo) produces only the standard unresolved-reference + Warning and no `Render` edge — `GeneralViewLayoutStrategy` observes the resulting absence of a + `Render` edge and falls back to rendering the full workspace, the same observable state as a + view with no `render` statement at all. This is the fix for the reported bug: a bogus render + target now surfaces a visible diagnostic instead of silently rendering everything. + `FilterExpressionText` is raw source text, not a reference, and is never inspected here. ##### Deviations From Uniform Resolution (Behavior-Neutral Additive Fixes) @@ -178,7 +189,8 @@ unresolved names are present. `VerifiedRequirementNames`; checks for `SysmlFeatureNode.FeatureTyping`, `SysmlSatisfyNode` (`SubjectName`/`RequirementName`), `SysmlConnectionNode` with `ConnectionKeyword == "allocation"` (`EndpointA`/`EndpointB`), `SysmlConnectionNode` with `ConnectionKeyword == - "connection"` or `"message"`, and `SysmlTransitionNode` (`Source`/`Target`); reads + "connection"` or `"message"`, `SysmlTransitionNode` (`Source`/`Target`), and `SysmlViewNode` + (`RenderTargetName`/`ExposedNames`; `FilterExpressionText` is never read); reads `ResolvedEdges` (`Typing`/`Supertype` kinds) during feature-chain resolution. - `SysmlEdge`, `SemanticIndex` — resolved references are recorded as `SysmlEdge` instances and aggregated into the returned `SemanticIndex`. diff --git a/docs/design/sysml2-tools-language/semantic/model/sysml-edge.md b/docs/design/sysml2-tools-language/semantic/model/sysml-edge.md index 7a8f2857..a9218595 100644 --- a/docs/design/sysml2-tools-language/semantic/model/sysml-edge.md +++ b/docs/design/sysml2-tools-language/semantic/model/sysml-edge.md @@ -4,12 +4,12 @@ `SysmlEdge` and `SysmlEdgeKind` model a single resolved directed reference between two qualified names in the semantic model. Edges are produced by `ReferenceResolver` while -walking supertype, feature-typing, import, satisfy, verify, allocate, connect, and transition -references, and are the raw material indexed by `SemanticIndex`. +walking supertype, feature-typing, import, satisfy, verify, allocate, connect, transition, +render, and expose references, and are the raw material indexed by `SemanticIndex`. ##### Types -`SysmlEdgeKind` is an enum with eight members: +`SysmlEdgeKind` is an enum with ten members: - `Supertype` — a specialization reference (`SupertypeNames` / `specializes` / `:>`). - `Typing` — a feature typing reference (`SysmlFeatureNode.FeatureTyping`, the type after `:`). @@ -33,6 +33,17 @@ references, and are the raw material indexed by `SemanticIndex`. from the source state and targeting the target state (`SysmlTransitionNode`). Either side may be a dotted feature chain, resolved the same way as `Connect`; recorded only when both the source and target resolve — an implied/omitted source produces no edge. +- `Render` — a view's resolved render-target reference (`render ;`), sourced from the + view's qualified name and targeting the resolved `SysmlViewNode.RenderTargetName`. + `GeneralViewLayoutStrategy` scopes its diagram to this target's containment subtree when + present; an unresolvable render target produces no `Render` edge (only the standard + unresolved-reference diagnostic), and the layout strategy falls back to rendering the full + workspace for that view. +- `Expose` — a view usage's resolved exposed-name reference (`expose ;`, valid only inside + a `view` usage's body), sourced from the view's qualified name and targeting each resolvable + entry in `SysmlViewNode.ExposedNames`, one `Expose` edge per entry. `GeneralViewLayoutStrategy` + additively unions each exposed name's containment subtree on top of the `Render` subject's + subtree. `SysmlEdge` is a sealed positional record with three properties: diff --git a/docs/design/sysml2-tools-language/semantic/model/sysml-node.md b/docs/design/sysml2-tools-language/semantic/model/sysml-node.md index 00464c7e..3f86e268 100644 --- a/docs/design/sysml2-tools-language/semantic/model/sysml-node.md +++ b/docs/design/sysml2-tools-language/semantic/model/sysml-node.md @@ -15,7 +15,7 @@ requirement-satisfaction usages. | `SysmlDefinitionNode` | Definition element (part def, attribute def, etc.); adds DefinitionKeyword | | `SysmlFeatureNode` | Feature/usage element | | `SysmlImportNode` | Import declaration; adds ImportedNamespace, IsWildcard | -| `SysmlViewNode` | View definition | +| `SysmlViewNode` | View definition; adds RenderTargetName, ExposedNames, FilterExpressionText | | `SysmlViewpointNode` | Viewpoint definition | | `SysmlConnectionNode` | Connection/binding/allocation usage; adds ConnectionKeyword, EndpointA, EndpointB | | `SysmlTransitionNode` | State transition; adds Source, Target, Guard | @@ -94,6 +94,27 @@ There are no behavioral methods beyond the inherited `object` members. `SysmlImp - `SubjectName` — the raw reference text of the satisfying subject (from the `by ` clause), or null when no `by` clause is present. +`SysmlViewNode` adds: + +- `RenderTargetName` — the raw reference text of the view's `render ;` member (the + first one found if more than one appears), or null when the view has no `render` member. + Extracted by the shared `AstBuilder.ExtractRenderTargetName` helper, which follows the same + two-form fallback pattern (direct reference, then typed placeholder) `VisitSatisfyRequirementUsage` + already uses. Resolved by `ReferenceResolver` into a `SysmlEdgeKind.Render` edge, or an + unresolved-reference diagnostic (and no edge) when the target does not resolve. +- `ExposedNames` — the raw reference text of each `expose ;` member in a `view` usage's + body, in source order, or empty when none are present (and always empty for a `view def` + definition — `expose` is only valid grammar inside a `view` usage's body). Extracted by + `AstBuilder.ExtractExposedNames`, sharing the same `ExtractImportTarget` helper `VisitImportRule` + uses for plain `import`. Each entry is independently resolved by `ReferenceResolver` into a + `SysmlEdgeKind.Expose` edge, or an unresolved-reference diagnostic (and no edge) for that entry. +- `FilterExpressionText` — the raw source text of the view's `filter [];` member's + bracketed expression, or null when absent. Captured verbatim by `AstBuilder` + (`elementFilterMember().ownedExpression().GetText()`) and never evaluated or inspected by + `ReferenceResolver` — full filter expression evaluation is deferred future work (see + ROADMAP.md). `GeneralViewLayoutStrategy` emits a "parsed but not yet evaluated" warning + whenever this is non-null. + ##### Error Handling N/A — node types are pure data containers with no logic or validation. Invalid or anonymous @@ -111,12 +132,16 @@ elements are filtered out by `AstBuilder` before a node is constructed. captured `comment`/`doc` annotating elements nested in the node's body; sets `VerifiedRequirementNames` via the recursive verification-member finder; builds `SysmlSatisfyNode` instances from `satisfyRequirementUsage` via `VisitSatisfyRequirementUsage`, and the - `"allocation"` `SysmlConnectionNode` variant via `VisitAllocationUsage`. + `"allocation"` `SysmlConnectionNode` variant via `VisitAllocationUsage`; builds `SysmlViewNode` + instances (setting `RenderTargetName`/`FilterExpressionText`, and additionally `ExposedNames` + for usages) from both `VisitViewDefinition` (`view def`) and `VisitViewUsage` (`view`, the + only form that can carry `expose`). - `SymbolTable` — traverses the node hierarchy via `Children`; reads `QualifiedName`. - `ReferenceResolver` — reads `SupertypeNames`, `FeatureTyping`, `ImportedNames`, `VerifiedRequirementNames`, `Children`; checks for `SysmlImportNode`, `SysmlSatisfyNode`, the `"allocation"` `SysmlConnectionNode` variant, the `"connection"`/`"message"` - `SysmlConnectionNode` variants, and `SysmlTransitionNode`; writes `ResolvedEdges` after - resolving references (in two passes — supertype/typing/import/satisfy/verify/allocate, then - feature-chain connect/transition). + `SysmlConnectionNode` variants, `SysmlTransitionNode`, and `SysmlViewNode` (reading + `RenderTargetName`/`ExposedNames`, never `FilterExpressionText`); writes `ResolvedEdges` after + resolving references (in two passes — supertype/typing/import/satisfy/verify/allocate/render/ + expose, then feature-chain connect/transition). - `SupertypeWalker` — reads `SupertypeNames` on each node retrieved from `SymbolTable`. diff --git a/docs/reqstream/sysml2-tools-core/layout/internal/general-view-layout-strategy.yaml b/docs/reqstream/sysml2-tools-core/layout/internal/general-view-layout-strategy.yaml index 7e56dc49..312804e3 100644 --- a/docs/reqstream/sysml2-tools-core/layout/internal/general-view-layout-strategy.yaml +++ b/docs/reqstream/sysml2-tools-core/layout/internal/general-view-layout-strategy.yaml @@ -150,3 +150,53 @@ sections: - GeneralViewLayoutStrategy_BuildLayout_AdaptiveGap_DenseModelProducesNonOverlappingBoxes - GeneralViewLayoutStrategy_BuildLayout_HeatLayout_ConnectedModelKeepsBoxesSeparated - GeneralViewLayoutStrategy_BuildLayout_HeatLayout_SparseModelProducesCompactCanvas + + - id: SysML2Tools-Core-Layout-Internal-GeneralViewLayoutStrategy-RenderTargetScoping + title: >- + When a view's ViewContext carries a resolved Render edge to a qualified name Q, + GeneralViewLayoutStrategy shall scope the diagram to Q and every declaration whose + qualified name is Q or has "Q::" as a prefix (Q's containment subtree), additionally + merging in the containment subtree of every resolved Expose edge target, combined + with Q's subtree. + justification: | + Every declared view previously rendered the identical full-workspace "GeneralView" + diagram regardless of its `render ;`/`expose <...>;` body statements — the + reported bug. Scoping the diagram to the render target's (and any exposed names') + containment subtree makes those statements finally have an observable effect, matching + the SysML v2 view/viewpoint mechanism's subject-scoping intent. + tests: + - GeneralViewLayoutStrategy_BuildLayout_ResolvedRenderTarget_ScopesToSubtreeFewerBoxes + - GeneralViewLayoutStrategy_BuildLayout_ExposedName_UnionsAdditionalSubtree + + - id: SysML2Tools-Core-Layout-Internal-GeneralViewLayoutStrategy-UnresolvedRenderFallback + title: >- + When a view's ViewContext carries no resolved Render edge — because the view has no + `render` statement, its ViewNode is null (e.g. the `--auto` synthesized view), or its + declared render target failed to resolve — GeneralViewLayoutStrategy shall render the + full workspace, unchanged from its pre-scoping behavior. + justification: | + An unresolved `render ;` (e.g. a typo) must not produce a broken or empty + diagram; falling back to full-workspace rendering (with the unresolved-reference + diagnostic already surfaced by ReferenceResolver) preserves today's behavior as a safe + default while still giving the user a visible signal elsewhere in the diagnostics + output. Views with no `render` statement at all — including the `--auto` synthesized + view, which never carries render/expose/filter data — must continue to render + everything exactly as before this change, with zero regression risk. + tests: + - GeneralViewLayoutStrategy_BuildLayout_UnresolvedRenderTarget_FallsBackToFullWorkspace + - GeneralViewLayoutStrategy_BuildLayout_NullViewNode_RendersFullWorkspaceUnchanged + + - id: SysML2Tools-Core-Layout-Internal-GeneralViewLayoutStrategy-UnevaluatedFilterWarning + title: >- + When a view's FilterExpressionText is non-null, GeneralViewLayoutStrategy shall emit a + diagnostic through the layout's Warnings channel stating that the view declares a + filter expression which is parsed but not yet evaluated, and shall continue rendering + the (unfiltered) resolved scope. + justification: | + Full `filter [];` expression evaluation is deferred future work (see ROADMAP.md). + Silently ignoring a declared filter would mislead a user into believing their diagram is + filtered when every element in the resolved scope is actually shown unfiltered; + surfacing an explicit warning through the existing layout-warnings channel makes this + limitation visible rather than a silent gap. + tests: + - GeneralViewLayoutStrategy_BuildLayout_FilterExpressionPresent_EmitsNotYetEvaluatedWarning diff --git a/docs/reqstream/sysml2-tools-core/layout/internal/layout-warnings.yaml b/docs/reqstream/sysml2-tools-core/layout/internal/layout-warnings.yaml index 976db4ea..74fa042c 100644 --- a/docs/reqstream/sysml2-tools-core/layout/internal/layout-warnings.yaml +++ b/docs/reqstream/sysml2-tools-core/layout/internal/layout-warnings.yaml @@ -30,3 +30,17 @@ sections: tests: - ForCrossings_One_ReturnsSingularWarning - ForCrossings_Many_ReturnsPluralWarning + + - id: SysML2Tools-Core-Layout-Internal-LayoutWarnings-UnevaluatedFilter + title: >- + When a view's filter expression text is non-null, LayoutWarnings shall produce a + warning naming the view and stating that its filter expression is parsed but not yet + evaluated; when null, LayoutWarnings shall produce no warning. + justification: | + Full `filter [];` expression evaluation is deferred future work (see + ROADMAP.md). Silently ignoring a declared filter would mislead a user into believing + their diagram is filtered when every element in the resolved scope actually renders + unfiltered; this warning makes the limitation visible. + tests: + - ForUnevaluatedFilter_NullText_ReturnsEmpty + - ForUnevaluatedFilter_NonNullText_ReturnsNotYetEvaluatedWarning diff --git a/docs/reqstream/sysml2-tools-core/rendering.yaml b/docs/reqstream/sysml2-tools-core/rendering.yaml index d34398de..aed9b105 100644 --- a/docs/reqstream/sysml2-tools-core/rendering.yaml +++ b/docs/reqstream/sysml2-tools-core/rendering.yaml @@ -102,6 +102,22 @@ sections: - DiagramRenderer_RenderWorkspace_SoftwareStructureModel_ReturnsSvgOutput - DiagramRenderer_RenderWorkspace_GeneralViewModel_SvgContainsElementNames + - id: SysML2Tools-Core-Rendering-ViewContextViewNode + title: >- + The view context passed to a layout strategy shall optionally carry the view's + resolved AST node, so that a layout strategy can access the view's declared + render target, exposed names, and filter expression when computing its layout. + justification: | + Every declared view previously rendered the identical full-workspace diagram + because no layout strategy had access to a view's declared `render`/`expose`/ + `filter` body statements at all — only its name and the whole workspace. Threading + the view's AST node through DiagramRenderer to each ILayoutStrategy is the + plumbing prerequisite for GeneralViewLayoutStrategy's subject-scoped rendering. + tests: + - ViewContext_Construction_WithViewNode_StoresViewNode + - GeneralViewLayoutStrategy_BuildLayout_ResolvedRenderTarget_ScopesToSubtreeFewerBoxes + - GeneralViewLayoutStrategy_BuildLayout_NullViewNode_RendersFullWorkspaceUnchanged + - id: SysML2Tools-Core-Rendering-DiagramRenderer title: >- For each view in a workspace, the Rendering subsystem shall lay the view out and diff --git a/docs/reqstream/sysml2-tools-language/semantic/model/ast-builder.yaml b/docs/reqstream/sysml2-tools-language/semantic/model/ast-builder.yaml index 30d5741d..21fc97b8 100644 --- a/docs/reqstream/sysml2-tools-language/semantic/model/ast-builder.yaml +++ b/docs/reqstream/sysml2-tools-language/semantic/model/ast-builder.yaml @@ -38,3 +38,34 @@ sections: tests: - WorkspaceLoader_LoadAsync_SpecializesChain_Registered - WorkspaceLoader_LoadAsync_UnresolvedReference_ProducesWarning + + - id: SysML2Tools-Language-Semantic-Model-AstBuilder-ViewRenderFilter + title: >- + AstBuilder shall capture a view's render target reference text (from its + `render ;` member), its filter expression source text (from its + `filter [];` member), and its exposed name references (from its + `expose ;` members, `view` usages only) as raw, unevaluated data on the + corresponding SysmlViewNode. + justification: | + Every declared view previously rendered the identical full-workspace diagram because + none of its `render`/`expose`/`filter` body statements were captured at all. Capturing + this data during AST construction is the prerequisite for GeneralViewLayoutStrategy to + scope a view's diagram to its declared subject, and for ReferenceResolver to resolve + the render/expose references into edges. + tests: + - WorkspaceLoader_LoadAsync_ViewRenderTargetResolves_RecordsRenderEdge + - WorkspaceLoader_LoadAsync_ViewFilterExpression_CapturesTextVerbatimNoEdge + - WorkspaceLoader_LoadAsync_ViewEmptyBody_AllNewFieldsNullOrEmpty + + - id: SysML2Tools-Language-Semantic-Model-AstBuilder-ViewUsageExpose + title: >- + AstBuilder shall build a SysmlViewNode from a named `view` usage (in addition to a + `view def` definition), capturing the same render/filter members plus its `expose` + members. + justification: | + `expose ;` is only valid grammar inside a `view` usage's body, not a `view def` + definition's body; without a dedicated VisitViewUsage override, named view usages + (including every `expose` statement) were never visited at all, so a view's exposed + names could never be captured, resolved, or scoped. + tests: + - WorkspaceLoader_LoadAsync_ViewUsageWithExpose_RecordsExposeEdge diff --git a/docs/reqstream/sysml2-tools-language/semantic/model/reference-resolver.yaml b/docs/reqstream/sysml2-tools-language/semantic/model/reference-resolver.yaml index 859027a6..db4ca962 100644 --- a/docs/reqstream/sysml2-tools-language/semantic/model/reference-resolver.yaml +++ b/docs/reqstream/sysml2-tools-language/semantic/model/reference-resolver.yaml @@ -26,3 +26,23 @@ sections: The cycle detection uses depth-first search with a traversal stack. tests: - WorkspaceLoader_LoadAsync_CircularImport_ProducesWarningNoInfiniteLoop + + - id: SysML2Tools-Language-Semantic-Model-ReferenceResolver-ViewRenderExpose + title: >- + ReferenceResolver.ResolveAll shall independently resolve a view's RenderTargetName + into a Render-kind edge and each of its ExposedNames into an Expose-kind edge, and + shall produce an unresolved-reference Warning diagnostic (with no corresponding edge) + for either when it cannot resolve — mirroring the existing SubjectName/EndpointA + unresolved-reference diagnostic pattern. FilterExpressionText is raw source text, not + a reference, and is never inspected here. + justification: | + Before this change, an unresolved `render ;` (e.g. a typo) silently rendered + the full workspace with no diagnostic at all, indistinguishable from a view with no + `render` statement — the exact bug being fixed. Surfacing the same unresolved-reference + diagnostic already used for satisfy/allocate gives the user a visible signal instead of + silent full-workspace rendering. + tests: + - WorkspaceLoader_LoadAsync_ViewRenderTargetResolves_RecordsRenderEdge + - WorkspaceLoader_LoadAsync_ViewRenderTargetUnresolved_ProducesWarningNoEdge + - WorkspaceLoader_LoadAsync_ViewUsageWithExpose_RecordsExposeEdge + - RenderSubsystem_ViewsWithDistinctRenderTargets_ProduceDifferingOutputsAndDiagnostic diff --git a/docs/reqstream/sysml2-tools-language/semantic/model/sysml-node.yaml b/docs/reqstream/sysml2-tools-language/semantic/model/sysml-node.yaml index 2ea9204d..09394f94 100644 --- a/docs/reqstream/sysml2-tools-language/semantic/model/sysml-node.yaml +++ b/docs/reqstream/sysml2-tools-language/semantic/model/sysml-node.yaml @@ -28,3 +28,21 @@ sections: on element type without string comparisons. tests: - WorkspaceLoader_LoadAsync_PartDef_RegistersDefinition + + - id: SysML2Tools-Language-Semantic-Model-SysmlNode-ViewRenderExposeFilter + title: >- + SysmlViewNode shall expose RenderTargetName (the raw reference text of its `render + ;` member, or null when absent), ExposedNames (the raw reference text of + each `expose ;` member, empty when absent), and FilterExpressionText (the raw + source text of its `filter [];` member, or null when absent). + justification: | + Every declared view previously rendered the identical full-workspace diagram: none of + its `render`/`expose`/`filter` body statements were observable anywhere in the AST, so + no downstream component (ReferenceResolver, GeneralViewLayoutStrategy) could act on + them. These three fields make a view's declared rendering intent user-observable data, + not merely an internal implementation detail. + tests: + - WorkspaceLoader_LoadAsync_ViewRenderTargetResolves_RecordsRenderEdge + - WorkspaceLoader_LoadAsync_ViewFilterExpression_CapturesTextVerbatimNoEdge + - WorkspaceLoader_LoadAsync_ViewUsageWithExpose_RecordsExposeEdge + - WorkspaceLoader_LoadAsync_ViewEmptyBody_AllNewFieldsNullOrEmpty diff --git a/docs/user_guide/introduction.md b/docs/user_guide/introduction.md index 61fa20b3..95c329d7 100644 --- a/docs/user_guide/introduction.md +++ b/docs/user_guide/introduction.md @@ -108,6 +108,33 @@ sysml2tools render model.sysml --auto --output out --format svg | Multiple views, `--view ` | Render only the named view | | `--view ` names a view that does not exist | Error: lists available view names, exits non-zero | +## View Body Statements + +A `view def`/`view` declaration's body may contain `render ;`, `expose ;`, and +`filter [];` statements. For the General View strategy (the diagram produced when no +more specialized view kind applies), these statements now scope the rendered diagram instead +of always rendering the entire workspace: + +- `render ;` — scopes the diagram to `` plus every declaration whose qualified + name is `` or is contained within it (a containment-subtree match, not just the exact + element). If `` does not resolve to any declaration in the workspace (for example, a + typo), the tool falls back to rendering the full workspace for that view — but now also + reports a diagnostic identifying the unresolved name, so the mistake is visible instead of + silently rendering everything with no signal. +- `expose ;` — additively includes ``'s containment subtree alongside the `render` + target's subtree. Like `render`, an unresolved `expose` name produces a diagnostic. +- `filter [];` — the bracketed filter expression is parsed and captured, but **not yet + evaluated**: the resolved (render/expose) scope is rendered unfiltered, and a diagnostic + reports that the filter expression was parsed but not yet evaluated. Full filter-expression + evaluation is planned future work — see `ROADMAP.md`. +- A view with **no** `render` statement (including the `--auto`-synthesized view) renders the + full workspace, exactly as before this scoping behavior was introduced. + +Only the General View layout strategy honors `render`/`expose` scoping in this release; the +other view kinds (Interconnection, State Transition, Action Flow, Sequence, Grid, Browser) +continue to render their full applicable scope regardless of a view's declared render target — +see `ROADMAP.md` for the planned follow-up extending scoping to those view kinds. + ## Depth Limiting Use `--depth ` to limit the nesting depth rendered. Parts beyond the limit are replaced diff --git a/docs/verification/sysml2-tools-core/layout/internal/general-view-layout-strategy.md b/docs/verification/sysml2-tools-core/layout/internal/general-view-layout-strategy.md index db704d46..9af71fb1 100644 --- a/docs/verification/sysml2-tools-core/layout/internal/general-view-layout-strategy.md +++ b/docs/verification/sysml2-tools-core/layout/internal/general-view-layout-strategy.md @@ -39,6 +39,20 @@ configuration are required beyond a standard .NET SDK installation. delegated layout does not over-pad sparse diagrams. - Standard-library-only input (by prefix or by seed set) yields a minimal empty canvas. - An empty workspace yields a 200×100 canvas with no nodes. +- A view whose `ViewContext.ViewNode` carries a resolved `Render` edge scopes the diagram to that + target's containment subtree, excluding unrelated sibling definitions and producing fewer boxes + than an unscoped (no-`ViewNode`) rendering of the same workspace. +- A view whose `ViewContext.ViewNode` additionally carries a resolved `Expose` edge additively + includes that exposed name's containment subtree alongside the render target's subtree. +- A view whose declared render target failed to resolve (no `Render` edge present) falls back to + rendering the full workspace, identical to a view with a `null` `ViewNode`. +- A view whose `ViewContext.ViewNode` carries a non-null `FilterExpressionText` emits the "parsed + but not yet evaluated" warning through `LayoutTree.Warnings`, while still rendering the resolved + (unfiltered) scope. +- A view with a `null` `ViewContext.ViewNode` (the `--auto` synthesized-view path, and the + pre-scoping-change 2-argument `ViewContext` construction used throughout the rest of this test + file) renders every non-stdlib definition in the workspace, unchanged from before this feature — + the critical regression guard confirming full backward compatibility. ##### Test Scenarios @@ -78,3 +92,14 @@ configuration are required beyond a standard .NET SDK installation. Connected cross-referencing model keeps every definition box non-overlapping - `GeneralViewLayoutStrategy_BuildLayout_HeatLayout_SparseModelProducesCompactCanvas`: Sparse canvas stays compact with no warnings (no over-padding) +- `GeneralViewLayoutStrategy_BuildLayout_ResolvedRenderTarget_ScopesToSubtreeFewerBoxes`: + A resolved `Render` edge scopes the diagram to the target's containment subtree, fewer boxes + than the full workspace +- `GeneralViewLayoutStrategy_BuildLayout_ExposedName_UnionsAdditionalSubtree`: + A resolved `Expose` edge additively includes its containment subtree +- `GeneralViewLayoutStrategy_BuildLayout_UnresolvedRenderTarget_FallsBackToFullWorkspace`: + No `Render` edge (target failed to resolve) falls back to full-workspace rendering +- `GeneralViewLayoutStrategy_BuildLayout_FilterExpressionPresent_EmitsNotYetEvaluatedWarning`: + A non-null `FilterExpressionText` emits the "parsed but not yet evaluated" warning +- `GeneralViewLayoutStrategy_BuildLayout_NullViewNode_RendersFullWorkspaceUnchanged`: + A `null` `ViewNode` (`--auto`/default) renders every definition, unchanged (regression guard) diff --git a/docs/verification/sysml2-tools-core/layout/internal/layout-warnings.md b/docs/verification/sysml2-tools-core/layout/internal/layout-warnings.md index a4cee701..012e5fa9 100644 --- a/docs/verification/sysml2-tools-core/layout/internal/layout-warnings.md +++ b/docs/verification/sysml2-tools-core/layout/internal/layout-warnings.md @@ -3,8 +3,9 @@ ##### Verification Approach `LayoutWarnings` is verified through unit tests in `LayoutWarningsTests` that call `ForCrossings` -with a view name and a crossing count and assert on the returned list. The unit is a pure function, -so no mocking is required. +with a view name and a crossing count, and `ForUnevaluatedFilter` with a view name and a filter +expression text, asserting on the returned lists in each case. The unit is a pure function, so no +mocking is required. ##### Test Environment @@ -17,6 +18,9 @@ configuration are required beyond a standard .NET SDK installation. - A zero crossing count yields no warning. - A count of one yields a single singular-form warning naming the view. - A count greater than one yields a single plural-form warning reporting the count. +- A null filter expression text yields no warning. +- A non-null filter expression text yields a single warning naming the view and stating the + filter expression is parsed but not yet evaluated. ##### Test Scenarios @@ -25,3 +29,5 @@ configuration are required beyond a standard .NET SDK installation. | `ForCrossings_Zero_ReturnsEmpty` | Zero crossings yields an empty list | | `ForCrossings_One_ReturnsSingularWarning` | One crossing yields a singular warning naming the view | | `ForCrossings_Many_ReturnsPluralWarning` | Multiple crossings yield a plural warning with the count | +| `ForUnevaluatedFilter_NullText_ReturnsEmpty` | A null filter expression text yields an empty list | +| `ForUnevaluatedFilter_NonNullText_ReturnsNotYetEvaluatedWarning` | Non-null filter yields a warning naming the view | diff --git a/docs/verification/sysml2-tools-core/rendering.md b/docs/verification/sysml2-tools-core/rendering.md index 524993fc..9e1af3c9 100644 --- a/docs/verification/sysml2-tools-core/rendering.md +++ b/docs/verification/sysml2-tools-core/rendering.md @@ -32,6 +32,8 @@ All test inputs are constructed inline. No external network access or services a - `Themes.Print` is non-null and has `DepthFillColors.Count >= 1` and non-empty `StrokeColor`. - `new RenderOptions(Themes.Light)` has `Scale == 1.0`, `Dpi == 96.0`, `DepthLimit == 0`. - `RenderOutput` stores `SuggestedFileName`, `MediaType`, and `Data` as supplied. +- `ViewContext` constructed with only the required `ViewName`/`Workspace` arguments has a + `null` `ViewNode`; constructed with an explicit `SysmlViewNode` stores it unchanged. ### Test Scenarios @@ -96,3 +98,13 @@ all three properties are asserted to equal the supplied values. when a concrete `ILayoutStrategy` implementation is available. The scenario will construct a `ViewContext` with a minimal `SysmlWorkspace` and assert that `BuildLayout` returns a non-null `LayoutTree` with non-negative `Width` and `Height`. + +**ViewContext_Construction_StoresAllFields**: A `ViewContext` is constructed with only +`ViewName` and `Workspace`; both properties are asserted to equal the supplied values and +`ViewNode` is asserted to be `null`. + +**ViewContext_Construction_WithViewNode_StoresViewNode**: A `ViewContext` is constructed +with an explicit `SysmlViewNode` as the third argument; `ViewNode` is asserted to be the +same instance supplied. This confirms a layout strategy that reads `ViewContext.ViewNode` +(currently only `GeneralViewLayoutStrategy`) receives the view's resolved render/expose/ +filter data. diff --git a/docs/verification/sysml2-tools-core/rendering/diagram-renderer.md b/docs/verification/sysml2-tools-core/rendering/diagram-renderer.md index 8c659b02..4fe5106f 100644 --- a/docs/verification/sysml2-tools-core/rendering/diagram-renderer.md +++ b/docs/verification/sysml2-tools-core/rendering/diagram-renderer.md @@ -20,6 +20,11 @@ external services are required. - Rendering a workspace with views produces one output per view, containing the rendered elements. - The same workspace renders successfully with both the SVG and PNG renderers. - A workspace with no views produces an empty result. +- `RenderWorkspace` threads each view's resolved `SysmlViewNode` into the `ViewContext` it + constructs, so `GeneralViewLayoutStrategy` can scope a rendered diagram to a view's declared + `render`/`expose` subject — verified end-to-end at the CLI level (see the `RenderCommand` + verification doc's `RenderSubsystem_ViewsWithDistinctRenderTargets_ProduceDifferingOutputsAndDiagnostic`) + and at the layout-strategy level (see the `GeneralViewLayoutStrategy` verification doc). #### Test Scenarios @@ -30,3 +35,4 @@ external services are required. | `DiagramRenderer_RenderWorkspace_GeneralViewModel_SvgContainsElementNames` | The SVG output contains element names | | `DiagramRenderer_RenderWorkspace_GeneralViewModel_PngProducesValidOutput` | The PNG output carries the PNG signature | | `DiagramRenderer_RenderWorkspace_NoViews_ReturnsEmptyList` | A view-free workspace yields an empty result | +| `RenderSubsystem_ViewsWithDistinctRenderTargets_ProduceDifferingOutputsAndDiagnostic` | Produces scoped output | diff --git a/docs/verification/sysml2-tools-language/semantic/model/ast-builder.md b/docs/verification/sysml2-tools-language/semantic/model/ast-builder.md index 489ebc74..c1f4cc10 100644 --- a/docs/verification/sysml2-tools-language/semantic/model/ast-builder.md +++ b/docs/verification/sysml2-tools-language/semantic/model/ast-builder.md @@ -22,6 +22,11 @@ external services or additional configuration are required beyond a standard .NE - An element with only a short name `< shortName >` (no declared name) is not registered. - A definition with `specializes KnownType` produces a `SupertypeNames` entry that resolves without a Warning when `KnownType` is registered. +- `VisitViewDefinition` captures `render ;` and `filter [];` members' raw text on + the corresponding `SysmlViewNode`, and leaves both null for a view with an empty body. +- `VisitViewUsage` (a named `view` usage, not a `view def` definition) captures the same + render/filter members plus `expose ;` members, producing a `SysmlViewNode` with + populated `ExposedNames`. ##### Test Scenarios @@ -31,3 +36,7 @@ external services or additional configuration are required beyond a standard .NE | Qualified name from namespace stack | `WorkspaceLoader_LoadAsync_NestedPackages_RegistersQualifiedNames` | | Definition registration | `WorkspaceLoader_LoadAsync_PartDef_RegistersDefinition` | | Supertype extraction | `WorkspaceLoader_LoadAsync_SpecializesChain_Registered` | +| `VisitViewDefinition` render capture | `WorkspaceLoader_LoadAsync_ViewRenderTargetResolves_RecordsRenderEdge` | +| `VisitViewDefinition` filter capture | `WorkspaceLoader_LoadAsync_ViewFilterExpression_CapturesTextVerbatimNoEdge` | +| `VisitViewUsage` expose capture | `WorkspaceLoader_LoadAsync_ViewUsageWithExpose_RecordsExposeEdge` | +| Empty view body regression guard | `WorkspaceLoader_LoadAsync_ViewEmptyBody_AllNewFieldsNullOrEmpty` | diff --git a/docs/verification/sysml2-tools-language/semantic/model/reference-resolver.md b/docs/verification/sysml2-tools-language/semantic/model/reference-resolver.md index d6a2eb9e..f4eb5f77 100644 --- a/docs/verification/sysml2-tools-language/semantic/model/reference-resolver.md +++ b/docs/verification/sysml2-tools-language/semantic/model/reference-resolver.md @@ -27,6 +27,11 @@ external services or additional configuration are required beyond a standard .NE unresolved one produces a Warning diagnostic and no edge. - A resolved import reference (wildcard or named) is recorded as an `Import`-kind `SysmlEdge`; an unresolved import reference produces a Warning diagnostic without crashing. +- A view's resolved `render ;` reference is recorded as a `Render`-kind `SysmlEdge`; an + unresolved render target produces a Warning diagnostic naming the unresolved identifier and no + edge — the exact fix for the reported bug (previously, an unresolved render target rendered + the full workspace silently with no diagnostic). +- A view usage's resolved `expose ;` reference is recorded as an `Expose`-kind `SysmlEdge`. ##### Test Scenarios @@ -41,3 +46,7 @@ external services or additional configuration are required beyond a standard .NE | Wildcard import records edge | `WorkspaceLoader_LoadAsync_WildcardImport_RecordsImportEdge` | | Named import records edge | `WorkspaceLoader_LoadAsync_NamedImport_RecordsImportEdge` | | Unresolved import — Warning, no crash | `WorkspaceLoader_LoadAsync_UnresolvedImport_ProducesWarningNoCrash` | +| Resolved render target records edge | `WorkspaceLoader_LoadAsync_ViewRenderTargetResolves_RecordsRenderEdge` | +| Unresolved render target, no edge | `WorkspaceLoader_LoadAsync_ViewRenderTargetUnresolved_ProducesWarningNoEdge` | +| Resolved expose name records edge | `WorkspaceLoader_LoadAsync_ViewUsageWithExpose_RecordsExposeEdge` | +| E2E diagnostic visibility | `RenderSubsystem_ViewsWithDistinctRenderTargets_ProduceDifferingOutputsAndDiagnostic` | diff --git a/docs/verification/sysml2-tools-language/semantic/model/sysml-edge.md b/docs/verification/sysml2-tools-language/semantic/model/sysml-edge.md index b3789939..b6afe5cf 100644 --- a/docs/verification/sysml2-tools-language/semantic/model/sysml-edge.md +++ b/docs/verification/sysml2-tools-language/semantic/model/sysml-edge.md @@ -20,6 +20,10 @@ external services or additional configuration are required beyond a standard .NE and the fully-qualified `TargetQualifiedName`. - A resolved feature typing reference produces a `SysmlEdge` with `Kind == SysmlEdgeKind.Typing`. - A resolved import reference produces a `SysmlEdge` with `Kind == SysmlEdgeKind.Import`. +- A resolved view render-target reference produces a `SysmlEdge` with + `Kind == SysmlEdgeKind.Render`. +- A resolved view-usage exposed-name reference produces a `SysmlEdge` with + `Kind == SysmlEdgeKind.Expose`. ##### Test Scenarios @@ -29,3 +33,5 @@ external services or additional configuration are required beyond a standard .NE | Typing edge recorded | `WorkspaceLoader_LoadAsync_ResolvedFeatureTyping_RecordsTypingEdge` | | Import edge recorded (wildcard) | `WorkspaceLoader_LoadAsync_WildcardImport_RecordsImportEdge` | | Import edge recorded (named) | `WorkspaceLoader_LoadAsync_NamedImport_RecordsImportEdge` | +| Render edge recorded | `WorkspaceLoader_LoadAsync_ViewRenderTargetResolves_RecordsRenderEdge` | +| Expose edge recorded | `WorkspaceLoader_LoadAsync_ViewUsageWithExpose_RecordsExposeEdge` | diff --git a/docs/verification/sysml2-tools-language/semantic/model/sysml-node.md b/docs/verification/sysml2-tools-language/semantic/model/sysml-node.md index 4b33aab5..ce28466f 100644 --- a/docs/verification/sysml2-tools-language/semantic/model/sysml-node.md +++ b/docs/verification/sysml2-tools-language/semantic/model/sysml-node.md @@ -28,6 +28,9 @@ external services or additional configuration are required beyond a standard .NE - `SysmlNode.Annotations` is populated by `AstBuilder` with captured `comment`/`doc` text for a node whose body contains one or more annotating elements, and is empty (never null) for a node with none. +- `SysmlViewNode.RenderTargetName`/`FilterExpressionText`/`ExposedNames` are populated verbatim + from a view's `render`/`filter`/`expose` body members (raw reference/expression text, never + evaluated), and are `null`/empty for a view with no such members. ##### Test Scenarios @@ -38,3 +41,7 @@ external services or additional configuration are required beyond a standard .NE | `SupertypeNames` population | `WorkspaceLoader_LoadAsync_SpecializesChain_Registered` | | `ResolvedEdges` populated | `WorkspaceLoader_LoadAsync_ResolvedSupertype_RecordsSupertypeEdge` | | `Annotations` populated | `WorkspaceLoader_LoadAsync_CommentAndDocumentation_CapturesBothInSourceOrder` | +| `SysmlViewNode.RenderTargetName` resolves | `WorkspaceLoader_LoadAsync_ViewRenderTargetResolves_RecordsRenderEdge` | +| `FilterExpressionText` verbatim | `WorkspaceLoader_LoadAsync_ViewFilterExpression_CapturesTextVerbatimNoEdge` | +| `SysmlViewNode.ExposedNames` from a `view` usage | `WorkspaceLoader_LoadAsync_ViewUsageWithExpose_RecordsExposeEdge` | +| Empty view body leaves all fields null/empty | `WorkspaceLoader_LoadAsync_ViewEmptyBody_AllNewFieldsNullOrEmpty` | diff --git a/docs/verification/sysml2-tools-tool/render.md b/docs/verification/sysml2-tools-tool/render.md index b258097f..26b1911d 100644 --- a/docs/verification/sysml2-tools-tool/render.md +++ b/docs/verification/sysml2-tools-tool/render.md @@ -39,6 +39,10 @@ on context output and exit code. File-writing scenarios use a temporary director - `render --help` prints render-specific usage and options (not the generic top-level command list), and is identical to `help render`'s output (see the Help subsystem verification document). +- A workspace with two views — one with a `render ;` statement naming a resolvable + target, one with a bogus `render thisIdentifierDoesNotExistAnywhere;` statement — produces + two output files whose content DIFFERS (the reported bug's exact fix), and the bogus view's + unresolved render target is visible as a diagnostic in the captured log output. ### Test Scenarios @@ -115,6 +119,18 @@ Verifies that `render --help` prints the render-specific usage line and its `--o regression-proofing test added alongside the `help` command's command-aware `--help` dispatch (see `docs/design/sysml2-tools-tool/help.md`). +#### RenderSubsystem_ViewsWithDistinctRenderTargets_ProduceDifferingOutputsAndDiagnostic + +End-to-end regression test reproducing the reported bug: a workspace declares two views — one +with `render TargetA;` (resolving to a `part def` with a nested child), one with `render +thisIdentifierDoesNotExistAnywhere;` (an unresolvable target). Verifies that rendering both +without `--view` produces `ViewValid.svg` and `ViewBogus.svg` whose content now DIFFERS (before +this fix, every view rendered the identical full-workspace diagram, so the two files would have +been byte-identical), and that the captured `--log` output contains +`"thisIdentifierDoesNotExistAnywhere"` — the unresolved-reference diagnostic surfaced by +`ReferenceResolver` for the bogus render target, closing the exact reported symptom (a typo'd +render target previously produced no visible signal at all). + #### ResxResource_EveryKey_ResolvesToNonEmptyText / ResxResource_KeysAndAccessorProperties_AreInBidirectionalParity (ResxResourceTests.cs) For the `RenderStrings` resource base name/accessor pair (one of four covered by these theory diff --git a/src/DemaConsulting.SysML2Tools.Core/Layout/Internal/GeneralViewLayoutStrategy.cs b/src/DemaConsulting.SysML2Tools.Core/Layout/Internal/GeneralViewLayoutStrategy.cs index 3f48fb4a..396c6c7a 100644 --- a/src/DemaConsulting.SysML2Tools.Core/Layout/Internal/GeneralViewLayoutStrategy.cs +++ b/src/DemaConsulting.SysML2Tools.Core/Layout/Internal/GeneralViewLayoutStrategy.cs @@ -108,8 +108,16 @@ public LayoutTree BuildLayout(ViewContext context, RenderOptions options) var theme = options.Theme; - // Collect all user-defined definitions, sized for rendering. - var defs = CollectDefinitions(context.Workspace, theme); + // Resolve the view's render-target subject scope (render target plus exposed names' + // containment subtrees), or null when the view has no resolved Render edge — including a + // null ViewNode (the --auto synthetic-view path) and an unresolved render target, both of + // which leave no Render edge on ResolvedEdges. A null scope means "render everything", + // byte-identical to the pre-scoping behavior. + var scope = ResolveSubjectScope(context.ViewNode); + + // Collect all user-defined definitions, sized for rendering, restricted to the resolved + // scope when one applies. + var defs = CollectDefinitions(context.Workspace, theme, scope); if (defs.Count == 0) { return new LayoutTree(200.0, 100.0, []); @@ -136,14 +144,69 @@ public LayoutTree BuildLayout(ViewContext context, RenderOptions options) // Stamp the "+N more…" ellipsis label onto each truncated folder's placed box. The leaf // algorithm emits one box per root node in Nodes order, so the boxes portion of the placed // tree aligns with graph.Nodes by index. - return truncated.Count == 0 ? tree : DecorateTruncatedFolders(tree, graph, truncated, theme); + var placed = truncated.Count == 0 ? tree : DecorateTruncatedFolders(tree, graph, truncated, theme); + + // A `filter [];` statement is parsed (SysmlViewNode.FilterExpressionText) but not + // yet evaluated — full expression evaluation is deferred future work (see ROADMAP.md). + // Surface this to the caller through the standard layout-warnings channel rather than + // silently rendering a diagram the user may believe is filtered. + var warnings = LayoutWarnings.ForUnevaluatedFilter(context.ViewName, context.ViewNode?.FilterExpressionText); + return warnings.Count == 0 ? placed : placed with { Warnings = warnings }; } + /// + /// Resolves the qualified-name containment-subtree scope a view's render/expose + /// statements restrict the diagram to, or when the view has no resolved + /// edge — meaning every non-stdlib definition is included, + /// unchanged from the pre-scoping behavior. This covers three equivalent "no scoping" cases + /// uniformly: a null (the --auto synthetic view, which never + /// carries render/expose/filter data), a view with no render statement, and a view whose + /// render target failed to resolve (per binding decision, an unresolved target falls + /// back to full-workspace rendering; the diagnostic that informs the user is emitted by + /// ReferenceResolver, not here). + /// + /// The view's AST node, or null for the synthetic --auto view. + /// + /// The list of subject qualified names (the render target plus any exposed names) whose + /// containment subtrees are in scope, or null when no scoping applies. + /// + private static IReadOnlyList? ResolveSubjectScope(SysmlViewNode? viewNode) + { + var renderTarget = viewNode?.ResolvedEdges + .FirstOrDefault(edge => edge.Kind == SysmlEdgeKind.Render) + ?.TargetQualifiedName; + if (renderTarget is null) + { + return null; + } + + var subjects = new List { renderTarget }; + subjects.AddRange(viewNode!.ResolvedEdges + .Where(edge => edge.Kind == SysmlEdgeKind.Expose) + .Select(edge => edge.TargetQualifiedName)); + return subjects; + } + + /// + /// Returns when is one of + /// or lies within one of their containment subtrees (a + /// "{subject}::" prefix match), reusing the same qualified-name-prefix idiom + /// already uses for + /// stdlib-prefix matching. + /// + private static bool IsInSubjectScope(string qualifiedName, IReadOnlyList subjects) => + subjects.Any(subject => + qualifiedName == subject || qualifiedName.StartsWith(subject + "::", StringComparison.Ordinal)); + /// /// Collects every user-defined from the workspace and computes - /// each box's intrinsic size from its keyword and name. + /// each box's intrinsic size from its keyword and name, restricted to + /// when non-null (the view's resolved render/expose containment subtrees). /// - private static IReadOnlyList CollectDefinitions(SysmlWorkspace workspace, Theme theme) + private static IReadOnlyList CollectDefinitions( + SysmlWorkspace workspace, + Theme theme, + IReadOnlyList? scope) { var result = new List(); @@ -159,6 +222,11 @@ private static IReadOnlyList CollectDefinitions(SysmlWorkspace workspace continue; } + if (scope is not null && !IsInSubjectScope(qualifiedName, scope)) + { + continue; + } + var simpleName = def.Name ?? qualifiedName; var keyword = string.IsNullOrEmpty(def.DefinitionKeyword) ? "def" : def.DefinitionKeyword; diff --git a/src/DemaConsulting.SysML2Tools.Core/Layout/Internal/LayoutWarnings.cs b/src/DemaConsulting.SysML2Tools.Core/Layout/Internal/LayoutWarnings.cs index c7c57aa5..fc0782e8 100644 --- a/src/DemaConsulting.SysML2Tools.Core/Layout/Internal/LayoutWarnings.cs +++ b/src/DemaConsulting.SysML2Tools.Core/Layout/Internal/LayoutWarnings.cs @@ -35,4 +35,29 @@ public static IReadOnlyList ForCrossings(string viewName, int crossings) "the diagram may be cluttered.", ]; } + + /// + /// Returns a single-element warning list stating that a view's filter [<expr>]; + /// statement was parsed but not evaluated, or an empty list when the view declares no filter + /// expression. + /// + /// Name of the view being laid out. + /// + /// The view's raw filter expression source text, or when the view + /// declares no filter member. + /// + /// The warning messages for the view. + public static IReadOnlyList ForUnevaluatedFilter(string viewName, string? filterExpressionText) + { + if (filterExpressionText is null) + { + return []; + } + + return + [ + $"View '{viewName}' declares a filter expression, which is parsed but not yet " + + "evaluated; all elements in the resolved scope are rendered unfiltered.", + ]; + } } diff --git a/src/DemaConsulting.SysML2Tools.Core/Rendering/DiagramRenderer.cs b/src/DemaConsulting.SysML2Tools.Core/Rendering/DiagramRenderer.cs index d734f54f..0d97172e 100644 --- a/src/DemaConsulting.SysML2Tools.Core/Rendering/DiagramRenderer.cs +++ b/src/DemaConsulting.SysML2Tools.Core/Rendering/DiagramRenderer.cs @@ -197,7 +197,7 @@ public IReadOnlyList RenderWorkspace( continue; } - var context = new ViewContext(viewName, workspace); + var context = new ViewContext(viewName, workspace, viewNode); // Build the layout tree for this view var layout = strategy.BuildLayout(context, options); diff --git a/src/DemaConsulting.SysML2Tools.Core/Rendering/ILayoutStrategy.cs b/src/DemaConsulting.SysML2Tools.Core/Rendering/ILayoutStrategy.cs index 0004c443..23623b78 100644 --- a/src/DemaConsulting.SysML2Tools.Core/Rendering/ILayoutStrategy.cs +++ b/src/DemaConsulting.SysML2Tools.Core/Rendering/ILayoutStrategy.cs @@ -5,6 +5,7 @@ using DemaConsulting.Rendering; using DemaConsulting.Rendering.Abstractions; using DemaConsulting.SysML2Tools.Semantic; +using DemaConsulting.SysML2Tools.Semantic.Model; namespace DemaConsulting.SysML2Tools.Rendering; @@ -21,9 +22,17 @@ namespace DemaConsulting.SysML2Tools.Rendering; /// resolve the view's target element(s) and traverse related declarations while building the /// layout tree. /// +/// +/// The resolved the view was declared from, carrying its resolved +/// Render/Expose edges and raw FilterExpressionText so a strategy can scope +/// its diagram accordingly. Nullable to preserve the --auto synthetic-view path, whose +/// synthesized carries no render/expose/filter data; defaults to +/// so existing two-argument construction call sites remain unchanged. +/// public sealed record ViewContext( string ViewName, - SysmlWorkspace Workspace); + SysmlWorkspace Workspace, + SysmlViewNode? ViewNode = null); /// /// Computes a from a . diff --git a/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/AstBuilder.cs b/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/AstBuilder.cs index dd4aacc0..dce640da 100644 --- a/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/AstBuilder.cs +++ b/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/AstBuilder.cs @@ -797,12 +797,153 @@ private static (string? A, string? B) ExtractConnectorEnds(SysMLv2Parser.Connect var supertypeNames = GetSubclassificationSupertypes( context.definitionDeclaration()?.subclassificationPart()); + var (renderTargetName, filterExpressionText) = + ExtractViewRenderAndFilter(context.viewDefinitionBody()?.viewDefinitionBodyItem() ?? []); + return new SysmlViewNode { Name = name, QualifiedName = qualifiedName, SupertypeNames = supertypeNames, + RenderTargetName = renderTargetName, + FilterExpressionText = filterExpressionText, + }; + } + + /// + /// Builds a from a view usage (as opposed to a + /// view def definition), capturing the same render/filter members as + /// plus expose members — the usage form's only + /// grammar addition over the definition form's body. + /// + /// + /// Unnamed view usages (no declared name) are not registered as symbols and are skipped, + /// mirroring the existing anonymous-element convention used by + /// and other usage visitors. + /// + public override SysmlNode? VisitViewUsage(SysMLv2Parser.ViewUsageContext context) + { + var name = GetDeclaredName(context.usageDeclaration()?.identification()); + if (name is null) + { + return null; + } + + var qualifiedName = QualifyName(name); + var bodyItems = context.viewBody()?.viewBodyItem() ?? []; + + var (renderTargetName, filterExpressionText) = ExtractViewRenderAndFilter(bodyItems); + var exposedNames = ExtractExposedNames(bodyItems); + + return new SysmlViewNode + { + Name = name, + QualifiedName = qualifiedName, + RenderTargetName = renderTargetName, + ExposedNames = exposedNames, + FilterExpressionText = filterExpressionText, + }; + } + + /// + /// Scans a view's body items for a render <target>; member and a + /// filter [<expression>]; member, returning the raw reference text and raw + /// expression source text respectively (or for either when absent). + /// Shared by (viewDefinitionBodyItem) and + /// (viewBodyItem) since both context types expose + /// identically-shaped viewRenderingMember()/elementFilterMember() accessors. + /// The first render member wins if more than one appears — SysML disallows more + /// than one render subject per view, so this is a defensive tie-break, not a validated + /// constraint enforced by this tool. + /// + private static (string? RenderTargetName, string? FilterExpressionText) ExtractViewRenderAndFilter( + IEnumerable bodyItems) + where TItem : Antlr4.Runtime.ParserRuleContext + { + string? renderTargetName = null; + string? filterExpressionText = null; + + foreach (var item in bodyItems) + { + if (renderTargetName is null && + GetViewRenderingMember(item) is { } renderingMember) + { + renderTargetName = ExtractRenderTargetName(renderingMember.viewRenderingUsage()); + } + + if (filterExpressionText is null && + GetElementFilterMember(item) is { } filterMember) + { + filterExpressionText = filterMember.ownedExpression()?.GetText(); + } + } + + return (renderTargetName, filterExpressionText); + } + + /// Extracts the viewRenderingMember() accessor common to both view body item types. + private static SysMLv2Parser.ViewRenderingMemberContext? GetViewRenderingMember(Antlr4.Runtime.ParserRuleContext item) => + item switch + { + SysMLv2Parser.ViewDefinitionBodyItemContext defItem => defItem.viewRenderingMember(), + SysMLv2Parser.ViewBodyItemContext usageItem => usageItem.viewRenderingMember(), + _ => null, + }; + + /// Extracts the elementFilterMember() accessor common to both view body item types. + private static SysMLv2Parser.ElementFilterMemberContext? GetElementFilterMember(Antlr4.Runtime.ParserRuleContext item) => + item switch + { + SysMLv2Parser.ViewDefinitionBodyItemContext defItem => defItem.elementFilterMember(), + SysMLv2Parser.ViewBodyItemContext usageItem => usageItem.elementFilterMember(), + _ => null, }; + + /// + /// Extracts the raw reference text of a render <target>; statement, preferring + /// the direct-reference form (ownedReferenceSubsetting) and falling back to the + /// typed-placeholder form's feature typing — the same two-form fallback pattern + /// uses for satisfy's two grammar forms. + /// + private static string? ExtractRenderTargetName(SysMLv2Parser.ViewRenderingUsageContext? usage) + { + if (usage is null) + { + return null; + } + + return usage.ownedReferenceSubsetting()?.GetText() + ?? ExtractFeatureTyping(usage.featureSpecializationPart()) + ?? usage.usage()?.GetText(); + } + + /// + /// Collects the raw reference text of every expose <name>; member in a + /// view usage's body, in source order, reusing — + /// the same namespace/membership-import shape import statements use. + /// + private static IReadOnlyList ExtractExposedNames( + IEnumerable bodyItems) + { + var names = new List(); + foreach (var item in bodyItems) + { + var expose = item.expose(); + if (expose is null) + { + continue; + } + + var (qn, _) = ExtractImportTarget( + expose.namespaceExpose()?.namespaceImport(), + expose.membershipExpose()?.membershipImport()); + if (qn is { Length: > 0 }) + { + names.Add(qn); + } + } + + return names; } /// @@ -835,40 +976,62 @@ private static (string? A, string? B) ExtractConnectorEnds(SysMLv2Parser.Connect return null; } + var (qn, isWildcard) = ExtractImportTarget(decl.namespaceImport(), decl.membershipImport()); + if (qn is null) + { + return null; + } + + return new SysmlImportNode + { + ImportedNamespace = qn, + ImportedNames = [qn], + IsWildcard = isWildcard, + }; + } + + /// + /// Extracts the qualified/dotted name text and wildcard flag from either an import's + /// namespace form (qualifiedName::*, a wildcard by definition) or membership form + /// (qualifiedName, optionally ::** for a recursive wildcard). Shared by + /// 's import handling and 's + /// expose handling, since both grammar constructs wrap the identical + /// namespaceImport/membershipImport shapes — extracted here per the + /// Copy-Paste Programming anti-pattern in coding-principles.md rather than duplicating the + /// extraction logic at both call sites. + /// + /// The wildcard-import alternative, or null when not this form. + /// The membership-import alternative, or null when not this form. + /// + /// The extracted qualified/dotted name text (or null when neither alternative yielded + /// text) and whether the import/expose is a wildcard. + /// + private static (string? QualifiedName, bool IsWildcard) ExtractImportTarget( + SysMLv2Parser.NamespaceImportContext? namespaceImport, + SysMLv2Parser.MembershipImportContext? membershipImport) + { // Namespace import: qualifiedName::* — wildcard, all members of the namespace are in scope - var nsImport = decl.namespaceImport(); - if (nsImport is not null) + if (namespaceImport is not null) { - var qn = nsImport.qualifiedName()?.GetText(); + var qn = namespaceImport.qualifiedName()?.GetText(); if (qn is { Length: > 0 }) { - return new SysmlImportNode - { - ImportedNamespace = qn, - ImportedNames = [qn], - IsWildcard = true, - }; + return (qn, true); } } // Membership import: qualifiedName (optional ::**) // The ** form is a recursive wildcard; either way it enables lookup under the namespace - var memImport = decl.membershipImport(); - if (memImport is not null) + if (membershipImport is not null) { - var qn = memImport.qualifiedName()?.GetText(); + var qn = membershipImport.qualifiedName()?.GetText(); if (qn is { Length: > 0 }) { - return new SysmlImportNode - { - ImportedNamespace = qn, - ImportedNames = [qn], - IsWildcard = memImport.STAR_STAR() is not null, - }; + return (qn, membershipImport.STAR_STAR() is not null); } } - return null; + return (null, false); } /// diff --git a/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/ReferenceResolver.cs b/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/ReferenceResolver.cs index d40bfb8c..218be2df 100644 --- a/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/ReferenceResolver.cs +++ b/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/ReferenceResolver.cs @@ -534,6 +534,52 @@ private void ResolveNode( } } + // Views resolve two independent reference sets: RenderTargetName (a view's `render + // ;` subject) and each entry of ExposedNames (a view's `expose ;` + // members). Unlike Satisfy/Allocate, each is resolved and edged independently — a + // resolved RenderTargetName produces a Render edge even when an ExposedNames entry fails + // to resolve, and vice versa, since the two statements are functionally unrelated (unlike + // satisfy's two-sided subject/requirement pair). An unresolved RenderTargetName produces + // only the Warning diagnostic below; GeneralViewLayoutStrategy observes the resulting + // absence of a Render edge and falls back to rendering the full workspace for that view + // — the same observable state as a view with no `render` statement at all. + // FilterExpressionText is raw source text, not a reference, and is intentionally never + // touched here. + if (node is SysmlViewNode view) + { + if (view.RenderTargetName is { Length: > 0 } renderTargetName) + { + if (TryResolve(renderTargetName, namespaceStack, imports, out var resolvedTarget)) + { + nodeEdges.Add(new SysmlEdge(node.QualifiedName, resolvedTarget, SysmlEdgeKind.Render)); + } + else if (resolvedInFile.Add(renderTargetName)) + { + _diagnostics.Add(new SysmlDiagnostic( + filePath, + 0, 0, + DiagnosticSeverity.Warning, + $"Unresolved reference: '{renderTargetName}'")); + } + } + + foreach (var exposedName in view.ExposedNames) + { + if (TryResolve(exposedName, namespaceStack, imports, out var resolvedExposed)) + { + nodeEdges.Add(new SysmlEdge(node.QualifiedName, resolvedExposed, SysmlEdgeKind.Expose)); + } + else if (resolvedInFile.Add(exposedName)) + { + _diagnostics.Add(new SysmlDiagnostic( + filePath, + 0, 0, + DiagnosticSeverity.Warning, + $"Unresolved reference: '{exposedName}'")); + } + } + } + if (nodeEdges.Count > 0) { node.ResolvedEdges = nodeEdges; diff --git a/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/SysmlEdge.cs b/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/SysmlEdge.cs index 85538a42..8f39ece3 100644 --- a/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/SysmlEdge.cs +++ b/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/SysmlEdge.cs @@ -65,6 +65,25 @@ public enum SysmlEdgeKind /// state to walk from) produces no edge, a documented limitation of this unit. /// Transition, + + /// + /// A view render-target reference (render <target>; nested in a view's + /// body), from the view to the resolved element the view is scoped to. Recorded only + /// when the target resolves; an unresolved target produces an unresolved-reference + /// diagnostic instead (see ), and + /// GeneralViewLayoutStrategy falls back to rendering the full workspace for that + /// view — identical to a view with no render statement at all. + /// + Render, + + /// + /// A view expose reference (expose <name>; nested in a view's body), from + /// the view to a resolved element whose containment subtree is additionally included in + /// the rendered scope alongside the subject. One edge is recorded + /// per resolvable entry in ; an unresolved entry + /// produces an unresolved-reference diagnostic instead. + /// + Expose, } /// diff --git a/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/SysmlNode.cs b/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/SysmlNode.cs index 008aa7ff..0040cd5f 100644 --- a/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/SysmlNode.cs +++ b/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/SysmlNode.cs @@ -167,7 +167,7 @@ public sealed class SysmlImportNode : SysmlNode } /// -/// AST node representing a view definition. +/// AST node representing a view definition or view usage. /// /// /// Inherited from SysmlNode: Name, QualifiedName, Children, SupertypeNames, ImportedNames, @@ -175,6 +175,36 @@ public sealed class SysmlImportNode : SysmlNode /// public sealed class SysmlViewNode : SysmlNode { + /// + /// Gets the raw reference text of this view's render <target>; statement + /// (from viewRenderingMember().viewRenderingUsage()), or + /// when the view declares no rendering member. Resolved by + /// into a edge when the name resolves, or an + /// unresolved-reference diagnostic when it does not — an unresolved (or absent) render + /// target causes GeneralViewLayoutStrategy to fall back to rendering the full + /// workspace, unchanged from the pre-scoping behavior. + /// + public string? RenderTargetName { get; init; } + + /// + /// Gets the raw reference text of each expose <name>; member nested in this + /// view's body, in source order. Empty when the view declares no expose members. + /// Each entry is resolved by into a + /// edge when it resolves, or an unresolved-reference + /// diagnostic when it does not. + /// + public IReadOnlyList ExposedNames { get; init; } = Array.Empty(); + + /// + /// Gets the raw source text of this view's filter [<expression>]; statement + /// (from elementFilterMember().ownedExpression().GetText()), or + /// when the view declares no filter member. Captured verbatim only + /// — no expression tree is built and no evaluation is performed; a non-null value causes + /// GeneralViewLayoutStrategy to emit a "not yet evaluated" warning while still + /// rendering the (unfiltered) resolved scope. Full filter-expression evaluation is + /// deferred future work — see the project ROADMAP. + /// + public string? FilterExpressionText { get; init; } } /// diff --git a/src/DemaConsulting.SysML2Tools.Stdlib/Resources/stdlib.json.gz b/src/DemaConsulting.SysML2Tools.Stdlib/Resources/stdlib.json.gz index 634cde8f2614ddd039b8f6e6f316d934cbe241e7..f857ad426b909c47aadcf0a21784aa0c959a6817 100644 GIT binary patch delta 35271 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

t=haE%w9*cXr^m~niB-5lnro7t=*EU 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 delta 35149 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 diff --git a/test/DemaConsulting.SysML2Tools.Tests/Layout/GeneralViewLayoutStrategyTests.cs b/test/DemaConsulting.SysML2Tools.Tests/Layout/GeneralViewLayoutStrategyTests.cs index e853f61b..deccadb7 100644 --- a/test/DemaConsulting.SysML2Tools.Tests/Layout/GeneralViewLayoutStrategyTests.cs +++ b/test/DemaConsulting.SysML2Tools.Tests/Layout/GeneralViewLayoutStrategyTests.cs @@ -756,6 +756,184 @@ public void GeneralViewLayoutStrategy_BuildLayout_HeatLayout_SparseModelProduces $"Sparse canvas height {layout.Height} should be below 500px (no over-padding)"); } + ///

+ /// Builds the fixed three-definition workspace shared by the render/expose scoping tests: + /// Root::A (the render target), Root::A::Child (inside A's containment + /// subtree, qualified name prefixed "Root::A::"), and Root::B (an unrelated + /// sibling definition, outside A's subtree). + /// + private static SysmlWorkspace BuildScopingWorkspace() => new() + { + Declarations = new Dictionary + { + ["Root::A"] = new SysmlDefinitionNode { Name = "A", QualifiedName = "Root::A", DefinitionKeyword = "part def" }, + ["Root::A::Child"] = new SysmlDefinitionNode { Name = "Child", QualifiedName = "Root::A::Child", DefinitionKeyword = "part def" }, + ["Root::B"] = new SysmlDefinitionNode { Name = "B", QualifiedName = "Root::B", DefinitionKeyword = "part def" } + } + }; + + /// + /// A view with a resolved Render edge to Root::A scopes the diagram to + /// Root::A plus its containment subtree (Root::A::Child), excluding the + /// unrelated sibling Root::B — producing fewer boxes than rendering the full + /// workspace. + /// + [Fact] + public void GeneralViewLayoutStrategy_BuildLayout_ResolvedRenderTarget_ScopesToSubtreeFewerBoxes() + { + // Arrange: a view whose render target resolves to Root::A + var strategy = new GeneralViewLayoutStrategy(); + var workspace = BuildScopingWorkspace(); + var viewNode = new SysmlViewNode + { + Name = "V", + QualifiedName = "Root::V", + RenderTargetName = "A", + ResolvedEdges = [new SysmlEdge("Root::V", "Root::A", SysmlEdgeKind.Render)] + }; + var context = new ViewContext("v", workspace, viewNode); + var options = new RenderOptions(Themes.Light); + + // Act + var scoped = strategy.BuildLayout(context, options); + var full = strategy.BuildLayout(new ViewContext("full", workspace), options); + + // Assert: the scoped view contains A and A::Child but not B, and has fewer boxes than + // the unscoped full-workspace rendering. + var labels = CollectBoxes(scoped.Nodes).Select(b => b.Label).ToList(); + Assert.Contains("A", labels); + Assert.Contains("Child", labels); + Assert.DoesNotContain("B", labels); + Assert.True(CollectBoxes(scoped.Nodes).Count < CollectBoxes(full.Nodes).Count); + } + + /// + /// A view with a resolved Render edge to Root::A plus a resolved + /// Expose edge to Root::B additively includes Root::B's containment + /// subtree alongside the render subject's subtree. + /// + [Fact] + public void GeneralViewLayoutStrategy_BuildLayout_ExposedName_UnionsAdditionalSubtree() + { + // Arrange: a view rendering Root::A and additionally exposing Root::B + var strategy = new GeneralViewLayoutStrategy(); + var workspace = BuildScopingWorkspace(); + var viewNode = new SysmlViewNode + { + Name = "V", + QualifiedName = "Root::V", + RenderTargetName = "A", + ExposedNames = ["B"], + ResolvedEdges = + [ + new SysmlEdge("Root::V", "Root::A", SysmlEdgeKind.Render), + new SysmlEdge("Root::V", "Root::B", SysmlEdgeKind.Expose) + ] + }; + var context = new ViewContext("v", workspace, viewNode); + var options = new RenderOptions(Themes.Light); + + // Act + var layout = strategy.BuildLayout(context, options); + + // Assert: A, A::Child, and B (the exposed subtree) are all present. + var labels = CollectBoxes(layout.Nodes).Select(b => b.Label).ToList(); + Assert.Contains("A", labels); + Assert.Contains("Child", labels); + Assert.Contains("B", labels); + } + + /// + /// A view whose RenderTargetName failed to resolve (no Render edge on + /// , since ReferenceResolver never adds one + /// for an unresolved target) falls back to rendering the full workspace — the same + /// byte/box-count-identical result as a view with no render statement at all. This + /// is the regression-safe fallback required by the bug-fix binding decision. + /// + [Fact] + public void GeneralViewLayoutStrategy_BuildLayout_UnresolvedRenderTarget_FallsBackToFullWorkspace() + { + // Arrange: a view whose render target never resolved (no Render edge present) + var strategy = new GeneralViewLayoutStrategy(); + var workspace = BuildScopingWorkspace(); + var viewNode = new SysmlViewNode + { + Name = "V", + QualifiedName = "Root::V", + RenderTargetName = "thisIdentifierDoesNotExistAnywhere", + ResolvedEdges = [] + }; + var context = new ViewContext("v", workspace, viewNode); + var options = new RenderOptions(Themes.Light); + + // Act + var fallback = strategy.BuildLayout(context, options); + var full = strategy.BuildLayout(new ViewContext("full", workspace), options); + + // Assert: identical box count/labels to the full-workspace (no-ViewNode) rendering. + var fallbackLabels = CollectBoxes(fallback.Nodes).Select(b => b.Label).OrderBy(l => l).ToList(); + var fullLabels = CollectBoxes(full.Nodes).Select(b => b.Label).OrderBy(l => l).ToList(); + Assert.Equal(fullLabels, fallbackLabels); + } + + /// + /// A view whose FilterExpressionText is non-null emits the "parsed but not yet + /// evaluated" diagnostic through , while still rendering + /// the (unfiltered) resolved scope — per the binding decision to defer filter expression + /// evaluation to a future roadmap item. + /// + [Fact] + public void GeneralViewLayoutStrategy_BuildLayout_FilterExpressionPresent_EmitsNotYetEvaluatedWarning() + { + // Arrange: a view declaring a filter expression + var strategy = new GeneralViewLayoutStrategy(); + var workspace = BuildScopingWorkspace(); + var viewNode = new SysmlViewNode + { + Name = "V", + QualifiedName = "Root::V", + FilterExpressionText = "@SysML::PartUsage" + }; + var context = new ViewContext("v", workspace, viewNode); + var options = new RenderOptions(Themes.Light); + + // Act + var layout = strategy.BuildLayout(context, options); + + // Assert: a warning about the unevaluated filter is present, and the resolved (unfiltered) + // scope — here, the full workspace, since no render target was declared — still renders. + Assert.Contains(layout.Warnings, w => w.Contains("filter expression") && w.Contains("not yet evaluated")); + var labels = CollectBoxes(layout.Nodes).Select(b => b.Label).ToList(); + Assert.Contains("A", labels); + Assert.Contains("B", labels); + } + + /// + /// A view with no render statement (a null , e.g. + /// the --auto synthesized view) renders identically to the pre-scoping-change + /// baseline: every non-stdlib definition in the workspace. This is the critical regression + /// guard confirming the scoping feature is fully backward-compatible when unused. + /// + [Fact] + public void GeneralViewLayoutStrategy_BuildLayout_NullViewNode_RendersFullWorkspaceUnchanged() + { + // Arrange: no ViewNode at all — the pre-existing 2-arg ViewContext construction. + var strategy = new GeneralViewLayoutStrategy(); + var workspace = BuildScopingWorkspace(); + var context = new ViewContext("v", workspace); + var options = new RenderOptions(Themes.Light); + + // Act + var layout = strategy.BuildLayout(context, options); + + // Assert: all three definitions are present and no warnings are emitted. + var labels = CollectBoxes(layout.Nodes).Select(b => b.Label).ToList(); + Assert.Contains("A", labels); + Assert.Contains("B", labels); + Assert.Contains("Child", labels); + Assert.Empty(layout.Warnings); + } + /// /// Asserts that no two rendered definition (rectangle-shaped) boxes overlap in the layout. /// diff --git a/test/DemaConsulting.SysML2Tools.Tests/Layout/LayoutWarningsTests.cs b/test/DemaConsulting.SysML2Tools.Tests/Layout/LayoutWarningsTests.cs index ace82911..39f31a70 100644 --- a/test/DemaConsulting.SysML2Tools.Tests/Layout/LayoutWarningsTests.cs +++ b/test/DemaConsulting.SysML2Tools.Tests/Layout/LayoutWarningsTests.cs @@ -38,4 +38,26 @@ public void ForCrossings_Many_ReturnsPluralWarning() var message = Assert.Single(warnings); Assert.Contains("3 connectors", message); } + + /// A null filter expression text produces no warnings. + [Fact] + public void ForUnevaluatedFilter_NullText_ReturnsEmpty() + { + Assert.Empty(LayoutWarnings.ForUnevaluatedFilter("View", null)); + } + + /// + /// A non-null filter expression text produces a single warning naming the view and stating + /// that the filter expression is parsed but not yet evaluated. + /// + [Fact] + public void ForUnevaluatedFilter_NonNullText_ReturnsNotYetEvaluatedWarning() + { + var warnings = LayoutWarnings.ForUnevaluatedFilter("MyView", "@SysML::PartUsage"); + + var message = Assert.Single(warnings); + Assert.Contains("MyView", message); + Assert.Contains("filter expression", message); + Assert.Contains("not yet evaluated", message); + } } diff --git a/test/DemaConsulting.SysML2Tools.Tests/Rendering/RenderingTests.cs b/test/DemaConsulting.SysML2Tools.Tests/Rendering/RenderingTests.cs index 1d86269a..e7f9ba88 100644 --- a/test/DemaConsulting.SysML2Tools.Tests/Rendering/RenderingTests.cs +++ b/test/DemaConsulting.SysML2Tools.Tests/Rendering/RenderingTests.cs @@ -8,6 +8,7 @@ using DemaConsulting.Rendering.Svg; using DemaConsulting.SysML2Tools.Rendering; using DemaConsulting.SysML2Tools.Semantic; +using DemaConsulting.SysML2Tools.Semantic.Model; namespace DemaConsulting.SysML2Tools.Tests.Rendering; @@ -262,5 +263,25 @@ public void ViewContext_Construction_StoresAllFields() // Assert: both fields equal the supplied values Assert.Equal("myView", context.ViewName); Assert.Same(workspace, context.Workspace); + Assert.Null(context.ViewNode); + } + + /// + /// Constructing a with an explicit ViewNode stores the + /// supplied node, giving layout strategies access to the view's resolved render/expose/ + /// filter data. + /// + [Fact] + public void ViewContext_Construction_WithViewNode_StoresViewNode() + { + // Arrange: a minimal SysmlWorkspace and a SysmlViewNode + var workspace = new SysmlWorkspace(); + var viewNode = new SysmlViewNode { Name = "V", QualifiedName = "P::V" }; + + // Act: construct a ViewContext with the view node supplied + var context = new ViewContext("myView", workspace, viewNode); + + // Assert: the supplied view node is stored unchanged + Assert.Same(viewNode, context.ViewNode); } } diff --git a/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs b/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs index 7bf4ff1d..51a8fa25 100644 --- a/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs +++ b/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs @@ -2394,6 +2394,203 @@ public async Task WorkspaceLoader_LoadAsync_DocumentationExampleFixture_Captures Assert.Equal(" This documentation of Automobile. ", automobileDoc.Text); } + /// + /// A view def with a render <target>; member whose target resolves + /// should be recorded as a + /// edge from the view to the resolved qualified name. + /// + [Fact] + public async Task WorkspaceLoader_LoadAsync_ViewRenderTargetResolves_RecordsRenderEdge() + { + // Arrange + var tempFile = Path.GetTempFileName() + ".sysml"; + try + { + await File.WriteAllTextAsync(tempFile, """ + package P { + part def Target {} + view def V { + render Target; + } + } + """, TestContext.Current.CancellationToken); + + // Act + var (stdlibTable, _) = StdlibProvider.GetSymbolTable(); + var result = await WorkspaceLoader.LoadAsync([tempFile], stdlibTable); + + // Assert + Assert.NotNull(result.Workspace); + Assert.Contains(result.Workspace!.Index.AllEdges, + e => e.Kind == DemaConsulting.SysML2Tools.Semantic.Model.SysmlEdgeKind.Render && + e.SourceQualifiedName == "P::V" && + e.TargetQualifiedName == "P::Target"); + } + finally + { + File.Delete(tempFile); + } + } + + /// + /// A view def with a render <target>; member whose target does NOT + /// resolve (e.g. a typo) must produce an unresolved-reference Warning diagnostic and must + /// not produce a Render edge — the exact fix for the reported bug (a bogus render + /// target previously rendered everything silently, with no signal to the user). + /// + [Fact] + public async Task WorkspaceLoader_LoadAsync_ViewRenderTargetUnresolved_ProducesWarningNoEdge() + { + // Arrange + var tempFile = Path.GetTempFileName() + ".sysml"; + try + { + await File.WriteAllTextAsync(tempFile, """ + package P { + view def V { + render thisIdentifierDoesNotExistAnywhere; + } + } + """, TestContext.Current.CancellationToken); + + // Act + var (stdlibTable, _) = StdlibProvider.GetSymbolTable(); + var result = await WorkspaceLoader.LoadAsync([tempFile], stdlibTable); + + // Assert + Assert.NotNull(result.Workspace); + Assert.Contains(result.Diagnostics, + d => d.Severity == DemaConsulting.SysML2Tools.Parser.DiagnosticSeverity.Warning && + d.Message.Contains("thisIdentifierDoesNotExistAnywhere")); + Assert.DoesNotContain(result.Workspace!.Index.AllEdges, + e => e.Kind == DemaConsulting.SysML2Tools.Semantic.Model.SysmlEdgeKind.Render); + } + finally + { + File.Delete(tempFile); + } + } + + /// + /// A view def with a filter [<expr>]; member should capture the raw + /// expression source text verbatim on + /// without evaluating it, producing no diagnostic and no edge (per binding decision: filter + /// expression evaluation is explicitly deferred future work). + /// + [Fact] + public async Task WorkspaceLoader_LoadAsync_ViewFilterExpression_CapturesTextVerbatimNoEdge() + { + // Arrange + var tempFile = Path.GetTempFileName() + ".sysml"; + try + { + await File.WriteAllTextAsync(tempFile, """ + package P { + view def V { + filter @SysML::PartUsage; + } + } + """, TestContext.Current.CancellationToken); + + // Act + var (stdlibTable, _) = StdlibProvider.GetSymbolTable(); + var result = await WorkspaceLoader.LoadAsync([tempFile], stdlibTable); + + // Assert + Assert.NotNull(result.Workspace); + var view = Assert.IsType( + result.Workspace!.Declarations["P::V"]); + Assert.Equal("@SysML::PartUsage", view.FilterExpressionText); + Assert.Empty(result.Diagnostics); + Assert.DoesNotContain(result.Workspace!.Index.AllEdges, + e => e.SourceQualifiedName == "P::V"); + } + finally + { + File.Delete(tempFile); + } + } + + /// + /// A named view usage (not a view def) with an expose <ns>; + /// member should build a + /// via AstBuilder.VisitViewUsage (the first test to exercise that visitor) and + /// resolve the exposed name into an + /// edge. + /// + [Fact] + public async Task WorkspaceLoader_LoadAsync_ViewUsageWithExpose_RecordsExposeEdge() + { + // Arrange + var tempFile = Path.GetTempFileName() + ".sysml"; + try + { + await File.WriteAllTextAsync(tempFile, """ + package P { + part def Exposed {} + view V { + expose Exposed; + } + } + """, TestContext.Current.CancellationToken); + + // Act + var (stdlibTable, _) = StdlibProvider.GetSymbolTable(); + var result = await WorkspaceLoader.LoadAsync([tempFile], stdlibTable); + + // Assert + Assert.NotNull(result.Workspace); + var view = Assert.IsType( + result.Workspace!.Declarations["P::V"]); + Assert.Contains("Exposed", view.ExposedNames); + Assert.Contains(result.Workspace!.Index.AllEdges, + e => e.Kind == DemaConsulting.SysML2Tools.Semantic.Model.SysmlEdgeKind.Expose && + e.SourceQualifiedName == "P::V" && + e.TargetQualifiedName == "P::Exposed"); + } + finally + { + File.Delete(tempFile); + } + } + + /// + /// A view def with an empty body should leave + /// and + /// null and + /// empty — a regression guard for the "no render statement → render everything" fallback. + /// + [Fact] + public async Task WorkspaceLoader_LoadAsync_ViewEmptyBody_AllNewFieldsNullOrEmpty() + { + // Arrange + var tempFile = Path.GetTempFileName() + ".sysml"; + try + { + await File.WriteAllTextAsync(tempFile, """ + package P { + view def V {} + } + """, TestContext.Current.CancellationToken); + + // Act + var (stdlibTable, _) = StdlibProvider.GetSymbolTable(); + var result = await WorkspaceLoader.LoadAsync([tempFile], stdlibTable); + + // Assert + Assert.NotNull(result.Workspace); + var view = Assert.IsType( + result.Workspace!.Declarations["P::V"]); + Assert.Null(view.RenderTargetName); + Assert.Null(view.FilterExpressionText); + Assert.Empty(view.ExposedNames); + } + finally + { + File.Delete(tempFile); + } + } + /// /// Finds the test/SysMLModels directory relative to the test assembly. /// diff --git a/test/DemaConsulting.SysML2Tools.Tool.Tests/Render/RenderSubsystemTests.cs b/test/DemaConsulting.SysML2Tools.Tool.Tests/Render/RenderSubsystemTests.cs index 3672f1bd..18b0fef2 100644 --- a/test/DemaConsulting.SysML2Tools.Tool.Tests/Render/RenderSubsystemTests.cs +++ b/test/DemaConsulting.SysML2Tools.Tool.Tests/Render/RenderSubsystemTests.cs @@ -532,6 +532,79 @@ public async Task RenderSubsystem_MultipleViews_WithViewFlag_RendersSelectedView } } + /// + /// End-to-end regression test for the reported bug: a workspace with two views — one + /// with a render <target>; body statement naming a resolvable target, and one + /// with a bogus render thisIdentifierDoesNotExistAnywhere; — must now produce two + /// visibly DIFFERENT rendered outputs (before this fix, every view rendered the identical + /// full-workspace diagram regardless of its render statement), and the bogus view's + /// unresolved render target must surface a diagnostic in the captured log output. + /// + [Fact] + public async Task RenderSubsystem_ViewsWithDistinctRenderTargets_ProduceDifferingOutputsAndDiagnostic() + { + // Arrange: a workspace with two named render targets in disjoint subtrees, one view + // whose render statement resolves to "TargetA", and one whose render statement names a + // nonexistent identifier. + const string sysmlWithRenderTargets = """ + package RenderScopeTest { + part def TargetA { + part childA1 : TargetA {} + } + part def TargetB { + part childB1 : TargetB {} + } + view def ViewValid { + render TargetA; + } + view def ViewBogus { + render thisIdentifierDoesNotExistAnywhere; + } + } + """; + + var tempDir = Path.Combine(Path.GetTempPath(), $"render_scope_bug_{Guid.NewGuid():N}"); + Directory.CreateDirectory(tempDir); + var tempFile = Path.Combine(tempDir, "model.sysml"); + await File.WriteAllTextAsync(tempFile, sysmlWithRenderTargets, TestContext.Current.CancellationToken); + + var outputDir = Path.Combine(tempDir, "out"); + var originalOut = Console.Out; + try + { + using var outWriter = new StringWriter(); + Console.SetOut(outWriter); + + // Act: render without --view so both views are rendered; capture output via --log + var logFile = Path.Combine(tempDir, "output.log"); + using (var context = Context.Create( + ["render", "--log", logFile, "--output", outputDir, tempFile])) + { + await Program.RunAsync(context); + + // Assert: exit code indicates success + Assert.Equal(0, context.ExitCode); + } + var validSvg = Path.Combine(outputDir, "ViewValid.svg"); + var bogusSvg = Path.Combine(outputDir, "ViewBogus.svg"); + Assert.True(File.Exists(validSvg), "Expected ViewValid.svg to be produced"); + Assert.True(File.Exists(bogusSvg), "Expected ViewBogus.svg to be produced"); + var validContent = await File.ReadAllTextAsync(validSvg, TestContext.Current.CancellationToken); + var bogusContent = await File.ReadAllTextAsync(bogusSvg, TestContext.Current.CancellationToken); + Assert.NotEqual(validContent, bogusContent); + + // Assert: the bogus render target surfaces a visible diagnostic naming the unresolved + // identifier, rather than silently rendering everything with no signal. + var logContent = await File.ReadAllTextAsync(logFile, TestContext.Current.CancellationToken); + Assert.Contains("thisIdentifierDoesNotExistAnywhere", logContent); + } + finally + { + Console.SetOut(originalOut); + Directory.Delete(tempDir, recursive: true); + } + } + /// /// 'render --help' now prints render-specific usage (a regression-proofing test for the /// command-aware help dispatch added alongside the 'help' command). From 8d4f00180b1ee12ecc1f40c2b8ffe51339e577fd Mon Sep 17 00:00:00 2001 From: Malcolm Nixon Date: Mon, 6 Jul 2026 10:55:40 -0400 Subject: [PATCH 2/6] fix(views): make `expose` the sole content-scoping mechanism, not `render` Corrects a design error in the prior view-scoped-rendering implementation. Per real SysML v2 grammar/semantics, a view's `render ;` member selects a rendering style/format (a `rendering` usage such as `asTreeDiagram`/`asElementTable`), never a content-scoping subject. The only real content-scoping mechanism a view has is `expose`. The previous implementation wrongly resolved `RenderTargetName` as a content reference, producing false "Unresolved reference" diagnostics against the OMG test corpus (e.g. `asTreeDiagram`, `asElementTable`) and silently rendering an empty diagram when a render target happened to resolve to a usage instead of a definition. Core fixes: - Remove `SysmlEdgeKind.Render` entirely; nothing produces it anymore. - `ReferenceResolver` no longer resolves `RenderTargetName` into an edge or diagnostic; it is captured as inert metadata only (reserved for a future rendering-style-selection feature). - `GeneralViewLayoutStrategy.ResolveSubjectScope` renamed to `ResolveExposedScope` and now scopes solely via resolved `Expose` edges. When a view has no `Expose` edges, it renders the full workspace, matching `main`'s pre-change behavior byte-for-byte. - Fixed the same usage-vs-definition containment gap for `expose`: when an exposed target resolves to a usage (`SysmlFeatureNode`), the scope additionally follows the usage's `Typing` edge to include its type definition's subtree, so `expose myVehicle;` now renders `Vehicle`'s full contents instead of an empty diagram. `VisitViewUsage` (making named `view Name { ... }` usages independently renderable, required for `expose` support) is kept and now explicitly documented/tested as an intentional capability addition: the OMG `11b-SafetyAndSecurityFeatureViews.sysml` corpus file now surfaces 5 renderable views instead of 2 (2 `view def`s + 3 named `view` usages). Companion artifacts updated: design docs, verification docs, reqstream requirements, ROADMAP.md (new deferred "render-style-selection" entry), README.md, and the user guide's "View Body Statements" section, plus test suites (GeneralViewLayoutStrategyTests, WorkspaceLoaderTests, RenderSubsystemTests) rewritten to remove wrong-premise render-scoping tests and add expose-scoping/regression coverage. `FilterExpressionText` capture/warning behavior is unchanged. Re-validated empirically against the OMG SysML test corpus: - No new unresolved-reference diagnostics for `asTreeDiagram`/ `asElementTable`/etc. when rendering `11b-SafetyAndSecurityFeatureViews.sysml` and `42.Views/ViewsExample.sysml`. - `render`-only view (no `expose`) now renders the full workspace, matching `main`, instead of an empty canvas. - `expose`-only view renders the exposed usage's type's full content. - OMG corpus view count increase (2 -> 5) confirmed deliberate, tested, and documented. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 8 +- ROADMAP.md | 60 ++++++--- .../internal/general-view-layout-strategy.md | 34 +++-- .../semantic/model/ast-builder.md | 12 ++ .../semantic/model/reference-resolver.md | 19 +-- .../semantic/model/sysml-edge.md | 15 +-- .../semantic/model/sysml-node.md | 24 ++-- .../general-view-layout-strategy.yaml | 53 ++++---- .../sysml2-tools-core/rendering.yaml | 11 +- .../semantic/model/ast-builder.yaml | 21 ++- .../semantic/model/reference-resolver.yaml | 34 ++--- .../semantic/model/sysml-node.yaml | 13 +- docs/user_guide/introduction.md | 120 +++++++++++++++--- .../internal/general-view-layout-strategy.md | 25 ++-- .../rendering/diagram-renderer.md | 6 +- .../semantic/model/ast-builder.md | 7 +- .../semantic/model/reference-resolver.md | 16 +-- .../semantic/model/sysml-edge.md | 8 +- .../semantic/model/sysml-node.md | 6 +- docs/verification/sysml2-tools-tool/render.md | 42 ++++-- .../Internal/GeneralViewLayoutStrategy.cs | 79 ++++++++---- .../Semantic/Model/ReferenceResolver.cs | 34 +---- .../Semantic/Model/SysmlEdge.cs | 21 +-- .../Semantic/Model/SysmlNode.cs | 12 +- .../Layout/GeneralViewLayoutStrategyTests.cs | 105 ++++++++------- .../Semantic/WorkspaceLoaderTests.cs | 61 ++------- .../Render/RenderSubsystemTests.cs | 119 ++++++++++++++--- 27 files changed, 599 insertions(+), 366 deletions(-) diff --git a/README.md b/README.md index aa315fed..21875a95 100644 --- a/README.md +++ b/README.md @@ -225,9 +225,11 @@ section above). | Multiple views, `--view ` | Render only the named view | | `--view ` names a view that does not exist | Error: lists available view names and exits non-zero | -A view's body `render ;` and `expose <...>;` statements now scope the rendered diagram -to that target's containment subtree (plus any exposed names), instead of always rendering the -full workspace — see the *Introduction* user guide for details. +A view's body `expose <...>;` statements now scope the rendered diagram to the union of the +exposed names' containment subtrees, instead of always rendering the full workspace; a view with +no `expose` statement continues to render the full workspace. `render ;` names a +rendering style (not yet honored) and has no effect on scope — see the *Introduction* user guide +for details. ## NuGet Packages diff --git a/ROADMAP.md b/ROADMAP.md index 94ca0d33..050b96a0 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -71,9 +71,9 @@ a decision/merge with correct shapes. ### View `filter [];` expression evaluation -`GeneralViewLayoutStrategy` now scopes a rendered diagram to a view's `render ;`/ -`expose <...>;` subject subtree, but a view's `filter [];` body statement is only parsed -into `SysmlViewNode.FilterExpressionText` (raw source text) — it is never evaluated, and a +`GeneralViewLayoutStrategy` now scopes a rendered diagram to a view's `expose <...>;` subject +subtree, but a view's `filter [];` body statement is only parsed into +`SysmlViewNode.FilterExpressionText` (raw source text) — it is never evaluated, and a layout warning ("parsed but not yet evaluated") is emitted in its place. SysML v2 view filtering is part of the standard's view/viewpoint mechanism for selectively including/excluding elements from a rendered diagram by predicate, beyond simple subject-subtree containment scoping (for @@ -84,37 +84,61 @@ the warning. - Design and implement an expression evaluator for the bracketed filter expression grammar (boolean/membership predicates over the resolved scope's elements), reusing/aligning with existing expression-parsing infrastructure where practical. -- Apply the evaluated predicate as an additional filter over the resolved (render/expose) - scope in `GeneralViewLayoutStrategy`, removing the "not yet evaluated" warning once a filter +- Apply the evaluated predicate as an additional filter over the resolved (expose) scope in + `GeneralViewLayoutStrategy`, removing the "not yet evaluated" warning once a filter expression is present and successfully evaluated. - Surface a diagnostic for filter expressions that fail to parse or evaluate, mirroring the - unresolved-render-target diagnostic pattern. + unresolved-reference diagnostic pattern already used for `expose`. **Scope:** `AstBuilder`/`SysmlViewNode` (expression AST capture, if warranted, beyond raw text); new expression-evaluation component; `GeneralViewLayoutStrategy` filter application. **Visual gate:** a view with a `filter [];` statement renders only the elements satisfying the predicate, with no "not yet evaluated" warning. -### Render-target scoping for the remaining layout strategies +### Expose-based scoping for the remaining layout strategies -`GeneralViewLayoutStrategy` implements render-target subject-scoping (containment-subtree -filtering driven by a view's `render`/`expose` body statements), but `InterconnectionView`, -`StateTransitionView`, `ActionFlowView`, `SequenceView`, `GridView`, and `BrowserView` layout -strategies do not yet honor `ViewContext.ViewNode`'s `Render`/`Expose` edges and continue to -render their full applicable scope regardless of a view's declared render target. +`GeneralViewLayoutStrategy` implements `expose`-based subject-scoping (containment-subtree +filtering driven by a view's `expose` body statements — the only content-scoping mechanism a +view has), but `InterconnectionView`, `StateTransitionView`, `ActionFlowView`, `SequenceView`, +`GridView`, and `BrowserView` layout strategies do not yet honor `ViewContext.ViewNode`'s +`Expose` edges and continue to render their full applicable scope regardless of a view's +declared `expose` statements. -- Extend the same `ResolveSubjectScope`/`IsInSubjectScope` containment-subtree idiom (or a +- Extend the same `ResolveExposedScope`/`IsInSubjectScope` containment-subtree idiom (or a shared helper extracted from `GeneralViewLayoutStrategy`) to each of the six remaining layout - strategies, respecting the "no `Render` edge → render everything unchanged" fallback used by + strategies, respecting the "no `Expose` edge → render everything unchanged" fallback used by `GeneralViewLayoutStrategy`. -- Add regression tests per strategy mirroring `GeneralViewLayoutStrategyTests`'s scoping, - expose-union, unresolved-fallback, and no-render-statement-regression scenarios. +- Add regression tests per strategy mirroring `GeneralViewLayoutStrategyTests`'s expose-scoping, + expose-union, and no-expose-statement-regression scenarios. **Scope:** `InterconnectionViewLayoutStrategy`, `StateTransitionViewLayoutStrategy`, `ActionFlowViewLayoutStrategy`, `SequenceViewLayoutStrategy`, `GridViewLayoutStrategy`, `BrowserViewLayoutStrategy`; corresponding test files. -**Visual gate:** each of the six views renders a scoped diagram when its view declares a -`render ;` statement naming a resolvable target, unchanged when it does not. +**Visual gate:** each of the six views renders a scoped diagram when its view declares an +`expose <...>;` statement naming a resolvable target, unchanged when it does not. + +### Support selecting rendering style via `render ;` + +A view's `render ;` member names a rendering style/format usage per the SysML v2 +grammar (e.g. `asTreeDiagram`, `asElementTable`, `asTextualNotation`, `asTextualNotationTable` — +`rendering` usages, a distinct usage/definition kind) — it is captured verbatim on +`SysmlViewNode.RenderTargetName` but currently has no effect on rendering; every view renders +through the single `DiagramTypeRouter` → `ILayoutStrategy` selection regardless of its declared +`render` member. This is a distinct future capability from content scoping, which is `expose`'s +exclusive role. + +- Design a mapping from recognized rendering-style names (`asTreeDiagram`, `asElementTable`, + and so on, once corresponding layout/rendering strategies exist) to an `ILayoutStrategy`/ + renderer selection, honored when a view declares a `render` member naming one. +- Leave `RenderTargetName` un-honored (as today) for rendering-style names with no corresponding + strategy, with no diagnostic — an unrecognized rendering-style name is not an error, since + `render` selects presentation, not content. + +**Scope:** `DiagramTypeRouter`/`RenderCommand` (rendering-style selection); new layout/rendering +strategies for tree-diagram/element-table/textual-notation styles, if not already covered by an +existing strategy. +**Visual gate:** a view declaring `render asTreeDiagram;` (once a tree-diagram strategy exists) +renders using that style instead of the default `GeneralView` layout. --- diff --git a/docs/design/sysml2-tools-core/layout/internal/general-view-layout-strategy.md b/docs/design/sysml2-tools-core/layout/internal/general-view-layout-strategy.md index fefd3938..d965467a 100644 --- a/docs/design/sysml2-tools-core/layout/internal/general-view-layout-strategy.md +++ b/docs/design/sysml2-tools-core/layout/internal/general-view-layout-strategy.md @@ -34,7 +34,7 @@ from the structural relationships. ###### `BuildLayout(ViewContext context, RenderOptions options)` -Entry point. First resolves the view's render-target subject scope via `ResolveSubjectScope`, +Entry point. First resolves the view's exposed-name scope via `ResolveExposedScope`, then calls `CollectDefinitions` to gather user definitions restricted to that scope (or every definition when no scope applies); returns a minimal 200×100 empty `LayoutTree` when none are found. Otherwise groups the definitions by package with `GroupByPackage`, resolves the @@ -50,23 +50,29 @@ box. Finally, when `context.ViewNode?.FilterExpressionText` is non-null, attache tree's `Warnings` via the `LayoutTree with { Warnings = … }` record-copy idiom, leaving the resolved (unfiltered) scope's content unchanged. -###### `ResolveSubjectScope(SysmlViewNode? viewNode)` - -Resolves the render-target subject scope a view's `render`/`expose` statements restrict the -diagram to. Returns `null` — meaning "render everything", byte-identical to the pre-scoping -behavior — in three equivalent cases: a `null` `viewNode` (the `--auto` synthesized view, which -never carries render/expose/filter data), a view with no `render` statement, and a view whose -`render` target failed to resolve (`ReferenceResolver` never adds a `Render` edge for an -unresolvable target; the diagnostic informing the user is emitted there, not here). When a -`Render` edge is present, returns a list starting with its resolved target qualified name, -followed by the resolved target qualified name of every `Expose` edge on the same view — i.e. the -render subject plus each exposed name, additively. +###### `ResolveExposedScope(SysmlWorkspace workspace, SysmlViewNode? viewNode)` + +Resolves the qualified-name containment-subtree scope a view's `expose` statements restrict the +diagram to — the only content-scoping mechanism a view has (`render ;` names a +rendering style/format, e.g. `asTreeDiagram`/`asElementTable`, per the SysML v2 grammar, never +content, so `RenderTargetName` never affects this decision). Returns `null` — meaning "render +everything", byte-identical to the pre-scoping behavior — whenever the view has no resolved +`Expose`-kind `ResolvedEdges` entries: covering a `null` `viewNode` (the `--auto` synthesized +view, which never carries expose/render/filter data), a view with no `expose` statement, and a +view whose every `expose` entry failed to resolve, uniformly. Otherwise, for each resolved +`Expose` edge's target qualified name, adds that name to the scope; when +`workspace.Declarations` resolves the target to a `SysmlFeatureNode` (a usage, e.g. +`part myVehicle : Vehicle;`) rather than a `SysmlDefinitionNode`, additionally resolves the +usage's own `Typing`-kind `ResolvedEdges` entry (if any) and adds *that* type's qualified name to +the scope too — the usage-to-type resolution fix for the containment gap where a usage's own +(typically empty) subtree would otherwise silently produce zero content. ###### `IsInSubjectScope(qualifiedName, subjects)` Returns `true` when `qualifiedName` equals one of `subjects` or lies within one of their containment subtrees (a `"{subject}::"` prefix match) — the same qualified-name-prefix idiom -`StdlibFilter.IsStdlibElement` already uses for stdlib-prefix matching. +`StdlibFilter.IsStdlibElement` already uses for stdlib-prefix matching. Generic over any subject +list; unchanged by this fix. ###### `CollectDefinitions(workspace, theme, scope)` @@ -152,7 +158,7 @@ produces valid geometry, so no crossing warnings are emitted. - `StdlibFilter` (Rendering Internal subsystem) — standard-library exclusion. - `SysmlWorkspace`, `SysmlDefinitionNode`, `SysmlFeatureNode` (Semantic subsystem) — model input. - `SysmlViewNode`, `SysmlEdge`, `SysmlEdgeKind` (Semantic subsystem) — a view's resolved - render/expose data, read by `ResolveSubjectScope`. + `expose` data, read by `ResolveExposedScope`. - `LayoutWarnings` (Layout Internal subsystem) — `ForUnevaluatedFilter` supplies the "parsed but not yet evaluated" filter-expression warning text. - The `LayoutTree`, `LayoutBox`, `LayoutCompartment`, `LayoutLine`, `LayoutLabel`, and `Point2D` data diff --git a/docs/design/sysml2-tools-language/semantic/model/ast-builder.md b/docs/design/sysml2-tools-language/semantic/model/ast-builder.md index 2454b869..323c2cc6 100644 --- a/docs/design/sysml2-tools-language/semantic/model/ast-builder.md +++ b/docs/design/sysml2-tools-language/semantic/model/ast-builder.md @@ -97,6 +97,18 @@ only body form that may additionally contain `expose` members) the same way, plu `ExtractExposedNames` to populate `ExposedNames`. Unnamed view usages are skipped (no declared name), mirroring the existing anonymous-element convention. +**`VisitViewUsage` is an intentional capability addition, not merely an `expose`-capture +prerequisite.** Before this override existed, named `view Name { ... }` usages were silently +dropped by the default `VisitChildren` aggregation — only `view def` declarations were ever +visible as renderable top-level declarations. Adding `VisitViewUsage` means every named `view` +usage in a workspace (whether or not it declares `expose`) now becomes its own `SysmlViewNode` +declaration that the render subsystem discovers and renders. This is a deliberate, documented +increase in output surface area: for example, the OMG corpus fixture +`test/SysMLModels/OMG/validation/11-ViewAndViewpoint/11b-SafetyAndSecurityFeatureViews.sysml` +declares 2 `view def`s plus 3 named `view` usages, so rendering it with no `--view` filter now +produces 5 output files instead of 2 (see +`RenderSubsystemTests.RenderSubsystem_OmgSafetyFeatureViewsCorpus_RendersAllNamedViewUsages`). + `ExtractViewRenderAndFilter(IEnumerable bodyItems)` is a single generic helper shared by both `VisitViewDefinition` (`ViewDefinitionBodyItemContext`) and `VisitViewUsage` (`ViewBodyItemContext`) — the two context types are unrelated in the generated parser's type diff --git a/docs/design/sysml2-tools-language/semantic/model/reference-resolver.md b/docs/design/sysml2-tools-language/semantic/model/reference-resolver.md index df363273..d0c080cd 100644 --- a/docs/design/sysml2-tools-language/semantic/model/reference-resolver.md +++ b/docs/design/sysml2-tools-language/semantic/model/reference-resolver.md @@ -132,17 +132,12 @@ no edge): = resolved first end, `Target` = resolved second end) only when both ends resolve, using the identical both-sides-must-resolve contract as `Satisfy`. Regular `"connection"`/`"message"` keyword variants remain intentionally unresolved (out of scope for this unit). -- **`SysmlViewNode` (Render/Expose)** — resolves `RenderTargetName` and each `ExposedNames` - entry **independently of one another** (unlike `Satisfy`/`Allocate`'s two-sided contract, - since `render`/`expose` are functionally unrelated statements): a resolved `RenderTargetName` - produces one `SysmlEdgeKind.Render` edge sourced from `node.QualifiedName`, and each resolved - `ExposedNames` entry produces its own `SysmlEdgeKind.Expose` edge, independently. An - unresolvable `RenderTargetName` (e.g. a typo) produces only the standard unresolved-reference - Warning and no `Render` edge — `GeneralViewLayoutStrategy` observes the resulting absence of a - `Render` edge and falls back to rendering the full workspace, the same observable state as a - view with no `render` statement at all. This is the fix for the reported bug: a bogus render - target now surfaces a visible diagnostic instead of silently rendering everything. - `FilterExpressionText` is raw source text, not a reference, and is never inspected here. +- **`SysmlViewNode` (Expose)** — resolves each `ExposedNames` entry into its own + `SysmlEdgeKind.Expose` edge, or the standard unresolved-reference Warning diagnostic when it + does not resolve. `RenderTargetName` names a rendering style/format (e.g. `asTreeDiagram`, + `asElementTable`) per the SysML v2 grammar — never model content — so `ReferenceResolver` + never inspects it: no edge is produced and no diagnostic is emitted for it, exactly mirroring + how `FilterExpressionText` (raw source text, not a reference) is also never inspected here. ##### Deviations From Uniform Resolution (Behavior-Neutral Additive Fixes) @@ -190,7 +185,7 @@ unresolved names are present. (`SubjectName`/`RequirementName`), `SysmlConnectionNode` with `ConnectionKeyword == "allocation"` (`EndpointA`/`EndpointB`), `SysmlConnectionNode` with `ConnectionKeyword == "connection"` or `"message"`, `SysmlTransitionNode` (`Source`/`Target`), and `SysmlViewNode` - (`RenderTargetName`/`ExposedNames`; `FilterExpressionText` is never read); reads + (`ExposedNames`; `RenderTargetName`/`FilterExpressionText` are never read); reads `ResolvedEdges` (`Typing`/`Supertype` kinds) during feature-chain resolution. - `SysmlEdge`, `SemanticIndex` — resolved references are recorded as `SysmlEdge` instances and aggregated into the returned `SemanticIndex`. diff --git a/docs/design/sysml2-tools-language/semantic/model/sysml-edge.md b/docs/design/sysml2-tools-language/semantic/model/sysml-edge.md index a9218595..dd6b3f98 100644 --- a/docs/design/sysml2-tools-language/semantic/model/sysml-edge.md +++ b/docs/design/sysml2-tools-language/semantic/model/sysml-edge.md @@ -33,17 +33,14 @@ render, and expose references, and are the raw material indexed by `SemanticInde from the source state and targeting the target state (`SysmlTransitionNode`). Either side may be a dotted feature chain, resolved the same way as `Connect`; recorded only when both the source and target resolve — an implied/omitted source produces no edge. -- `Render` — a view's resolved render-target reference (`render ;`), sourced from the - view's qualified name and targeting the resolved `SysmlViewNode.RenderTargetName`. - `GeneralViewLayoutStrategy` scopes its diagram to this target's containment subtree when - present; an unresolvable render target produces no `Render` edge (only the standard - unresolved-reference diagnostic), and the layout strategy falls back to rendering the full - workspace for that view. - `Expose` — a view usage's resolved exposed-name reference (`expose ;`, valid only inside a `view` usage's body), sourced from the view's qualified name and targeting each resolvable - entry in `SysmlViewNode.ExposedNames`, one `Expose` edge per entry. `GeneralViewLayoutStrategy` - additively unions each exposed name's containment subtree on top of the `Render` subject's - subtree. + entry in `SysmlViewNode.ExposedNames`, one `Expose` edge per entry. `Expose` is the sole + view-scoping edge kind: `GeneralViewLayoutStrategy` scopes its diagram to the union of every + `Expose` edge's target containment subtree; a view with no `Expose` edges renders the full + workspace, unchanged from the pre-scoping baseline. `SysmlViewNode.RenderTargetName` (a + rendering-style/format selector, e.g. `asTreeDiagram`) is captured but never resolved into an + edge and has no effect on scope. `SysmlEdge` is a sealed positional record with three properties: diff --git a/docs/design/sysml2-tools-language/semantic/model/sysml-node.md b/docs/design/sysml2-tools-language/semantic/model/sysml-node.md index 3f86e268..d0fbca28 100644 --- a/docs/design/sysml2-tools-language/semantic/model/sysml-node.md +++ b/docs/design/sysml2-tools-language/semantic/model/sysml-node.md @@ -97,17 +97,22 @@ There are no behavioral methods beyond the inherited `object` members. `SysmlImp `SysmlViewNode` adds: - `RenderTargetName` — the raw reference text of the view's `render ;` member (the - first one found if more than one appears), or null when the view has no `render` member. - Extracted by the shared `AstBuilder.ExtractRenderTargetName` helper, which follows the same - two-form fallback pattern (direct reference, then typed placeholder) `VisitSatisfyRequirementUsage` - already uses. Resolved by `ReferenceResolver` into a `SysmlEdgeKind.Render` edge, or an - unresolved-reference diagnostic (and no edge) when the target does not resolve. + first one found if more than one appears), or null when the view has no `render` member. Per + the SysML v2 grammar this names a rendering style/format usage (e.g. `asTreeDiagram`, + `asElementTable`) — never a content-scoping subject. Extracted by the shared + `AstBuilder.ExtractRenderTargetName` helper, which follows the same two-form fallback pattern + (direct reference, then typed placeholder) `VisitSatisfyRequirementUsage` already uses. + Captured verbatim only: `ReferenceResolver` never inspects or resolves this value (no edge is + produced, no diagnostic is emitted), and it has no effect on `GeneralViewLayoutStrategy`'s + rendered scope. Reserved for a possible future capability that selects among rendering-style + strategies — see the project ROADMAP. - `ExposedNames` — the raw reference text of each `expose ;` member in a `view` usage's body, in source order, or empty when none are present (and always empty for a `view def` definition — `expose` is only valid grammar inside a `view` usage's body). Extracted by `AstBuilder.ExtractExposedNames`, sharing the same `ExtractImportTarget` helper `VisitImportRule` uses for plain `import`. Each entry is independently resolved by `ReferenceResolver` into a - `SysmlEdgeKind.Expose` edge, or an unresolved-reference diagnostic (and no edge) for that entry. + `SysmlEdgeKind.Expose` edge, or an unresolved-reference diagnostic (and no edge) for that + entry. This is the sole field `GeneralViewLayoutStrategy` uses to scope a rendered diagram. - `FilterExpressionText` — the raw source text of the view's `filter [];` member's bracketed expression, or null when absent. Captured verbatim by `AstBuilder` (`elementFilterMember().ownedExpression().GetText()`) and never evaluated or inspected by @@ -141,7 +146,8 @@ elements are filtered out by `AstBuilder` before a node is constructed. `VerifiedRequirementNames`, `Children`; checks for `SysmlImportNode`, `SysmlSatisfyNode`, the `"allocation"` `SysmlConnectionNode` variant, the `"connection"`/`"message"` `SysmlConnectionNode` variants, `SysmlTransitionNode`, and `SysmlViewNode` (reading - `RenderTargetName`/`ExposedNames`, never `FilterExpressionText`); writes `ResolvedEdges` after - resolving references (in two passes — supertype/typing/import/satisfy/verify/allocate/render/ - expose, then feature-chain connect/transition). + `ExposedNames`; `RenderTargetName`/`FilterExpressionText` are never read); writes + `ResolvedEdges` after resolving references (in two passes — + supertype/typing/import/satisfy/verify/allocate/expose, then feature-chain + connect/transition). - `SupertypeWalker` — reads `SupertypeNames` on each node retrieved from `SymbolTable`. diff --git a/docs/reqstream/sysml2-tools-core/layout/internal/general-view-layout-strategy.yaml b/docs/reqstream/sysml2-tools-core/layout/internal/general-view-layout-strategy.yaml index 312804e3..9eb27212 100644 --- a/docs/reqstream/sysml2-tools-core/layout/internal/general-view-layout-strategy.yaml +++ b/docs/reqstream/sysml2-tools-core/layout/internal/general-view-layout-strategy.yaml @@ -151,39 +151,42 @@ sections: - GeneralViewLayoutStrategy_BuildLayout_HeatLayout_ConnectedModelKeepsBoxesSeparated - GeneralViewLayoutStrategy_BuildLayout_HeatLayout_SparseModelProducesCompactCanvas - - id: SysML2Tools-Core-Layout-Internal-GeneralViewLayoutStrategy-RenderTargetScoping + - id: SysML2Tools-Core-Layout-Internal-GeneralViewLayoutStrategy-ExposeScoping title: >- - When a view's ViewContext carries a resolved Render edge to a qualified name Q, - GeneralViewLayoutStrategy shall scope the diagram to Q and every declaration whose - qualified name is Q or has "Q::" as a prefix (Q's containment subtree), additionally - merging in the containment subtree of every resolved Expose edge target, combined - with Q's subtree. + When a view's ViewContext carries one or more resolved Expose edges, + GeneralViewLayoutStrategy shall scope the diagram to the union of each edge's target + containment subtree, resolved through the target's own type reference when the target + is a usage rather than a definition. justification: | - Every declared view previously rendered the identical full-workspace "GeneralView" - diagram regardless of its `render ;`/`expose <...>;` body statements — the - reported bug. Scoping the diagram to the render target's (and any exposed names') - containment subtree makes those statements finally have an observable effect, matching - the SysML v2 view/viewpoint mechanism's subject-scoping intent. + `expose ;` is the only real content-scoping mechanism a view has in SysML v2 — + `render ;` names a rendering style/format (e.g. `asTreeDiagram`, + `asElementTable`), never content, so it plays no role in scoping. Scoping the diagram + to the union of every resolved `expose` target's containment subtree makes `expose` + statements finally have an observable effect, matching the SysML v2 view mechanism's + subject-scoping intent. When an exposed target resolves to a feature usage rather than + a definition, its own containment subtree is typically empty — the real content lives + under its type's subtree — so the target's own resolved type reference is additionally + followed to include that type's subtree, fixing the usage-vs-definition containment gap. tests: - - GeneralViewLayoutStrategy_BuildLayout_ResolvedRenderTarget_ScopesToSubtreeFewerBoxes - GeneralViewLayoutStrategy_BuildLayout_ExposedName_UnionsAdditionalSubtree + - GeneralViewLayoutStrategy_BuildLayout_ExposedUsage_ResolvesThroughTypingToDefinitionSubtree - - id: SysML2Tools-Core-Layout-Internal-GeneralViewLayoutStrategy-UnresolvedRenderFallback + - id: SysML2Tools-Core-Layout-Internal-GeneralViewLayoutStrategy-NoExposeFallback title: >- - When a view's ViewContext carries no resolved Render edge — because the view has no - `render` statement, its ViewNode is null (e.g. the `--auto` synthesized view), or its - declared render target failed to resolve — GeneralViewLayoutStrategy shall render the - full workspace, unchanged from its pre-scoping behavior. + When a view's ViewContext carries no resolved Expose edge — because the view has no + `expose` statement, its ViewNode is null (e.g. the `--auto` synthesized view), or its + every declared `expose` entry failed to resolve — GeneralViewLayoutStrategy shall + render the full workspace, unchanged from its pre-scoping behavior, regardless of + whether `RenderTargetName`/`FilterExpressionText` are present. justification: | - An unresolved `render ;` (e.g. a typo) must not produce a broken or empty - diagram; falling back to full-workspace rendering (with the unresolved-reference - diagnostic already surfaced by ReferenceResolver) preserves today's behavior as a safe - default while still giving the user a visible signal elsewhere in the diagnostics - output. Views with no `render` statement at all — including the `--auto` synthesized - view, which never carries render/expose/filter data — must continue to render - everything exactly as before this change, with zero regression risk. + Nearly every real-world view declares no `expose` statement at all, so rendering the + full workspace must remain the default. This also guards against a bogus `expose ;` + silently rendering nothing: an unresolvable exposed name (e.g. a typo) falls back to + full-workspace rendering (with the unresolved-reference diagnostic already surfaced by + `ReferenceResolver`) rather than producing a broken or empty diagram. `RenderTargetName` + (a rendering-style/format selector, not content) never affects this decision. tests: - - GeneralViewLayoutStrategy_BuildLayout_UnresolvedRenderTarget_FallsBackToFullWorkspace + - GeneralViewLayoutStrategy_BuildLayout_RenderTargetNameOnly_NoExposeEdges_RendersFullWorkspace - GeneralViewLayoutStrategy_BuildLayout_NullViewNode_RendersFullWorkspaceUnchanged - id: SysML2Tools-Core-Layout-Internal-GeneralViewLayoutStrategy-UnevaluatedFilterWarning diff --git a/docs/reqstream/sysml2-tools-core/rendering.yaml b/docs/reqstream/sysml2-tools-core/rendering.yaml index aed9b105..51cba53a 100644 --- a/docs/reqstream/sysml2-tools-core/rendering.yaml +++ b/docs/reqstream/sysml2-tools-core/rendering.yaml @@ -108,14 +108,13 @@ sections: resolved AST node, so that a layout strategy can access the view's declared render target, exposed names, and filter expression when computing its layout. justification: | - Every declared view previously rendered the identical full-workspace diagram - because no layout strategy had access to a view's declared `render`/`expose`/ - `filter` body statements at all — only its name and the whole workspace. Threading - the view's AST node through DiagramRenderer to each ILayoutStrategy is the - plumbing prerequisite for GeneralViewLayoutStrategy's subject-scoped rendering. + A layout strategy needs access to a view's declared `render`/`expose`/`filter` body + statements — not just its name and the whole workspace — to scope its diagram. + Threading the view's AST node through DiagramRenderer to each ILayoutStrategy is the + plumbing prerequisite for GeneralViewLayoutStrategy's `expose`-scoped rendering. tests: - ViewContext_Construction_WithViewNode_StoresViewNode - - GeneralViewLayoutStrategy_BuildLayout_ResolvedRenderTarget_ScopesToSubtreeFewerBoxes + - GeneralViewLayoutStrategy_BuildLayout_ExposedName_UnionsAdditionalSubtree - GeneralViewLayoutStrategy_BuildLayout_NullViewNode_RendersFullWorkspaceUnchanged - id: SysML2Tools-Core-Rendering-DiagramRenderer diff --git a/docs/reqstream/sysml2-tools-language/semantic/model/ast-builder.yaml b/docs/reqstream/sysml2-tools-language/semantic/model/ast-builder.yaml index 21fc97b8..ab9a7bbc 100644 --- a/docs/reqstream/sysml2-tools-language/semantic/model/ast-builder.yaml +++ b/docs/reqstream/sysml2-tools-language/semantic/model/ast-builder.yaml @@ -47,13 +47,12 @@ sections: `expose ;` members, `view` usages only) as raw, unevaluated data on the corresponding SysmlViewNode. justification: | - Every declared view previously rendered the identical full-workspace diagram because - none of its `render`/`expose`/`filter` body statements were captured at all. Capturing - this data during AST construction is the prerequisite for GeneralViewLayoutStrategy to - scope a view's diagram to its declared subject, and for ReferenceResolver to resolve - the render/expose references into edges. + Capturing this data during AST construction is the prerequisite for ReferenceResolver + to resolve the `expose` references into edges that GeneralViewLayoutStrategy uses for + scoping. `RenderTargetName` names a rendering style/format, not content, so it is + captured verbatim only and never drives scoping or resolution. tests: - - WorkspaceLoader_LoadAsync_ViewRenderTargetResolves_RecordsRenderEdge + - WorkspaceLoader_LoadAsync_ViewRenderTarget_CapturedRawNeverResolvedNoDiagnostic - WorkspaceLoader_LoadAsync_ViewFilterExpression_CapturesTextVerbatimNoEdge - WorkspaceLoader_LoadAsync_ViewEmptyBody_AllNewFieldsNullOrEmpty @@ -66,6 +65,14 @@ sections: `expose ;` is only valid grammar inside a `view` usage's body, not a `view def` definition's body; without a dedicated VisitViewUsage override, named view usages (including every `expose` statement) were never visited at all, so a view's exposed - names could never be captured, resolved, or scoped. + names could never be captured, resolved, or scoped. This is also an intentional + capability addition beyond `expose` support alone: `VisitViewUsage` causes every named + `view` usage — whether or not it declares `expose` — to become its own renderable + top-level `SysmlViewNode` declaration, whereas previously only `view def` declarations + were ever visible to the render subsystem. For example, the OMG corpus fixture + `11b-SafetyAndSecurityFeatureViews.sysml` declares 2 `view def`s plus 3 named `view` + usages, so rendering it with no `--view` filter now produces 5 output files instead + of 2. tests: - WorkspaceLoader_LoadAsync_ViewUsageWithExpose_RecordsExposeEdge + - RenderSubsystem_OmgSafetyFeatureViewsCorpus_RendersAllNamedViewUsages diff --git a/docs/reqstream/sysml2-tools-language/semantic/model/reference-resolver.yaml b/docs/reqstream/sysml2-tools-language/semantic/model/reference-resolver.yaml index db4ca962..ce6cf30c 100644 --- a/docs/reqstream/sysml2-tools-language/semantic/model/reference-resolver.yaml +++ b/docs/reqstream/sysml2-tools-language/semantic/model/reference-resolver.yaml @@ -27,22 +27,26 @@ sections: tests: - WorkspaceLoader_LoadAsync_CircularImport_ProducesWarningNoInfiniteLoop - - id: SysML2Tools-Language-Semantic-Model-ReferenceResolver-ViewRenderExpose + - id: SysML2Tools-Language-Semantic-Model-ReferenceResolver-ViewExpose title: >- - ReferenceResolver.ResolveAll shall independently resolve a view's RenderTargetName - into a Render-kind edge and each of its ExposedNames into an Expose-kind edge, and - shall produce an unresolved-reference Warning diagnostic (with no corresponding edge) - for either when it cannot resolve — mirroring the existing SubjectName/EndpointA - unresolved-reference diagnostic pattern. FilterExpressionText is raw source text, not - a reference, and is never inspected here. + ReferenceResolver.ResolveAll shall resolve each of a view's ExposedNames into an + Expose-kind edge, and shall produce an unresolved-reference Warning diagnostic (with no + corresponding edge) when an entry cannot resolve — mirroring the existing + SubjectName/EndpointA unresolved-reference diagnostic pattern. RenderTargetName names a + rendering style/format per the SysML v2 grammar (e.g. `asTreeDiagram`, + `asElementTable`), never model content, and is raw metadata that ReferenceResolver never + inspects: no edge is produced and no diagnostic is emitted for it. FilterExpressionText + is likewise raw source text, not a reference, and is never inspected here. justification: | - Before this change, an unresolved `render ;` (e.g. a typo) silently rendered - the full workspace with no diagnostic at all, indistinguishable from a view with no - `render` statement — the exact bug being fixed. Surfacing the same unresolved-reference - diagnostic already used for satisfy/allocate gives the user a visible signal instead of - silent full-workspace rendering. + `expose ;` is the only reference a view's body statements resolve into model + content; a `render ;` member never refers to content per the SysML v2 grammar + (confirmed against the OMG test corpus, where every `render` member names a + rendering-style identifier such as `asTreeDiagram`), so attempting to resolve it as a + reference could only ever produce a false "Unresolved reference" diagnostic or + coincidentally match an unrelated stdlib symbol of the same name. Restricting resolution + to ExposedNames removes that false-diagnostic risk while preserving the genuine + unresolved-reference signal for a bogus `expose` entry. tests: - - WorkspaceLoader_LoadAsync_ViewRenderTargetResolves_RecordsRenderEdge - - WorkspaceLoader_LoadAsync_ViewRenderTargetUnresolved_ProducesWarningNoEdge + - WorkspaceLoader_LoadAsync_ViewRenderTarget_CapturedRawNeverResolvedNoDiagnostic - WorkspaceLoader_LoadAsync_ViewUsageWithExpose_RecordsExposeEdge - - RenderSubsystem_ViewsWithDistinctRenderTargets_ProduceDifferingOutputsAndDiagnostic + - RenderSubsystem_ViewsWithDistinctExposeTargets_ProduceDifferingOutputsAndDiagnostic diff --git a/docs/reqstream/sysml2-tools-language/semantic/model/sysml-node.yaml b/docs/reqstream/sysml2-tools-language/semantic/model/sysml-node.yaml index 09394f94..5d1988ff 100644 --- a/docs/reqstream/sysml2-tools-language/semantic/model/sysml-node.yaml +++ b/docs/reqstream/sysml2-tools-language/semantic/model/sysml-node.yaml @@ -36,13 +36,14 @@ sections: each `expose ;` member, empty when absent), and FilterExpressionText (the raw source text of its `filter [];` member, or null when absent). justification: | - Every declared view previously rendered the identical full-workspace diagram: none of - its `render`/`expose`/`filter` body statements were observable anywhere in the AST, so - no downstream component (ReferenceResolver, GeneralViewLayoutStrategy) could act on - them. These three fields make a view's declared rendering intent user-observable data, - not merely an internal implementation detail. + These three fields make a view's declared body statements user-observable data, not + merely an internal implementation detail. RenderTargetName is exposed as raw metadata + only: per the SysML v2 grammar it names a rendering style/format (e.g. + `asTreeDiagram`, `asElementTable`), never content, so it is never resolved into an edge + and has no effect on scope. ExposedNames is the field ReferenceResolver resolves into + `Expose`-kind edges that GeneralViewLayoutStrategy uses for scoping. tests: - - WorkspaceLoader_LoadAsync_ViewRenderTargetResolves_RecordsRenderEdge + - WorkspaceLoader_LoadAsync_ViewRenderTarget_CapturedRawNeverResolvedNoDiagnostic - WorkspaceLoader_LoadAsync_ViewFilterExpression_CapturesTextVerbatimNoEdge - WorkspaceLoader_LoadAsync_ViewUsageWithExpose_RecordsExposeEdge - WorkspaceLoader_LoadAsync_ViewEmptyBody_AllNewFieldsNullOrEmpty diff --git a/docs/user_guide/introduction.md b/docs/user_guide/introduction.md index 95c329d7..846be3bc 100644 --- a/docs/user_guide/introduction.md +++ b/docs/user_guide/introduction.md @@ -112,28 +112,112 @@ sysml2tools render model.sysml --auto --output out --format svg A `view def`/`view` declaration's body may contain `render ;`, `expose ;`, and `filter [];` statements. For the General View strategy (the diagram produced when no -more specialized view kind applies), these statements now scope the rendered diagram instead -of always rendering the entire workspace: - -- `render ;` — scopes the diagram to `` plus every declaration whose qualified - name is `` or is contained within it (a containment-subtree match, not just the exact - element). If `` does not resolve to any declaration in the workspace (for example, a - typo), the tool falls back to rendering the full workspace for that view — but now also - reports a diagnostic identifying the unresolved name, so the mistake is visible instead of - silently rendering everything with no signal. -- `expose ;` — additively includes ``'s containment subtree alongside the `render` - target's subtree. Like `render`, an unresolved `expose` name produces a diagnostic. +more specialized view kind applies), `expose` now scopes the rendered diagram instead of always +rendering the entire workspace: + +- `expose ;` (valid only inside a named `view` usage's body, not a `view def` + definition's body) — scopes the diagram to the union of every exposed name's containment + subtree: `` plus every declaration whose qualified name is `` or is contained + within it (a containment-subtree match, not just the exact element). If `` does not + resolve to any declaration in the workspace (for example, a typo), the tool falls back to + rendering the full workspace for that view — but now also reports a diagnostic identifying the + unresolved name, so the mistake is visible instead of silently rendering everything with no + signal. +- `render ;` — per the SysML v2 grammar, this names a rendering style/format (e.g. + `asTreeDiagram`, `asElementTable`) rather than content. It is captured but currently has **no + effect** on the rendered scope — see `ROADMAP.md` for the planned future capability to honor + it as a rendering-style selector. - `filter [];` — the bracketed filter expression is parsed and captured, but **not yet - evaluated**: the resolved (render/expose) scope is rendered unfiltered, and a diagnostic - reports that the filter expression was parsed but not yet evaluated. Full filter-expression + evaluated**: the resolved (`expose`) scope is rendered unfiltered, and a diagnostic reports + that the filter expression was parsed but not yet evaluated. Full filter-expression evaluation is planned future work — see `ROADMAP.md`. -- A view with **no** `render` statement (including the `--auto`-synthesized view) renders the +- A view with **no** `expose` statement (including the `--auto`-synthesized view) renders the full workspace, exactly as before this scoping behavior was introduced. -Only the General View layout strategy honors `render`/`expose` scoping in this release; the -other view kinds (Interconnection, State Transition, Action Flow, Sequence, Grid, Browser) -continue to render their full applicable scope regardless of a view's declared render target — -see `ROADMAP.md` for the planned follow-up extending scoping to those view kinds. +Only the General View layout strategy honors `expose` scoping in this release; the other view +kinds (Interconnection, State Transition, Action Flow, Sequence, Grid, Browser) continue to +render their full applicable scope regardless of a view's declared `expose` statements — see +`ROADMAP.md` for the planned follow-up extending scoping to those view kinds. + +Named `view Name { ... }` usages (not just `view def` declarations) are also now recognized as +their own renderable declarations: a workspace containing both `view def` declarations and named +`view` usages surfaces both kinds as views the `render` command discovers and renders. + +## Expose vs. Render: Worked Examples + +The three view body statements look similar but do very different jobs. This is a common point +of confusion, so it is worth stating plainly: + +> **`render ;` looks like it should select what's shown, but it does not — use +> `expose` for that.** + +| Statement | What it actually does | +| --- | --- | +| `expose ;` | The **only** mechanism scoping which model content appears in the diagram (see above). | +| `render ;` | Selects a rendering *style/format* (e.g. `asTreeDiagram`). Metadata only, no scoping. | +| `filter [];` | Captured as raw text only; not yet evaluated (see ROADMAP.md's filter-evaluation entry). | + +### Example A: exposing a definition to scope down to a subsystem + +```sysml +package Vehicle { + part def Engine { + part cylinder[4]; + } + part def Vehicle { + part engine : Engine; + part wheel[4]; + } + part myVehicle : Vehicle; + + view EngineOnlyView { + expose Engine; + render asTreeDiagram; + } +} +``` + +`EngineOnlyView` renders **only** the `Engine` definition's containment subtree (`Engine` and +its `cylinder` part) — `Vehicle`, `myVehicle`, and `wheel` are excluded entirely. Removing the +`expose Engine;` statement (leaving only `render asTreeDiagram;`, or an empty view body) renders +the **full workspace** instead: `render` never narrows the scope, only `expose` does. + +> **Note:** `expose` targets are qualified names (`::`-separated), not dotted member-access +> chains. `expose myVehicle.engine;` is a **syntax error**, not merely an unresolved reference — +> the grammar's `qualifiedName` rule does not accept `.`. To scope to a specific part *usage*, +> expose the usage itself by its own name (as in Example B below), not a dotted path into it. + +### Example B: exposing a usage vs. exposing a definition + +```sysml +package Vehicle { + part def Engine { + part cylinder[4]; + } + part def Vehicle { + part engine : Engine; + part wheel[4]; + } + part myVehicle : Vehicle; + + view UsageExposeView { + expose myVehicle; + render asTreeDiagram; + } +} +``` + +Here `expose myVehicle;` names a **usage** (`myVehicle : Vehicle`), not a `def`. The tool +resolves `myVehicle`'s own `Typing` edge to find the definition it is typed by (`Vehicle`), and +scopes the diagram to the union of `myVehicle`'s and `Vehicle`'s containment subtrees. The +rendered diagram therefore includes `Vehicle` (with its `engine` and `wheel` parts) and, because +`engine` is typed by `Engine`, the `Engine` definition (with its `cylinder` part) as well. + +Contrast this with `expose Vehicle;` (exposing the **definition** directly, as in Example A): +that scopes straight to `Vehicle`'s own containment subtree without needing to resolve any +`Typing` edge, since a definition's subtree is already the thing being scoped to. Exposing a +usage takes one extra hop — through the usage's type reference — to arrive at the same kind of +definition subtree that exposing a `def` reaches directly. ## Depth Limiting diff --git a/docs/verification/sysml2-tools-core/layout/internal/general-view-layout-strategy.md b/docs/verification/sysml2-tools-core/layout/internal/general-view-layout-strategy.md index 9af71fb1..7630d93b 100644 --- a/docs/verification/sysml2-tools-core/layout/internal/general-view-layout-strategy.md +++ b/docs/verification/sysml2-tools-core/layout/internal/general-view-layout-strategy.md @@ -39,13 +39,15 @@ configuration are required beyond a standard .NET SDK installation. delegated layout does not over-pad sparse diagrams. - Standard-library-only input (by prefix or by seed set) yields a minimal empty canvas. - An empty workspace yields a 200×100 canvas with no nodes. -- A view whose `ViewContext.ViewNode` carries a resolved `Render` edge scopes the diagram to that +- A view whose `ViewContext.ViewNode` carries a resolved `Expose` edge scopes the diagram to that target's containment subtree, excluding unrelated sibling definitions and producing fewer boxes than an unscoped (no-`ViewNode`) rendering of the same workspace. -- A view whose `ViewContext.ViewNode` additionally carries a resolved `Expose` edge additively - includes that exposed name's containment subtree alongside the render target's subtree. -- A view whose declared render target failed to resolve (no `Render` edge present) falls back to - rendering the full workspace, identical to a view with a `null` `ViewNode`. +- A view whose `ViewContext.ViewNode` carries a `RenderTargetName` but no resolved `Expose` edges + renders the full workspace, byte-identical to a view with a `null` `ViewNode` — proving + `RenderTargetName` never affects scope, regardless of `FilterExpressionText`. +- A view whose resolved `Expose` edge names a feature usage (not a definition) still renders that + usage's type's containment subtree, by additionally resolving the usage's own `Typing` edge — + the fix for the usage-vs-definition containment gap. - A view whose `ViewContext.ViewNode` carries a non-null `FilterExpressionText` emits the "parsed but not yet evaluated" warning through `LayoutTree.Warnings`, while still rendering the resolved (unfiltered) scope. @@ -92,13 +94,14 @@ configuration are required beyond a standard .NET SDK installation. Connected cross-referencing model keeps every definition box non-overlapping - `GeneralViewLayoutStrategy_BuildLayout_HeatLayout_SparseModelProducesCompactCanvas`: Sparse canvas stays compact with no warnings (no over-padding) -- `GeneralViewLayoutStrategy_BuildLayout_ResolvedRenderTarget_ScopesToSubtreeFewerBoxes`: - A resolved `Render` edge scopes the diagram to the target's containment subtree, fewer boxes - than the full workspace - `GeneralViewLayoutStrategy_BuildLayout_ExposedName_UnionsAdditionalSubtree`: - A resolved `Expose` edge additively includes its containment subtree -- `GeneralViewLayoutStrategy_BuildLayout_UnresolvedRenderTarget_FallsBackToFullWorkspace`: - No `Render` edge (target failed to resolve) falls back to full-workspace rendering + A resolved `Expose` edge scopes the diagram to the target's containment subtree, fewer boxes + than the full workspace +- `GeneralViewLayoutStrategy_BuildLayout_RenderTargetNameOnly_NoExposeEdges_RendersFullWorkspace`: + A `RenderTargetName` with no resolved `Expose` edges renders the full workspace unchanged +- `GeneralViewLayoutStrategy_BuildLayout_ExposedUsage_ResolvesThroughTypingToDefinitionSubtree`: + A resolved `Expose` edge naming a feature usage resolves through the usage's `Typing` edge to + include its type's containment subtree - `GeneralViewLayoutStrategy_BuildLayout_FilterExpressionPresent_EmitsNotYetEvaluatedWarning`: A non-null `FilterExpressionText` emits the "parsed but not yet evaluated" warning - `GeneralViewLayoutStrategy_BuildLayout_NullViewNode_RendersFullWorkspaceUnchanged`: diff --git a/docs/verification/sysml2-tools-core/rendering/diagram-renderer.md b/docs/verification/sysml2-tools-core/rendering/diagram-renderer.md index 4fe5106f..431a7b37 100644 --- a/docs/verification/sysml2-tools-core/rendering/diagram-renderer.md +++ b/docs/verification/sysml2-tools-core/rendering/diagram-renderer.md @@ -22,8 +22,8 @@ external services are required. - A workspace with no views produces an empty result. - `RenderWorkspace` threads each view's resolved `SysmlViewNode` into the `ViewContext` it constructs, so `GeneralViewLayoutStrategy` can scope a rendered diagram to a view's declared - `render`/`expose` subject — verified end-to-end at the CLI level (see the `RenderCommand` - verification doc's `RenderSubsystem_ViewsWithDistinctRenderTargets_ProduceDifferingOutputsAndDiagnostic`) + `expose` subject — verified end-to-end at the CLI level (see the `RenderCommand` + verification doc's `RenderSubsystem_ViewsWithDistinctExposeTargets_ProduceDifferingOutputsAndDiagnostic`) and at the layout-strategy level (see the `GeneralViewLayoutStrategy` verification doc). #### Test Scenarios @@ -35,4 +35,4 @@ external services are required. | `DiagramRenderer_RenderWorkspace_GeneralViewModel_SvgContainsElementNames` | The SVG output contains element names | | `DiagramRenderer_RenderWorkspace_GeneralViewModel_PngProducesValidOutput` | The PNG output carries the PNG signature | | `DiagramRenderer_RenderWorkspace_NoViews_ReturnsEmptyList` | A view-free workspace yields an empty result | -| `RenderSubsystem_ViewsWithDistinctRenderTargets_ProduceDifferingOutputsAndDiagnostic` | Produces scoped output | +| `RenderSubsystem_ViewsWithDistinctExposeTargets_ProduceDifferingOutputsAndDiagnostic` | Produces scoped output | diff --git a/docs/verification/sysml2-tools-language/semantic/model/ast-builder.md b/docs/verification/sysml2-tools-language/semantic/model/ast-builder.md index c1f4cc10..e221b438 100644 --- a/docs/verification/sysml2-tools-language/semantic/model/ast-builder.md +++ b/docs/verification/sysml2-tools-language/semantic/model/ast-builder.md @@ -26,7 +26,9 @@ external services or additional configuration are required beyond a standard .NE the corresponding `SysmlViewNode`, and leaves both null for a view with an empty body. - `VisitViewUsage` (a named `view` usage, not a `view def` definition) captures the same render/filter members plus `expose ;` members, producing a `SysmlViewNode` with - populated `ExposedNames`. + populated `ExposedNames`. This also makes every named `view` usage its own renderable + declaration, an intentional capability addition beyond `expose` capture alone (see the + ast-builder design doc). ##### Test Scenarios @@ -36,7 +38,8 @@ external services or additional configuration are required beyond a standard .NE | Qualified name from namespace stack | `WorkspaceLoader_LoadAsync_NestedPackages_RegistersQualifiedNames` | | Definition registration | `WorkspaceLoader_LoadAsync_PartDef_RegistersDefinition` | | Supertype extraction | `WorkspaceLoader_LoadAsync_SpecializesChain_Registered` | -| `VisitViewDefinition` render capture | `WorkspaceLoader_LoadAsync_ViewRenderTargetResolves_RecordsRenderEdge` | +| `VisitViewDefinition` render | `WorkspaceLoader_LoadAsync_ViewRenderTarget_CapturedRawNeverResolvedNoDiagnostic` | | `VisitViewDefinition` filter capture | `WorkspaceLoader_LoadAsync_ViewFilterExpression_CapturesTextVerbatimNoEdge` | | `VisitViewUsage` expose capture | `WorkspaceLoader_LoadAsync_ViewUsageWithExpose_RecordsExposeEdge` | +| `VisitViewUsage` renderable declaration | `RenderSubsystem_OmgSafetyFeatureViewsCorpus_RendersAllNamedViewUsages` | | Empty view body regression guard | `WorkspaceLoader_LoadAsync_ViewEmptyBody_AllNewFieldsNullOrEmpty` | diff --git a/docs/verification/sysml2-tools-language/semantic/model/reference-resolver.md b/docs/verification/sysml2-tools-language/semantic/model/reference-resolver.md index f4eb5f77..e734d90f 100644 --- a/docs/verification/sysml2-tools-language/semantic/model/reference-resolver.md +++ b/docs/verification/sysml2-tools-language/semantic/model/reference-resolver.md @@ -27,11 +27,12 @@ external services or additional configuration are required beyond a standard .NE unresolved one produces a Warning diagnostic and no edge. - A resolved import reference (wildcard or named) is recorded as an `Import`-kind `SysmlEdge`; an unresolved import reference produces a Warning diagnostic without crashing. -- A view's resolved `render ;` reference is recorded as a `Render`-kind `SysmlEdge`; an - unresolved render target produces a Warning diagnostic naming the unresolved identifier and no - edge — the exact fix for the reported bug (previously, an unresolved render target rendered - the full workspace silently with no diagnostic). -- A view usage's resolved `expose ;` reference is recorded as an `Expose`-kind `SysmlEdge`. +- A view usage's resolved `expose ;` reference is recorded as an `Expose`-kind `SysmlEdge`; + an unresolved `expose` reference produces a Warning diagnostic naming the unresolved + identifier and no edge. A view's `render ;` member (a rendering style/format selector + per the SysML v2 grammar, never content) is captured on `SysmlViewNode.RenderTargetName` but + never inspected by `ReferenceResolver` — no edge is produced and no diagnostic is emitted for + it, even when the named identifier is not declared anywhere in the file. ##### Test Scenarios @@ -46,7 +47,6 @@ external services or additional configuration are required beyond a standard .NE | Wildcard import records edge | `WorkspaceLoader_LoadAsync_WildcardImport_RecordsImportEdge` | | Named import records edge | `WorkspaceLoader_LoadAsync_NamedImport_RecordsImportEdge` | | Unresolved import — Warning, no crash | `WorkspaceLoader_LoadAsync_UnresolvedImport_ProducesWarningNoCrash` | -| Resolved render target records edge | `WorkspaceLoader_LoadAsync_ViewRenderTargetResolves_RecordsRenderEdge` | -| Unresolved render target, no edge | `WorkspaceLoader_LoadAsync_ViewRenderTargetUnresolved_ProducesWarningNoEdge` | +| RenderTargetName captured raw | `WorkspaceLoader_LoadAsync_ViewRenderTarget_CapturedRawNeverResolvedNoDiagnostic` | | Resolved expose name records edge | `WorkspaceLoader_LoadAsync_ViewUsageWithExpose_RecordsExposeEdge` | -| E2E diagnostic visibility | `RenderSubsystem_ViewsWithDistinctRenderTargets_ProduceDifferingOutputsAndDiagnostic` | +| E2E diagnostic visibility | `RenderSubsystem_ViewsWithDistinctExposeTargets_ProduceDifferingOutputsAndDiagnostic` | diff --git a/docs/verification/sysml2-tools-language/semantic/model/sysml-edge.md b/docs/verification/sysml2-tools-language/semantic/model/sysml-edge.md index b6afe5cf..73cd012c 100644 --- a/docs/verification/sysml2-tools-language/semantic/model/sysml-edge.md +++ b/docs/verification/sysml2-tools-language/semantic/model/sysml-edge.md @@ -20,10 +20,10 @@ external services or additional configuration are required beyond a standard .NE and the fully-qualified `TargetQualifiedName`. - A resolved feature typing reference produces a `SysmlEdge` with `Kind == SysmlEdgeKind.Typing`. - A resolved import reference produces a `SysmlEdge` with `Kind == SysmlEdgeKind.Import`. -- A resolved view render-target reference produces a `SysmlEdge` with - `Kind == SysmlEdgeKind.Render`. - A resolved view-usage exposed-name reference produces a `SysmlEdge` with - `Kind == SysmlEdgeKind.Expose`. + `Kind == SysmlEdgeKind.Expose`; `SysmlEdgeKind.Render` no longer exists, since a view's + `render ;` member is never resolved into an edge (it names a rendering style/format, + not content). ##### Test Scenarios @@ -33,5 +33,5 @@ external services or additional configuration are required beyond a standard .NE | Typing edge recorded | `WorkspaceLoader_LoadAsync_ResolvedFeatureTyping_RecordsTypingEdge` | | Import edge recorded (wildcard) | `WorkspaceLoader_LoadAsync_WildcardImport_RecordsImportEdge` | | Import edge recorded (named) | `WorkspaceLoader_LoadAsync_NamedImport_RecordsImportEdge` | -| Render edge recorded | `WorkspaceLoader_LoadAsync_ViewRenderTargetResolves_RecordsRenderEdge` | +| RenderTargetName never resolved | `WorkspaceLoader_LoadAsync_ViewRenderTarget_CapturedRawNeverResolvedNoDiagnostic` | | Expose edge recorded | `WorkspaceLoader_LoadAsync_ViewUsageWithExpose_RecordsExposeEdge` | diff --git a/docs/verification/sysml2-tools-language/semantic/model/sysml-node.md b/docs/verification/sysml2-tools-language/semantic/model/sysml-node.md index ce28466f..9376e5ba 100644 --- a/docs/verification/sysml2-tools-language/semantic/model/sysml-node.md +++ b/docs/verification/sysml2-tools-language/semantic/model/sysml-node.md @@ -30,7 +30,9 @@ external services or additional configuration are required beyond a standard .NE node with none. - `SysmlViewNode.RenderTargetName`/`FilterExpressionText`/`ExposedNames` are populated verbatim from a view's `render`/`filter`/`expose` body members (raw reference/expression text, never - evaluated), and are `null`/empty for a view with no such members. + evaluated), and are `null`/empty for a view with no such members. `RenderTargetName` is + captured but never resolved into an edge or diagnostic (it names a rendering style/format, not + content); `ExposedNames` is the only field independently resolved by `ReferenceResolver`. ##### Test Scenarios @@ -41,7 +43,7 @@ external services or additional configuration are required beyond a standard .NE | `SupertypeNames` population | `WorkspaceLoader_LoadAsync_SpecializesChain_Registered` | | `ResolvedEdges` populated | `WorkspaceLoader_LoadAsync_ResolvedSupertype_RecordsSupertypeEdge` | | `Annotations` populated | `WorkspaceLoader_LoadAsync_CommentAndDocumentation_CapturesBothInSourceOrder` | -| `SysmlViewNode.RenderTargetName` resolves | `WorkspaceLoader_LoadAsync_ViewRenderTargetResolves_RecordsRenderEdge` | +| `RenderTargetName` unresolved | `WorkspaceLoader_LoadAsync_ViewRenderTarget_CapturedRawNeverResolvedNoDiagnostic` | | `FilterExpressionText` verbatim | `WorkspaceLoader_LoadAsync_ViewFilterExpression_CapturesTextVerbatimNoEdge` | | `SysmlViewNode.ExposedNames` from a `view` usage | `WorkspaceLoader_LoadAsync_ViewUsageWithExpose_RecordsExposeEdge` | | Empty view body leaves all fields null/empty | `WorkspaceLoader_LoadAsync_ViewEmptyBody_AllNewFieldsNullOrEmpty` | diff --git a/docs/verification/sysml2-tools-tool/render.md b/docs/verification/sysml2-tools-tool/render.md index 26b1911d..2a74eff6 100644 --- a/docs/verification/sysml2-tools-tool/render.md +++ b/docs/verification/sysml2-tools-tool/render.md @@ -39,10 +39,15 @@ on context output and exit code. File-writing scenarios use a temporary director - `render --help` prints render-specific usage and options (not the generic top-level command list), and is identical to `help render`'s output (see the Help subsystem verification document). -- A workspace with two views — one with a `render ;` statement naming a resolvable - target, one with a bogus `render thisIdentifierDoesNotExistAnywhere;` statement — produces - two output files whose content DIFFERS (the reported bug's exact fix), and the bogus view's - unresolved render target is visible as a diagnostic in the captured log output. +- A workspace with two views — one with an `expose ;` statement naming a resolvable + target, one with a bogus `expose thisIdentifierDoesNotExistAnywhere;` statement — produces + two output files whose content DIFFERS, and the bogus view's unresolved exposed name is + visible as a diagnostic in the captured log output. +- Rendering the real OMG corpus fixture `11b-SafetyAndSecurityFeatureViews.sysml` with no + `--view` filter produces exactly 5 output files (2 `view def`s plus 3 named `view` usages), + regression-guarding the `VisitViewUsage` capability addition, with no false + "Unresolved reference" diagnostic for its `render asTreeDiagram;`/`render asElementTable;` + rendering-style members. ### Test Scenarios @@ -119,17 +124,30 @@ Verifies that `render --help` prints the render-specific usage line and its `--o regression-proofing test added alongside the `help` command's command-aware `--help` dispatch (see `docs/design/sysml2-tools-tool/help.md`). -#### RenderSubsystem_ViewsWithDistinctRenderTargets_ProduceDifferingOutputsAndDiagnostic +#### RenderSubsystem_ViewsWithDistinctExposeTargets_ProduceDifferingOutputsAndDiagnostic -End-to-end regression test reproducing the reported bug: a workspace declares two views — one -with `render TargetA;` (resolving to a `part def` with a nested child), one with `render +End-to-end regression test: a workspace declares two `view` usages — one with `expose TargetA;` +(resolving to a `part def` with a nested child), one with `expose thisIdentifierDoesNotExistAnywhere;` (an unresolvable target). Verifies that rendering both -without `--view` produces `ViewValid.svg` and `ViewBogus.svg` whose content now DIFFERS (before -this fix, every view rendered the identical full-workspace diagram, so the two files would have -been byte-identical), and that the captured `--log` output contains +without `--view` produces `ViewValid.svg` and `ViewBogus.svg` whose content DIFFERS (a view with +no resolved `Expose` edges renders the full workspace, while the valid view's sole `Expose` entry +scopes to that target's subtree), and that the captured `--log` output contains `"thisIdentifierDoesNotExistAnywhere"` — the unresolved-reference diagnostic surfaced by -`ReferenceResolver` for the bogus render target, closing the exact reported symptom (a typo'd -render target previously produced no visible signal at all). +`ReferenceResolver` for the bogus exposed name. `render ;` plays no role in scoping — +only `expose` does, per the corrected semantics. + +#### RenderSubsystem_OmgSafetyFeatureViewsCorpus_RendersAllNamedViewUsages + +Regression guard for the `AstBuilder.VisitViewUsage` capability addition: loads and renders the +real OMG corpus fixture +`test/SysMLModels/OMG/validation/11-ViewAndViewpoint/11b-SafetyAndSecurityFeatureViews.sysml` +with no `--view` filter, asserting exactly 5 output files are produced — the 2 `view def` +declarations (`SafetyFeatureView`, `SafetyOrSecurityFeatureView`) plus the 3 named `view` usages +(`vehicleSafetyFeatureView`, `vehicleMandatorySafetyFeatureView`, +`vehicleMandatorySafetyFeatureViewStandalone`), not just the 2 `view def`s that were the only +renderable declarations before `VisitViewUsage` was added. Also asserts the captured `--log` +output contains no `"asTreeDiagram"`/`"asElementTable"` text, confirming those rendering-style +`render` members never surface a false unresolved-reference diagnostic. #### ResxResource_EveryKey_ResolvesToNonEmptyText / ResxResource_KeysAndAccessorProperties_AreInBidirectionalParity (ResxResourceTests.cs) diff --git a/src/DemaConsulting.SysML2Tools.Core/Layout/Internal/GeneralViewLayoutStrategy.cs b/src/DemaConsulting.SysML2Tools.Core/Layout/Internal/GeneralViewLayoutStrategy.cs index 396c6c7a..1e88b7c6 100644 --- a/src/DemaConsulting.SysML2Tools.Core/Layout/Internal/GeneralViewLayoutStrategy.cs +++ b/src/DemaConsulting.SysML2Tools.Core/Layout/Internal/GeneralViewLayoutStrategy.cs @@ -108,12 +108,13 @@ public LayoutTree BuildLayout(ViewContext context, RenderOptions options) var theme = options.Theme; - // Resolve the view's render-target subject scope (render target plus exposed names' - // containment subtrees), or null when the view has no resolved Render edge — including a - // null ViewNode (the --auto synthetic-view path) and an unresolved render target, both of - // which leave no Render edge on ResolvedEdges. A null scope means "render everything", - // byte-identical to the pre-scoping behavior. - var scope = ResolveSubjectScope(context.ViewNode); + // Resolve the view's exposed-name scope (the union of each resolved Expose edge's + // containment subtree), or null when the view has no resolved Expose edges — including a + // null ViewNode (the --auto synthetic-view path), a view with no `expose` statement, and a + // view whose every `expose` entry failed to resolve. A null scope means "render + // everything", byte-identical to the pre-scoping behavior. RenderTargetName and + // FilterExpressionText never affect this decision. + var scope = ResolveExposedScope(context.Workspace, context.ViewNode); // Collect all user-defined definitions, sized for rendering, restricted to the resolved // scope when one applies. @@ -155,35 +156,59 @@ public LayoutTree BuildLayout(ViewContext context, RenderOptions options) } /// - /// Resolves the qualified-name containment-subtree scope a view's render/expose - /// statements restrict the diagram to, or when the view has no resolved - /// edge — meaning every non-stdlib definition is included, - /// unchanged from the pre-scoping behavior. This covers three equivalent "no scoping" cases - /// uniformly: a null (the --auto synthetic view, which never - /// carries render/expose/filter data), a view with no render statement, and a view whose - /// render target failed to resolve (per binding decision, an unresolved target falls - /// back to full-workspace rendering; the diagnostic that informs the user is emitted by - /// ReferenceResolver, not here). + /// Resolves the qualified-name containment-subtree scope a view's expose statements + /// restrict the diagram to, or when the view has no resolved + /// edge — meaning every non-stdlib definition is included, + /// unchanged from the pre-scoping behavior. This covers every "no scoping" case uniformly: a + /// null (the --auto synthetic view, which never carries + /// expose/render/filter data), a view with no expose statement, and a view whose every + /// expose entry failed to resolve. RenderTargetName (a rendering-style/format + /// selector, not content) and FilterExpressionText never affect this decision. /// + /// + /// When an exposed target resolves to a (a usage, e.g. + /// part myVehicle : Vehicle;) rather than a , the + /// usage's own containment subtree is typically empty — the real content lives under its + /// type's subtree. To avoid silently scoping to nothing, this also resolves the usage's own + /// edge (if any) and adds that type's qualified name to the + /// scope as well, so both the usage and its type's subtree are included. + /// + /// The workspace, used to look up each exposed target's declaration. /// The view's AST node, or null for the synthetic --auto view. /// - /// The list of subject qualified names (the render target plus any exposed names) whose - /// containment subtrees are in scope, or null when no scoping applies. + /// The list of subject qualified names (each exposed name, plus the resolved type of any + /// exposed name that names a usage) whose containment subtrees are in scope, or null when no + /// scoping applies. /// - private static IReadOnlyList? ResolveSubjectScope(SysmlViewNode? viewNode) + private static IReadOnlyList? ResolveExposedScope(SysmlWorkspace workspace, SysmlViewNode? viewNode) { - var renderTarget = viewNode?.ResolvedEdges - .FirstOrDefault(edge => edge.Kind == SysmlEdgeKind.Render) - ?.TargetQualifiedName; - if (renderTarget is null) + var exposedTargets = viewNode?.ResolvedEdges + .Where(edge => edge.Kind == SysmlEdgeKind.Expose) + .Select(edge => edge.TargetQualifiedName) + .ToList(); + if (exposedTargets is not { Count: > 0 }) { return null; } - var subjects = new List { renderTarget }; - subjects.AddRange(viewNode!.ResolvedEdges - .Where(edge => edge.Kind == SysmlEdgeKind.Expose) - .Select(edge => edge.TargetQualifiedName)); + var subjects = new List(); + foreach (var target in exposedTargets) + { + subjects.Add(target); + + if (workspace.Declarations.TryGetValue(target, out var declaration) && + declaration is SysmlFeatureNode { } feature) + { + var typeTarget = feature.ResolvedEdges + .FirstOrDefault(edge => edge.Kind == SysmlEdgeKind.Typing) + ?.TargetQualifiedName; + if (typeTarget is not null) + { + subjects.Add(typeTarget); + } + } + } + return subjects; } @@ -201,7 +226,7 @@ private static bool IsInSubjectScope(string qualifiedName, IReadOnlyList /// /// Collects every user-defined from the workspace and computes /// each box's intrinsic size from its keyword and name, restricted to - /// when non-null (the view's resolved render/expose containment subtrees). + /// when non-null (the view's resolved expose containment subtrees). /// private static IReadOnlyList CollectDefinitions( SysmlWorkspace workspace, diff --git a/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/ReferenceResolver.cs b/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/ReferenceResolver.cs index 218be2df..d0931be0 100644 --- a/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/ReferenceResolver.cs +++ b/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/ReferenceResolver.cs @@ -534,35 +534,15 @@ private void ResolveNode( } } - // Views resolve two independent reference sets: RenderTargetName (a view's `render - // ;` subject) and each entry of ExposedNames (a view's `expose ;` - // members). Unlike Satisfy/Allocate, each is resolved and edged independently — a - // resolved RenderTargetName produces a Render edge even when an ExposedNames entry fails - // to resolve, and vice versa, since the two statements are functionally unrelated (unlike - // satisfy's two-sided subject/requirement pair). An unresolved RenderTargetName produces - // only the Warning diagnostic below; GeneralViewLayoutStrategy observes the resulting - // absence of a Render edge and falls back to rendering the full workspace for that view - // — the same observable state as a view with no `render` statement at all. - // FilterExpressionText is raw source text, not a reference, and is intentionally never - // touched here. + // Views resolve only ExposedNames (a view's expose members) into Expose edges; each entry + // is resolved independently, and an unresolved entry produces the Warning diagnostic below + // with no edge. RenderTargetName (a view's render member) names a rendering style or + // format, not a content-scoping subject -- per the SysML v2 grammar it never refers to + // model content, so ReferenceResolver never inspects it: no edge is produced and no + // diagnostic is emitted for it, mirroring how FilterExpressionText (raw source text, not a + // reference) is also intentionally never touched here. if (node is SysmlViewNode view) { - if (view.RenderTargetName is { Length: > 0 } renderTargetName) - { - if (TryResolve(renderTargetName, namespaceStack, imports, out var resolvedTarget)) - { - nodeEdges.Add(new SysmlEdge(node.QualifiedName, resolvedTarget, SysmlEdgeKind.Render)); - } - else if (resolvedInFile.Add(renderTargetName)) - { - _diagnostics.Add(new SysmlDiagnostic( - filePath, - 0, 0, - DiagnosticSeverity.Warning, - $"Unresolved reference: '{renderTargetName}'")); - } - } - foreach (var exposedName in view.ExposedNames) { if (TryResolve(exposedName, namespaceStack, imports, out var resolvedExposed)) diff --git a/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/SysmlEdge.cs b/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/SysmlEdge.cs index 8f39ece3..184e2e43 100644 --- a/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/SysmlEdge.cs +++ b/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/SysmlEdge.cs @@ -66,22 +66,15 @@ public enum SysmlEdgeKind /// Transition, - /// - /// A view render-target reference (render <target>; nested in a view's - /// body), from the view to the resolved element the view is scoped to. Recorded only - /// when the target resolves; an unresolved target produces an unresolved-reference - /// diagnostic instead (see ), and - /// GeneralViewLayoutStrategy falls back to rendering the full workspace for that - /// view — identical to a view with no render statement at all. - /// - Render, - /// /// A view expose reference (expose <name>; nested in a view's body), from - /// the view to a resolved element whose containment subtree is additionally included in - /// the rendered scope alongside the subject. One edge is recorded - /// per resolvable entry in ; an unresolved entry - /// produces an unresolved-reference diagnostic instead. + /// the view to a resolved element whose containment subtree is included in the rendered + /// scope. One edge is recorded per resolvable entry in + /// ; an unresolved entry produces an + /// unresolved-reference diagnostic instead. GeneralViewLayoutStrategy scopes its + /// diagram to the union of every edge's target containment subtree; + /// a view with no edges renders the full workspace, unchanged from + /// the pre-scoping baseline. /// Expose, } diff --git a/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/SysmlNode.cs b/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/SysmlNode.cs index 0040cd5f..5bfcadf8 100644 --- a/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/SysmlNode.cs +++ b/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/SysmlNode.cs @@ -178,11 +178,13 @@ public sealed class SysmlViewNode : SysmlNode /// /// Gets the raw reference text of this view's render <target>; statement /// (from viewRenderingMember().viewRenderingUsage()), or - /// when the view declares no rendering member. Resolved by - /// into a edge when the name resolves, or an - /// unresolved-reference diagnostic when it does not — an unresolved (or absent) render - /// target causes GeneralViewLayoutStrategy to fall back to rendering the full - /// workspace, unchanged from the pre-scoping behavior. + /// when the view declares no rendering member. Per the SysML v2 grammar, this names a + /// rendering style/format usage (e.g. asTreeDiagram, asElementTable) — never + /// a content-scoping subject. Captured verbatim only: never + /// inspects or resolves this value (no edge is produced, no diagnostic is emitted), and it + /// has no effect on GeneralViewLayoutStrategy's rendered scope. Reserved for a + /// possible future capability that selects among rendering-style strategies — see the + /// project ROADMAP. /// public string? RenderTargetName { get; init; } diff --git a/test/DemaConsulting.SysML2Tools.Tests/Layout/GeneralViewLayoutStrategyTests.cs b/test/DemaConsulting.SysML2Tools.Tests/Layout/GeneralViewLayoutStrategyTests.cs index deccadb7..36df68f4 100644 --- a/test/DemaConsulting.SysML2Tools.Tests/Layout/GeneralViewLayoutStrategyTests.cs +++ b/test/DemaConsulting.SysML2Tools.Tests/Layout/GeneralViewLayoutStrategyTests.cs @@ -757,8 +757,8 @@ public void GeneralViewLayoutStrategy_BuildLayout_HeatLayout_SparseModelProduces } /// - /// Builds the fixed three-definition workspace shared by the render/expose scoping tests: - /// Root::A (the render target), Root::A::Child (inside A's containment + /// Builds the fixed three-definition workspace shared by the expose-scoping tests: + /// Root::A (an expose target), Root::A::Child (inside A's containment /// subtree, qualified name prefixed "Root::A::"), and Root::B (an unrelated /// sibling definition, outside A's subtree). /// @@ -773,23 +773,46 @@ public void GeneralViewLayoutStrategy_BuildLayout_HeatLayout_SparseModelProduces }; /// - /// A view with a resolved Render edge to Root::A scopes the diagram to + /// Builds a workspace modeling the usage-vs-definition containment gap fix: a + /// Root::Vehicle definition with an owned child Root::Vehicle::Engine, and a + /// Root::myVehicle feature usage typed by Vehicle (a resolved Typing + /// edge to Root::Vehicle) plus an unrelated sibling Root::Other. + /// + private static SysmlWorkspace BuildUsageTypingWorkspace() => new() + { + Declarations = new Dictionary + { + ["Root::Vehicle"] = new SysmlDefinitionNode { Name = "Vehicle", QualifiedName = "Root::Vehicle", DefinitionKeyword = "part def" }, + ["Root::Vehicle::Engine"] = new SysmlDefinitionNode { Name = "Engine", QualifiedName = "Root::Vehicle::Engine", DefinitionKeyword = "part def" }, + ["Root::myVehicle"] = new SysmlFeatureNode + { + Name = "myVehicle", + QualifiedName = "Root::myVehicle", + FeatureTyping = "Vehicle", + ResolvedEdges = [new SysmlEdge("Root::myVehicle", "Root::Vehicle", SysmlEdgeKind.Typing)] + }, + ["Root::Other"] = new SysmlDefinitionNode { Name = "Other", QualifiedName = "Root::Other", DefinitionKeyword = "part def" } + } + }; + + /// + /// A view with a resolved Expose edge to Root::A scopes the diagram to /// Root::A plus its containment subtree (Root::A::Child), excluding the /// unrelated sibling Root::B — producing fewer boxes than rendering the full /// workspace. /// [Fact] - public void GeneralViewLayoutStrategy_BuildLayout_ResolvedRenderTarget_ScopesToSubtreeFewerBoxes() + public void GeneralViewLayoutStrategy_BuildLayout_ExposedName_UnionsAdditionalSubtree() { - // Arrange: a view whose render target resolves to Root::A + // Arrange: a view exposing only Root::A var strategy = new GeneralViewLayoutStrategy(); var workspace = BuildScopingWorkspace(); var viewNode = new SysmlViewNode { Name = "V", QualifiedName = "Root::V", - RenderTargetName = "A", - ResolvedEdges = [new SysmlEdge("Root::V", "Root::A", SysmlEdgeKind.Render)] + ExposedNames = ["A"], + ResolvedEdges = [new SysmlEdge("Root::V", "Root::A", SysmlEdgeKind.Expose)] }; var context = new ViewContext("v", workspace, viewNode); var options = new RenderOptions(Themes.Light); @@ -808,72 +831,68 @@ public void GeneralViewLayoutStrategy_BuildLayout_ResolvedRenderTarget_ScopesToS } /// - /// A view with a resolved Render edge to Root::A plus a resolved - /// Expose edge to Root::B additively includes Root::B's containment - /// subtree alongside the render subject's subtree. + /// A view whose RenderTargetName is present but has no Expose edges renders + /// the full workspace, byte-identical (same box count/labels) to the null-ViewNode + /// case — proving RenderTargetName never affects scope, since it names a rendering + /// style/format, not content. /// [Fact] - public void GeneralViewLayoutStrategy_BuildLayout_ExposedName_UnionsAdditionalSubtree() + public void GeneralViewLayoutStrategy_BuildLayout_RenderTargetNameOnly_NoExposeEdges_RendersFullWorkspace() { - // Arrange: a view rendering Root::A and additionally exposing Root::B + // Arrange: a view with a RenderTargetName but no resolved Expose edges var strategy = new GeneralViewLayoutStrategy(); var workspace = BuildScopingWorkspace(); var viewNode = new SysmlViewNode { Name = "V", QualifiedName = "Root::V", - RenderTargetName = "A", - ExposedNames = ["B"], - ResolvedEdges = - [ - new SysmlEdge("Root::V", "Root::A", SysmlEdgeKind.Render), - new SysmlEdge("Root::V", "Root::B", SysmlEdgeKind.Expose) - ] + RenderTargetName = "asTreeDiagram", + ResolvedEdges = [] }; var context = new ViewContext("v", workspace, viewNode); var options = new RenderOptions(Themes.Light); // Act - var layout = strategy.BuildLayout(context, options); + var inert = strategy.BuildLayout(context, options); + var full = strategy.BuildLayout(new ViewContext("full", workspace), options); - // Assert: A, A::Child, and B (the exposed subtree) are all present. - var labels = CollectBoxes(layout.Nodes).Select(b => b.Label).ToList(); - Assert.Contains("A", labels); - Assert.Contains("Child", labels); - Assert.Contains("B", labels); + // Assert: identical box count/labels to the full-workspace (no-ViewNode) rendering. + var inertLabels = CollectBoxes(inert.Nodes).Select(b => b.Label).OrderBy(l => l).ToList(); + var fullLabels = CollectBoxes(full.Nodes).Select(b => b.Label).OrderBy(l => l).ToList(); + Assert.Equal(fullLabels, inertLabels); } /// - /// A view whose RenderTargetName failed to resolve (no Render edge on - /// , since ReferenceResolver never adds one - /// for an unresolved target) falls back to rendering the full workspace — the same - /// byte/box-count-identical result as a view with no render statement at all. This - /// is the regression-safe fallback required by the bug-fix binding decision. + /// A view whose Expose edge resolves to a feature usage (not a definition) still + /// renders that usage's type's containment subtree, by additionally resolving the usage's + /// own Typing edge — the fix for the usage-vs-definition containment gap. Excludes + /// the unrelated sibling Root::Other. /// [Fact] - public void GeneralViewLayoutStrategy_BuildLayout_UnresolvedRenderTarget_FallsBackToFullWorkspace() + public void GeneralViewLayoutStrategy_BuildLayout_ExposedUsage_ResolvesThroughTypingToDefinitionSubtree() { - // Arrange: a view whose render target never resolved (no Render edge present) + // Arrange: a view exposing Root::myVehicle, a usage typed by Root::Vehicle var strategy = new GeneralViewLayoutStrategy(); - var workspace = BuildScopingWorkspace(); + var workspace = BuildUsageTypingWorkspace(); var viewNode = new SysmlViewNode { Name = "V", QualifiedName = "Root::V", - RenderTargetName = "thisIdentifierDoesNotExistAnywhere", - ResolvedEdges = [] + ExposedNames = ["myVehicle"], + ResolvedEdges = [new SysmlEdge("Root::V", "Root::myVehicle", SysmlEdgeKind.Expose)] }; var context = new ViewContext("v", workspace, viewNode); var options = new RenderOptions(Themes.Light); // Act - var fallback = strategy.BuildLayout(context, options); - var full = strategy.BuildLayout(new ViewContext("full", workspace), options); + var layout = strategy.BuildLayout(context, options); - // Assert: identical box count/labels to the full-workspace (no-ViewNode) rendering. - var fallbackLabels = CollectBoxes(fallback.Nodes).Select(b => b.Label).OrderBy(l => l).ToList(); - var fullLabels = CollectBoxes(full.Nodes).Select(b => b.Label).OrderBy(l => l).ToList(); - Assert.Equal(fullLabels, fallbackLabels); + // Assert: Vehicle and its Engine child are present (resolved through the usage's typing + // edge), but the unrelated Other definition is excluded. + var labels = CollectBoxes(layout.Nodes).Select(b => b.Label).ToList(); + Assert.Contains("Vehicle", labels); + Assert.Contains("Engine", labels); + Assert.DoesNotContain("Other", labels); } /// @@ -901,7 +920,7 @@ public void GeneralViewLayoutStrategy_BuildLayout_FilterExpressionPresent_EmitsN var layout = strategy.BuildLayout(context, options); // Assert: a warning about the unevaluated filter is present, and the resolved (unfiltered) - // scope — here, the full workspace, since no render target was declared — still renders. + // scope — here, the full workspace, since no expose statement was declared — still renders. Assert.Contains(layout.Warnings, w => w.Contains("filter expression") && w.Contains("not yet evaluated")); var labels = CollectBoxes(layout.Nodes).Select(b => b.Label).ToList(); Assert.Contains("A", labels); @@ -909,7 +928,7 @@ public void GeneralViewLayoutStrategy_BuildLayout_FilterExpressionPresent_EmitsN } /// - /// A view with no render statement (a null , e.g. + /// A view with no expose statement (a null , e.g. /// the --auto synthesized view) renders identically to the pre-scoping-change /// baseline: every non-stdlib definition in the workspace. This is the critical regression /// guard confirming the scoping feature is fully backward-compatible when unused. diff --git a/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs b/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs index 51a8fa25..89132be8 100644 --- a/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs +++ b/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs @@ -2395,12 +2395,15 @@ public async Task WorkspaceLoader_LoadAsync_DocumentationExampleFixture_Captures } /// - /// A view def with a render <target>; member whose target resolves - /// should be recorded as a - /// edge from the view to the resolved qualified name. + /// A view def with a render <target>; member naming a rendering-style + /// identifier that is not declared anywhere in the file (which would have failed + /// resolution under the old, incorrect content-scoping semantics) produces zero + /// diagnostics and zero edges sourced from the view — ReferenceResolver never + /// inspects RenderTargetName — while + /// is still captured verbatim. /// [Fact] - public async Task WorkspaceLoader_LoadAsync_ViewRenderTargetResolves_RecordsRenderEdge() + public async Task WorkspaceLoader_LoadAsync_ViewRenderTarget_CapturedRawNeverResolvedNoDiagnostic() { // Arrange var tempFile = Path.GetTempFileName() + ".sysml"; @@ -2408,9 +2411,8 @@ public async Task WorkspaceLoader_LoadAsync_ViewRenderTargetResolves_RecordsRend { await File.WriteAllTextAsync(tempFile, """ package P { - part def Target {} view def V { - render Target; + render asTreeDiagram; } } """, TestContext.Current.CancellationToken); @@ -2421,49 +2423,12 @@ view def V { // Assert Assert.NotNull(result.Workspace); - Assert.Contains(result.Workspace!.Index.AllEdges, - e => e.Kind == DemaConsulting.SysML2Tools.Semantic.Model.SysmlEdgeKind.Render && - e.SourceQualifiedName == "P::V" && - e.TargetQualifiedName == "P::Target"); - } - finally - { - File.Delete(tempFile); - } - } - - /// - /// A view def with a render <target>; member whose target does NOT - /// resolve (e.g. a typo) must produce an unresolved-reference Warning diagnostic and must - /// not produce a Render edge — the exact fix for the reported bug (a bogus render - /// target previously rendered everything silently, with no signal to the user). - /// - [Fact] - public async Task WorkspaceLoader_LoadAsync_ViewRenderTargetUnresolved_ProducesWarningNoEdge() - { - // Arrange - var tempFile = Path.GetTempFileName() + ".sysml"; - try - { - await File.WriteAllTextAsync(tempFile, """ - package P { - view def V { - render thisIdentifierDoesNotExistAnywhere; - } - } - """, TestContext.Current.CancellationToken); - - // Act - var (stdlibTable, _) = StdlibProvider.GetSymbolTable(); - var result = await WorkspaceLoader.LoadAsync([tempFile], stdlibTable); - - // Assert - Assert.NotNull(result.Workspace); - Assert.Contains(result.Diagnostics, - d => d.Severity == DemaConsulting.SysML2Tools.Parser.DiagnosticSeverity.Warning && - d.Message.Contains("thisIdentifierDoesNotExistAnywhere")); + var view = Assert.IsType( + result.Workspace!.Declarations["P::V"]); + Assert.Equal("asTreeDiagram", view.RenderTargetName); + Assert.Empty(result.Diagnostics); Assert.DoesNotContain(result.Workspace!.Index.AllEdges, - e => e.Kind == DemaConsulting.SysML2Tools.Semantic.Model.SysmlEdgeKind.Render); + e => e.SourceQualifiedName == "P::V"); } finally { diff --git a/test/DemaConsulting.SysML2Tools.Tool.Tests/Render/RenderSubsystemTests.cs b/test/DemaConsulting.SysML2Tools.Tool.Tests/Render/RenderSubsystemTests.cs index 18b0fef2..6489eb76 100644 --- a/test/DemaConsulting.SysML2Tools.Tool.Tests/Render/RenderSubsystemTests.cs +++ b/test/DemaConsulting.SysML2Tools.Tool.Tests/Render/RenderSubsystemTests.cs @@ -533,40 +533,42 @@ public async Task RenderSubsystem_MultipleViews_WithViewFlag_RendersSelectedView } /// - /// End-to-end regression test for the reported bug: a workspace with two views — one - /// with a render <target>; body statement naming a resolvable target, and one - /// with a bogus render thisIdentifierDoesNotExistAnywhere; — must now produce two - /// visibly DIFFERENT rendered outputs (before this fix, every view rendered the identical - /// full-workspace diagram regardless of its render statement), and the bogus view's - /// unresolved render target must surface a diagnostic in the captured log output. + /// End-to-end regression test: a workspace with two views — one with an + /// expose <target>; body statement naming a resolvable target, and one with a + /// bogus expose thisIdentifierDoesNotExistAnywhere; — must produce two visibly + /// DIFFERENT rendered outputs (a view with no expose edges renders the full + /// workspace, while a view whose sole expose entry resolves scopes to that target's + /// subtree), and the bogus view's unresolved exposed name must surface a diagnostic in the + /// captured log output. render <target>; plays no role in scoping — only + /// expose does, per the corrected semantics. /// [Fact] - public async Task RenderSubsystem_ViewsWithDistinctRenderTargets_ProduceDifferingOutputsAndDiagnostic() + public async Task RenderSubsystem_ViewsWithDistinctExposeTargets_ProduceDifferingOutputsAndDiagnostic() { - // Arrange: a workspace with two named render targets in disjoint subtrees, one view - // whose render statement resolves to "TargetA", and one whose render statement names a + // Arrange: a workspace with two named expose targets in disjoint subtrees, one view + // whose expose statement resolves to "TargetA", and one whose expose statement names a // nonexistent identifier. - const string sysmlWithRenderTargets = """ - package RenderScopeTest { + const string sysmlWithExposeTargets = """ + package ExposeScopeTest { part def TargetA { part childA1 : TargetA {} } part def TargetB { part childB1 : TargetB {} } - view def ViewValid { - render TargetA; + view ViewValid { + expose TargetA; } - view def ViewBogus { - render thisIdentifierDoesNotExistAnywhere; + view ViewBogus { + expose thisIdentifierDoesNotExistAnywhere; } } """; - var tempDir = Path.Combine(Path.GetTempPath(), $"render_scope_bug_{Guid.NewGuid():N}"); + var tempDir = Path.Combine(Path.GetTempPath(), $"expose_scope_bug_{Guid.NewGuid():N}"); Directory.CreateDirectory(tempDir); var tempFile = Path.Combine(tempDir, "model.sysml"); - await File.WriteAllTextAsync(tempFile, sysmlWithRenderTargets, TestContext.Current.CancellationToken); + await File.WriteAllTextAsync(tempFile, sysmlWithExposeTargets, TestContext.Current.CancellationToken); var outputDir = Path.Combine(tempDir, "out"); var originalOut = Console.Out; @@ -593,7 +595,7 @@ view def ViewBogus { var bogusContent = await File.ReadAllTextAsync(bogusSvg, TestContext.Current.CancellationToken); Assert.NotEqual(validContent, bogusContent); - // Assert: the bogus render target surfaces a visible diagnostic naming the unresolved + // Assert: the bogus exposed name surfaces a visible diagnostic naming the unresolved // identifier, rather than silently rendering everything with no signal. var logContent = await File.ReadAllTextAsync(logFile, TestContext.Current.CancellationToken); Assert.Contains("thisIdentifierDoesNotExistAnywhere", logContent); @@ -605,6 +607,87 @@ view def ViewBogus { } } + /// + /// Regression guard for the AstBuilder.VisitViewUsage capability addition: rendering + /// the real OMG corpus fixture + /// test/SysMLModels/OMG/validation/11-ViewAndViewpoint/11b-SafetyAndSecurityFeatureViews.sysml + /// with no --view filter must produce exactly 5 output files — the 2 view def + /// declarations (SafetyFeatureView, SafetyOrSecurityFeatureView) plus the 3 + /// named view usages (vehicleSafetyFeatureView, + /// vehicleMandatorySafetyFeatureView, + /// vehicleMandatorySafetyFeatureViewStandalone) — not just the 2 view defs + /// that were the only renderable declarations before VisitViewUsage was added. This + /// also confirms rendering this file produces no false unresolved-reference diagnostics for + /// its render asTreeDiagram;/render asElementTable; rendering-style members. + /// + [Fact] + public async Task RenderSubsystem_OmgSafetyFeatureViewsCorpus_RendersAllNamedViewUsages() + { + // Arrange: locate the real OMG corpus fixture relative to the test assembly's repo root. + var fixturePath = Path.Combine( + FindOmgModelsRoot(), + "validation", "11-ViewAndViewpoint", "11b-SafetyAndSecurityFeatureViews.sysml"); + Assert.True(File.Exists(fixturePath), $"Expected OMG corpus fixture at {fixturePath}"); + + var tempDir = Path.Combine(Path.GetTempPath(), $"omg_11b_views_{Guid.NewGuid():N}"); + Directory.CreateDirectory(tempDir); + var outputDir = Path.Combine(tempDir, "out"); + var originalOut = Console.Out; + try + { + using var outWriter = new StringWriter(); + Console.SetOut(outWriter); + + // Act: render without --view so every declared view is rendered. + var logFile = Path.Combine(tempDir, "output.log"); + using (var context = Context.Create( + ["render", "--log", logFile, "--output", outputDir, fixturePath])) + { + await Program.RunAsync(context); + + // Assert: exit code indicates success + Assert.Equal(0, context.ExitCode); + } + + // Assert: exactly 5 output files were produced — the 2 view defs plus the 3 named + // view usages, once VisitViewUsage surfaces them as renderable declarations. + var outputFiles = Directory.GetFiles(outputDir, "*.svg"); + Assert.Equal(5, outputFiles.Length); + + // Assert: no false "Unresolved reference" diagnostic for the rendering-style names. + var logContent = await File.ReadAllTextAsync(logFile, TestContext.Current.CancellationToken); + Assert.DoesNotContain("asTreeDiagram", logContent); + Assert.DoesNotContain("asElementTable", logContent); + } + finally + { + Console.SetOut(originalOut); + Directory.Delete(tempDir, recursive: true); + } + } + + /// + /// Walks upward from the test assembly's base directory to find the repository's + /// test/SysMLModels/OMG directory (mirroring the same idiom used by + /// OmgModelsTests.FindOmgModelsRoot), so the OMG corpus fixture can be located + /// regardless of the test runner's working directory. + /// + private static string FindOmgModelsRoot() + { + var dir = new DirectoryInfo(AppContext.BaseDirectory); + while (dir != null && !Directory.Exists(Path.Combine(dir.FullName, "test", "SysMLModels", "OMG"))) + { + dir = dir.Parent; + } + + if (dir == null) + { + throw new DirectoryNotFoundException("Cannot locate test/SysMLModels/OMG from test assembly location."); + } + + return Path.Combine(dir.FullName, "test", "SysMLModels", "OMG"); + } + /// /// 'render --help' now prints render-specific usage (a regression-proofing test for the /// command-aware help dispatch added alongside the 'help' command). From 11d5c7280eef692f51dca2644be625644cf5d521 Mon Sep 17 00:00:00 2001 From: Malcolm Nixon Date: Mon, 6 Jul 2026 12:28:04 -0400 Subject: [PATCH 3/6] fix(language): resolve bracketed-filter expose form in ExtractImportTarget expose ::**[]; is the dominant expose form in the real OMG corpus (11b-SafetyAndSecurityFeatureViews.sysml, 11a-View-Viewpoint.sysml, ViewsExample.sysml), but AstBuilder.ExtractImportTarget only checked namespaceImport.qualifiedName(), which is null for this grammar alternative (the qualified name is nested two levels deeper via namespaceImport -> filterPackage -> filterPackageImportDeclaration -> (membershipImport | namespaceImportDirect)). This silently dropped the exposed name with no diagnostic, leaving ExposedNames/Expose edges empty and causing GeneralViewLayoutStrategy.ResolveExposedScope to fall back to rendering the entire workspace -- reproducing the exact unscoped-rendering bug the view-scoped-rendering feature exists to fix, for the single most common real-world expose form. Fix ExtractImportTarget to descend into filterPackage() -> filterPackageImportDeclaration() and extract the qualified name from whichever of membershipImport()/namespaceImportDirect() is present there, in addition to the existing direct-child checks. Also fix a stale doc comment on ViewContext.ViewNode in ILayoutStrategy.cs that referenced a nonexistent Render edge kind -- only Expose edges exist. Test coverage: - WorkspaceLoaderTests: bracketed-filter expose, plain wildcard expose (regression), and the real OMG 11b fixture's vehicleMandatorySafetyFeatureViewStandalone view. - GeneralViewLayoutStrategyTests: confirms the OMG 11b fixture's bracketed-filter view now resolves an Expose edge and renders fewer boxes than the unscoped full workspace (excluding the unrelated AnnotationDefinitions::Safety/Security metadata definitions). Companion doc update: docs/design .../ast-builder.md now documents the bracketed-filter grammar path ExtractImportTarget handles. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../semantic/model/ast-builder.md | 15 +- .../Rendering/ILayoutStrategy.cs | 2 +- .../Semantic/Model/AstBuilder.cs | 29 ++++ .../Layout/GeneralViewLayoutStrategyTests.cs | 95 +++++++++++++ .../Semantic/WorkspaceLoaderTests.cs | 132 ++++++++++++++++++ 5 files changed, 268 insertions(+), 5 deletions(-) diff --git a/docs/design/sysml2-tools-language/semantic/model/ast-builder.md b/docs/design/sysml2-tools-language/semantic/model/ast-builder.md index 323c2cc6..dae1912d 100644 --- a/docs/design/sysml2-tools-language/semantic/model/ast-builder.md +++ b/docs/design/sysml2-tools-language/semantic/model/ast-builder.md @@ -131,10 +131,17 @@ helper (see below) against each `expose` member's wrapped `namespaceImport()`/ `ExtractImportTarget(NamespaceImportContext?, MembershipImportContext?)` is a shared helper extracted from `VisitImportRule`'s previously inline logic, returning the extracted qualified/dotted name text and whether the reference is a wildcard, for either the -namespace-import form (`qualifiedName::*`, always a wildcard) or the membership-import form -(`qualifiedName`, optionally `::**`). `VisitImportRule` and `ExtractExposedNames` both call this -one helper rather than duplicating the extraction logic, per the Copy-Paste Programming -anti-pattern guidance in coding-principles.md. +namespace-import form (`qualifiedName::*`, always a wildcard), the membership-import form +(`qualifiedName`, optionally `::**`), or the bracketed-filter form nested inside a +namespace-import (`qualifiedName::**[]`) — the dominant `expose` shape in the real +OMG corpus (e.g. `expose vehicle::**[@Safety];`). The grammar nests the qualified name two levels +deeper for that third form: `namespaceImport -> filterPackage -> filterPackageImportDeclaration -> +(membershipImport | namespaceImportDirect)`. `ExtractImportTarget` descends into +`filterPackage().filterPackageImportDeclaration()` and extracts the qualified name from whichever +of `membershipImport()`/`namespaceImportDirect()` is present there, rather than only checking the +direct `qualifiedName()` child (which is null for this alternative). `VisitImportRule` and +`ExtractExposedNames` both call this one helper rather than duplicating the extraction logic, per +the Copy-Paste Programming anti-pattern guidance in coding-principles.md. `VisitRequirementUsage` performs a minimal capture (name/qualified-name only, so named requirement usages become resolvable symbols) and additionally invokes `FindVerificationMembers` diff --git a/src/DemaConsulting.SysML2Tools.Core/Rendering/ILayoutStrategy.cs b/src/DemaConsulting.SysML2Tools.Core/Rendering/ILayoutStrategy.cs index 23623b78..9770aa7b 100644 --- a/src/DemaConsulting.SysML2Tools.Core/Rendering/ILayoutStrategy.cs +++ b/src/DemaConsulting.SysML2Tools.Core/Rendering/ILayoutStrategy.cs @@ -24,7 +24,7 @@ namespace DemaConsulting.SysML2Tools.Rendering; /// /// /// The resolved the view was declared from, carrying its resolved -/// Render/Expose edges and raw FilterExpressionText so a strategy can scope +/// Expose edges and raw FilterExpressionText so a strategy can scope /// its diagram accordingly. Nullable to preserve the --auto synthetic-view path, whose /// synthesized carries no render/expose/filter data; defaults to /// so existing two-argument construction call sites remain unchanged. diff --git a/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/AstBuilder.cs b/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/AstBuilder.cs index dce640da..f21a8805 100644 --- a/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/AstBuilder.cs +++ b/src/DemaConsulting.SysML2Tools.Language/Semantic/Model/AstBuilder.cs @@ -1018,6 +1018,35 @@ private static (string? QualifiedName, bool IsWildcard) ExtractImportTarget( { return (qn, true); } + + // Bracketed-filter form: qualifiedName::**[filterExpr] — the dominant expose form in + // the real OMG corpus. The grammar nests the qualified name two levels deeper here: + // namespaceImport -> filterPackage -> filterPackageImportDeclaration -> (membershipImport + // | namespaceImportDirect). Descend through that chain rather than only checking the + // direct qualifiedName() child (which is null for this alternative). + var filterDecl = namespaceImport.filterPackage()?.filterPackageImportDeclaration(); + if (filterDecl is not null) + { + var filterMembershipImport = filterDecl.membershipImport(); + if (filterMembershipImport is not null) + { + var filterQn = filterMembershipImport.qualifiedName()?.GetText(); + if (filterQn is { Length: > 0 }) + { + return (filterQn, filterMembershipImport.STAR_STAR() is not null); + } + } + + var namespaceImportDirect = filterDecl.namespaceImportDirect(); + if (namespaceImportDirect is not null) + { + var directQn = namespaceImportDirect.qualifiedName()?.GetText(); + if (directQn is { Length: > 0 }) + { + return (directQn, true); + } + } + } } // Membership import: qualifiedName (optional ::**) diff --git a/test/DemaConsulting.SysML2Tools.Tests/Layout/GeneralViewLayoutStrategyTests.cs b/test/DemaConsulting.SysML2Tools.Tests/Layout/GeneralViewLayoutStrategyTests.cs index 36df68f4..03851be4 100644 --- a/test/DemaConsulting.SysML2Tools.Tests/Layout/GeneralViewLayoutStrategyTests.cs +++ b/test/DemaConsulting.SysML2Tools.Tests/Layout/GeneralViewLayoutStrategyTests.cs @@ -8,6 +8,7 @@ using DemaConsulting.SysML2Tools.Rendering; using DemaConsulting.SysML2Tools.Semantic; using DemaConsulting.SysML2Tools.Semantic.Model; +using DemaConsulting.SysML2Tools.Stdlib; namespace DemaConsulting.SysML2Tools.Tests.Layout; @@ -953,6 +954,100 @@ public void GeneralViewLayoutStrategy_BuildLayout_NullViewNode_RendersFullWorksp Assert.Empty(layout.Warnings); } + /// + /// Regression guard for the bracketed-filter expose parsing bug: + /// vehicleMandatorySafetyFeatureViewStandalone in the real OMG corpus fixture + /// 11b-SafetyAndSecurityFeatureViews.sysml declares + /// expose vehicle::**[@Safety and (as Safety).isMandatory]; — the bracketed-filter + /// grammar form that AstBuilder.ExtractImportTarget previously failed to descend + /// into, leaving the view with zero Expose edges and causing + /// to silently fall back to rendering the entire + /// workspace. After the fix, this view's layout must be scoped to the vehicle + /// subtree — which, in this fixture, contains no part def declarations (only + /// usages), so the correctly-scoped rendering drops to zero boxes, strictly fewer than the + /// unscoped full-workspace rendering (which still includes the unrelated + /// AnnotationDefinitions::Safety/Security metadata definitions). Before the + /// fix, scoped and full box counts were identical (both rendered everything). + /// + // cspell:ignore Feaure -- typo present verbatim in the real OMG corpus fixture's package name + [Fact] + public async Task GeneralViewLayoutStrategy_BuildLayout_OmgSafetyFeatureViewsFixture_ScopesToExposedVehicleSubtree() + { + // Arrange: load the real OMG corpus fixture. + var modelsRoot = FindSysMLModelsRoot(); + if (modelsRoot is null) + { + return; + } + + var fixturePath = Path.Combine( + modelsRoot, "OMG", "validation", "11-ViewAndViewpoint", "11b-SafetyAndSecurityFeatureViews.sysml"); + if (!File.Exists(fixturePath)) + { + return; + } + + var (stdlibTable, _) = StdlibProvider.GetSymbolTable(); + var result = await WorkspaceLoader.LoadAsync([fixturePath], stdlibTable); + Assert.NotNull(result.Workspace); + var workspace = result.Workspace!; + + const string viewQualifiedName = + "'11b-Safety and Security Feaure Views'::Views::vehicleMandatorySafetyFeatureViewStandalone"; + var viewNode = Assert.IsType(workspace.Declarations[viewQualifiedName]); + + // Confirm the fix actually resolved an Expose edge before asserting on layout scoping — + // otherwise this test would pass vacuously by comparing full-workspace to full-workspace. + Assert.NotEmpty(viewNode.ExposedNames); + Assert.Contains(workspace.Index.AllEdges, + e => e.Kind == SysmlEdgeKind.Expose && e.SourceQualifiedName == viewQualifiedName); + + var strategy = new GeneralViewLayoutStrategy(); + var options = new RenderOptions(Themes.Light); + + // Act + var scoped = strategy.BuildLayout(new ViewContext("scoped", workspace, viewNode), options); + var full = strategy.BuildLayout(new ViewContext("full", workspace), options); + + // Assert: the scoped view renders strictly fewer boxes than the full workspace. In this + // fixture the `vehicle` subtree is built entirely from part *usages* (not `part def` + // declarations), which `GeneralViewLayoutStrategy.CollectDefinitions` does not render as + // boxes — so the correctly-scoped view renders zero boxes here, while the unscoped full + // workspace still renders the two unrelated `AnnotationDefinitions` metadata definitions + // (`Safety`, `Security`). That drop from 2 boxes to 0 is itself the regression signal: + // before the fix, `ResolveExposedScope` saw no `Expose` edges and fell back to rendering + // the entire workspace (i.e. scoped would equal full, not be strictly smaller). + var scopedBoxes = CollectBoxes(scoped.Nodes); + var fullBoxes = CollectBoxes(full.Nodes); + Assert.True(scopedBoxes.Count < fullBoxes.Count, + $"expected scoped box count ({scopedBoxes.Count}) < full box count ({fullBoxes.Count})"); + var fullLabels = fullBoxes.Select(b => b.Label).ToList(); + Assert.Contains("Safety", fullLabels); + Assert.Contains("Security", fullLabels); + var scopedLabels = scopedBoxes.Select(b => b.Label).ToList(); + Assert.DoesNotContain("Safety", scopedLabels); + Assert.DoesNotContain("Security", scopedLabels); + } + + /// + /// Finds the test/SysMLModels directory relative to the test assembly. + /// + private static string? FindSysMLModelsRoot() + { + var dir = AppContext.BaseDirectory; + while (dir is not null) + { + var candidate = Path.Combine(dir, "test", "SysMLModels"); + if (Directory.Exists(candidate)) + { + return candidate; + } + dir = Directory.GetParent(dir)?.FullName; + } + + return null; + } + /// /// Asserts that no two rendered definition (rectangle-shaped) boxes overlap in the layout. /// diff --git a/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs b/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs index 89132be8..131f6160 100644 --- a/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs +++ b/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs @@ -2519,6 +2519,138 @@ view V { } } + /// + /// A view with the bracketed-filter expose <ns>::**[<filterExpr>]; form — + /// the dominant expose shape in the real OMG corpus (e.g. + /// expose vehicle::**[@Safety]; in 11b-SafetyAndSecurityFeatureViews.sysml) — + /// must resolve the exposed name into an + /// edge. This + /// grammar form nests the qualified name two levels deeper than the plain form + /// (namespaceImport -> filterPackage -> filterPackageImportDeclaration -> + /// membershipImport), which AstBuilder.ExtractImportTarget previously did not + /// descend into, silently dropping the exposed name with no diagnostic. + /// + [Fact] + public async Task WorkspaceLoader_LoadAsync_ViewUsageWithBracketedFilterExpose_RecordsExposeEdge() + { + // Arrange + var tempFile = Path.GetTempFileName() + ".sysml"; + try + { + await File.WriteAllTextAsync(tempFile, """ + package P { + metadata def Safety; + part def Exposed {} + view V { + expose Exposed::**[@Safety]; + } + } + """, TestContext.Current.CancellationToken); + + // Act + var (stdlibTable, _) = StdlibProvider.GetSymbolTable(); + var result = await WorkspaceLoader.LoadAsync([tempFile], stdlibTable); + + // Assert + Assert.NotNull(result.Workspace); + var view = Assert.IsType( + result.Workspace!.Declarations["P::V"]); + Assert.Contains("Exposed", view.ExposedNames); + Assert.Contains(result.Workspace!.Index.AllEdges, + e => e.Kind == DemaConsulting.SysML2Tools.Semantic.Model.SysmlEdgeKind.Expose && + e.SourceQualifiedName == "P::V" && + e.TargetQualifiedName == "P::Exposed"); + } + finally + { + File.Delete(tempFile); + } + } + + /// + /// Regression guard for the plain (non-bracketed) wildcard expose <ns>::*::**; + /// form — the sibling grammar shape to the bracketed-filter form above — confirming it + /// still resolves correctly after the ExtractImportTarget fix that added support for + /// descending into filterPackage(). + /// + [Fact] + public async Task WorkspaceLoader_LoadAsync_ViewUsageWithPlainWildcardExpose_RecordsExposeEdge() + { + // Arrange + var tempFile = Path.GetTempFileName() + ".sysml"; + try + { + await File.WriteAllTextAsync(tempFile, """ + package P { + part def Exposed {} + view V { + expose Exposed::*::**; + } + } + """, TestContext.Current.CancellationToken); + + // Act + var (stdlibTable, _) = StdlibProvider.GetSymbolTable(); + var result = await WorkspaceLoader.LoadAsync([tempFile], stdlibTable); + + // Assert + Assert.NotNull(result.Workspace); + var view = Assert.IsType( + result.Workspace!.Declarations["P::V"]); + Assert.Contains("Exposed", view.ExposedNames); + Assert.Contains(result.Workspace!.Index.AllEdges, + e => e.Kind == DemaConsulting.SysML2Tools.Semantic.Model.SysmlEdgeKind.Expose && + e.SourceQualifiedName == "P::V" && + e.TargetQualifiedName == "P::Exposed"); + } + finally + { + File.Delete(tempFile); + } + } + + /// + /// Loading the real OMG corpus fixture + /// 11b-SafetyAndSecurityFeatureViews.sysml must resolve + /// vehicleMandatorySafetyFeatureViewStandalone's bracketed-filter + /// expose vehicle::**[@Safety and (as Safety).isMandatory]; member into a non-empty + /// ExposedNames list and a resolved Expose edge to vehicle — the exact + /// scenario confirmed broken (empty ExposedNames, zero edges, no diagnostic) before + /// the ExtractImportTarget fix. + /// + // cspell:ignore Feaure -- typo present verbatim in the real OMG corpus fixture's package name + [Fact] + public async Task WorkspaceLoader_LoadAsync_OmgSafetyFeatureViewsFixture_ResolvesBracketedExpose() + { + // Arrange + var modelsRoot = FindSysMLModelsRoot(); + if (modelsRoot is null) + { + return; + } + + var fixturePath = Path.Combine( + modelsRoot, "OMG", "validation", "11-ViewAndViewpoint", "11b-SafetyAndSecurityFeatureViews.sysml"); + if (!File.Exists(fixturePath)) + { + return; + } + + // Act + var (stdlibTable, _) = StdlibProvider.GetSymbolTable(); + var result = await WorkspaceLoader.LoadAsync([fixturePath], stdlibTable); + + // Assert + Assert.NotNull(result.Workspace); + var view = Assert.IsType( + result.Workspace!.Declarations["'11b-Safety and Security Feaure Views'::Views::vehicleMandatorySafetyFeatureViewStandalone"]); + Assert.NotEmpty(view.ExposedNames); + Assert.Contains(result.Workspace!.Index.AllEdges, + e => e.Kind == DemaConsulting.SysML2Tools.Semantic.Model.SysmlEdgeKind.Expose && + e.SourceQualifiedName == "'11b-Safety and Security Feaure Views'::Views::vehicleMandatorySafetyFeatureViewStandalone" && + e.TargetQualifiedName == "'11b-Safety and Security Feaure Views'::PartsTree::vehicle"); + } + /// /// A view def with an empty body should leave /// and From bdc17ef9b42e92192058ccae900cfe23b752ef7d Mon Sep 17 00:00:00 2001 From: Malcolm Nixon Date: Mon, 6 Jul 2026 13:18:05 -0400 Subject: [PATCH 4/6] Split compound view render/filter/expose requirements (REQ-07) Split three compound requirements flagged by formal review into single-behavior requirements per requirements-principles.md 'no compound requirements' rule: - AstBuilder-ViewRenderFilter -> AstBuilder-ViewRenderTarget, AstBuilder-ViewFilterExpression, AstBuilder-ViewExposedNames - ReferenceResolver-ViewExpose -> ReferenceResolver-ViewExposeResolution, ReferenceResolver-ViewExposeUnresolvedWarning, ReferenceResolver-ViewRenderFilterNeverResolved - SysmlNode-ViewRenderExposeFilter -> SysmlNode-ViewRenderTarget, SysmlNode-ViewExposedNames, SysmlNode-ViewFilterExpression Also fixes a line-length lint violation introduced by the ReferenceResolver row edit in semantic/model.md, and includes pre-existing uncommitted working-tree fixes from a prior formal-review pass (README/design docs/user guide wording corrections, and a WorkspaceLoaderTests temp-file leak fix). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 11 ++-- docs/design/sysml2-tools-core.md | 3 +- docs/design/sysml2-tools-core/rendering.md | 17 ++++-- docs/design/sysml2-tools-language/semantic.md | 5 +- .../sysml2-tools-language/semantic/model.md | 2 +- .../semantic/model/sysml-edge.md | 4 +- .../semantic/model/sysml-node.md | 3 +- .../semantic/model/ast-builder.yaml | 42 +++++++++++--- .../semantic/model/reference-resolver.yaml | 57 +++++++++++++------ .../semantic/model/sysml-node.yaml | 41 +++++++++---- docs/user_guide/introduction.md | 10 ++-- .../Semantic/WorkspaceLoaderTests.cs | 12 ++-- 12 files changed, 142 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index 21875a95..686f836d 100644 --- a/README.md +++ b/README.md @@ -225,11 +225,12 @@ section above). | Multiple views, `--view ` | Render only the named view | | `--view ` names a view that does not exist | Error: lists available view names and exits non-zero | -A view's body `expose <...>;` statements now scope the rendered diagram to the union of the -exposed names' containment subtrees, instead of always rendering the full workspace; a view with -no `expose` statement continues to render the full workspace. `render ;` names a -rendering style (not yet honored) and has no effect on scope — see the *Introduction* user guide -for details. +For the General View diagram strategy (the diagram produced when no more specialized view kind +applies), a view's body `expose <...>;` statements now scope the rendered diagram to the union of +the exposed names' containment subtrees, instead of always rendering the full workspace; a view +with no `expose` statement continues to render the full workspace. `render ;` names a +rendering style (not yet honored) and has no effect on scope. This scoping is not yet extended to +the other layout strategies — see the *Introduction* user guide for details. ## NuGet Packages diff --git a/docs/design/sysml2-tools-core.md b/docs/design/sysml2-tools-core.md index 321c7694..bb2a8a69 100644 --- a/docs/design/sysml2-tools-core.md +++ b/docs/design/sysml2-tools-core.md @@ -99,7 +99,8 @@ N/A — not a safety-classified software item. 1. `DiagramRenderer.RenderWorkspace` receives a `SysmlWorkspace`, an `IRenderer`, and `RenderOptions`. For each view declared in the workspace it constructs a `ViewContext` - containing the view name and workspace reference. + containing the view name, workspace reference, and (when available) the view's resolved + AST node. 2. `ILayoutStrategy.BuildLayout` is called with the `ViewContext` and `RenderOptions`. The Layout subsystem produces a fully resolved `LayoutTree` by delegating geometric placement and routing to the off-the-shelf `DemaConsulting.Rendering.Layout` layered algorithm (through the diff --git a/docs/design/sysml2-tools-core/rendering.md b/docs/design/sysml2-tools-core/rendering.md index e498a2d8..72ded57b 100644 --- a/docs/design/sysml2-tools-core/rendering.md +++ b/docs/design/sysml2-tools-core/rendering.md @@ -55,9 +55,12 @@ flowchart TD - *Role*: Data transfer object. - *Contract*: `string ViewName`, `SysmlWorkspace Workspace`, `SysmlViewNode? ViewNode = null`. `ViewNode` is the view's resolved AST node, giving a layout strategy access to the view's - declared `render`/`expose`/`filter` body statements (`RenderTargetName`, `ExposedNames`, - `FilterExpressionText`, and their `ResolvedEdges`); it is `null` for the `--auto` synthesized - view, which carries no AST node of its own. + declared `render`/`expose`/`filter` body statements. Of these, only `ExposedNames` (and its + resolved `Expose` edges) drives content scoping; `RenderTargetName` names a rendering + style/format per the SysML v2 grammar and is captured as inert metadata only (it never + affects scope or resolution); `FilterExpressionText` is captured as raw text and not yet + evaluated. `ViewNode` is `null` for the `--auto` synthesized view, which carries no AST node + of its own. **Theme**: Visual configuration record. @@ -135,8 +138,12 @@ flowchart TD name, and (when available) the view's resolved AST node, plus `RenderOptions` for size and scale hints. It produces a fully resolved `LayoutTree` with all waypoints in absolute canvas coordinates. `GeneralViewLayoutStrategy` is the only strategy that currently reads - `ViewContext.ViewNode` to scope its diagram to a declared render target's subtree; every - other strategy ignores it and renders as before (see the Layout subsystem's + `ViewContext.ViewNode` to scope its diagram: when the view has one or more resolved `Expose` + edges, the diagram is scoped to the union of the exposed targets' containment subtrees + (resolving through a usage's type to its definition's subtree where needed); a view with no + `Expose` edges renders the full workspace, unchanged from prior behavior. The view's + `render`/`filter` statements never affect this scope. Every other strategy ignores + `ViewContext.ViewNode` and renders as before (see the Layout subsystem's `general-view-layout-strategy` design doc for the scoping algorithm). 3. `IRenderer.Render` receives the `LayoutTree` and `RenderOptions` and writes all rendered diff --git a/docs/design/sysml2-tools-language/semantic.md b/docs/design/sysml2-tools-language/semantic.md index ec118328..3b60944c 100644 --- a/docs/design/sysml2-tools-language/semantic.md +++ b/docs/design/sysml2-tools-language/semantic.md @@ -57,8 +57,9 @@ optionally seeded with a pre-populated symbol table. - *Role*: Data container. - *Contract*: Exposes `IReadOnlyList Files`, `IReadOnlySet StdlibNames`, `IReadOnlyDictionary Declarations` mapping qualified names to declaration - nodes, and `SemanticIndex Index` — a reverse-lookup index over resolved supertype, typing, - and import edges (see Semantic Model Subsystem). + nodes, and `SemanticIndex Index` — a reverse-lookup index over all resolved edge kinds + (supertype, typing, import, satisfy, verify, allocate, connect, transition, and expose; see + Semantic Model Subsystem). ### Design diff --git a/docs/design/sysml2-tools-language/semantic/model.md b/docs/design/sysml2-tools-language/semantic/model.md index d2f2e76f..3048f3b8 100644 --- a/docs/design/sysml2-tools-language/semantic/model.md +++ b/docs/design/sysml2-tools-language/semantic/model.md @@ -57,7 +57,7 @@ over resolved edges. | --- | --- | | `AstBuilder` | Visits ANTLR4 CST; builds typed AST nodes with qualified names and supertype lists | | `SymbolTable` | Registry mapping fully-qualified names to their AST nodes | -| `ReferenceResolver` | Resolves supertype/typing/import/satisfy/verify/allocate/connect/transition refs; builds index | +| `ReferenceResolver` | Resolves supertype/typing/import/satisfy/verify/allocate/connect/transition/expose refs | | `SupertypeWalker` | Walks specialization chains; detects cyclic specialization | | `SysmlNode` | Public abstract base record (and subtypes) modeling one parsed AST element | | `SysmlEdge` | Public record modeling one resolved reference (Supertype/Typing/Import/Satisfy/Verify/Allocate/etc.) | diff --git a/docs/design/sysml2-tools-language/semantic/model/sysml-edge.md b/docs/design/sysml2-tools-language/semantic/model/sysml-edge.md index dd6b3f98..d32f695a 100644 --- a/docs/design/sysml2-tools-language/semantic/model/sysml-edge.md +++ b/docs/design/sysml2-tools-language/semantic/model/sysml-edge.md @@ -5,11 +5,11 @@ `SysmlEdge` and `SysmlEdgeKind` model a single resolved directed reference between two qualified names in the semantic model. Edges are produced by `ReferenceResolver` while walking supertype, feature-typing, import, satisfy, verify, allocate, connect, transition, -render, and expose references, and are the raw material indexed by `SemanticIndex`. +and expose references, and are the raw material indexed by `SemanticIndex`. ##### Types -`SysmlEdgeKind` is an enum with ten members: +`SysmlEdgeKind` is an enum with nine members: - `Supertype` — a specialization reference (`SupertypeNames` / `specializes` / `:>`). - `Typing` — a feature typing reference (`SysmlFeatureNode.FeatureTyping`, the type after `:`). diff --git a/docs/design/sysml2-tools-language/semantic/model/sysml-node.md b/docs/design/sysml2-tools-language/semantic/model/sysml-node.md index d0fbca28..b31fbfb9 100644 --- a/docs/design/sysml2-tools-language/semantic/model/sysml-node.md +++ b/docs/design/sysml2-tools-language/semantic/model/sysml-node.md @@ -42,7 +42,8 @@ All nodes carry: sourced from this node — there is no standalone verify-usage node type; this list is the sole producer of `Verify` edges. - `ResolvedEdges` — resolved outgoing `SysmlEdge` entries (supertype, typing, import, satisfy, - verify, allocate), populated post-construction by `ReferenceResolver`; a settable (not `init`) + verify, allocate, connect, transition, expose), populated post-construction by + `ReferenceResolver`; a settable (not `init`) property since resolution runs after the AST is built and the symbol table is fully populated. Empty for stdlib-only nodes, which are registered but never passed through `ReferenceResolver.ResolveAll`. diff --git a/docs/reqstream/sysml2-tools-language/semantic/model/ast-builder.yaml b/docs/reqstream/sysml2-tools-language/semantic/model/ast-builder.yaml index ab9a7bbc..7693db5d 100644 --- a/docs/reqstream/sysml2-tools-language/semantic/model/ast-builder.yaml +++ b/docs/reqstream/sysml2-tools-language/semantic/model/ast-builder.yaml @@ -39,23 +39,47 @@ sections: - WorkspaceLoader_LoadAsync_SpecializesChain_Registered - WorkspaceLoader_LoadAsync_UnresolvedReference_ProducesWarning - - id: SysML2Tools-Language-Semantic-Model-AstBuilder-ViewRenderFilter + - id: SysML2Tools-Language-Semantic-Model-AstBuilder-ViewRenderTarget title: >- AstBuilder shall capture a view's render target reference text (from its - `render ;` member), its filter expression source text (from its - `filter [];` member), and its exposed name references (from its - `expose ;` members, `view` usages only) as raw, unevaluated data on the - corresponding SysmlViewNode. + `render ;` member) as raw, unevaluated data on the corresponding + SysmlViewNode's RenderTargetName property. justification: | - Capturing this data during AST construction is the prerequisite for ReferenceResolver - to resolve the `expose` references into edges that GeneralViewLayoutStrategy uses for - scoping. `RenderTargetName` names a rendering style/format, not content, so it is - captured verbatim only and never drives scoping or resolution. + `RenderTargetName` names a rendering style/format, not content, so it is captured + verbatim only and never drives scoping or resolution. tests: - WorkspaceLoader_LoadAsync_ViewRenderTarget_CapturedRawNeverResolvedNoDiagnostic + - WorkspaceLoader_LoadAsync_ViewEmptyBody_AllNewFieldsNullOrEmpty + + - id: SysML2Tools-Language-Semantic-Model-AstBuilder-ViewFilterExpression + title: >- + AstBuilder shall capture a view's filter expression source text (from its + `filter [];` member) as raw, unevaluated data on the corresponding + SysmlViewNode's FilterExpressionText property. + justification: | + Full filter expression evaluation is deferred future work; capturing the raw source + text during AST construction preserves the information for a future capability + without requiring premature expression-evaluation logic in this unit. + tests: - WorkspaceLoader_LoadAsync_ViewFilterExpression_CapturesTextVerbatimNoEdge - WorkspaceLoader_LoadAsync_ViewEmptyBody_AllNewFieldsNullOrEmpty + - id: SysML2Tools-Language-Semantic-Model-AstBuilder-ViewExposedNames + title: >- + AstBuilder shall capture the raw reference text of each `expose ;` member + (`view` usages only, in source order) on the corresponding SysmlViewNode's + ExposedNames property. + justification: | + Capturing this data during AST construction is the prerequisite for ReferenceResolver + to resolve the `expose` references into edges that GeneralViewLayoutStrategy uses for + scoping. `expose` is only valid grammar inside a `view` usage's body, not a `view def` + definition's body. + tests: + - WorkspaceLoader_LoadAsync_ViewUsageWithExpose_RecordsExposeEdge + - WorkspaceLoader_LoadAsync_ViewUsageWithBracketedFilterExpose_RecordsExposeEdge + - WorkspaceLoader_LoadAsync_ViewUsageWithPlainWildcardExpose_RecordsExposeEdge + - WorkspaceLoader_LoadAsync_ViewEmptyBody_AllNewFieldsNullOrEmpty + - id: SysML2Tools-Language-Semantic-Model-AstBuilder-ViewUsageExpose title: >- AstBuilder shall build a SysmlViewNode from a named `view` usage (in addition to a diff --git a/docs/reqstream/sysml2-tools-language/semantic/model/reference-resolver.yaml b/docs/reqstream/sysml2-tools-language/semantic/model/reference-resolver.yaml index ce6cf30c..712fa961 100644 --- a/docs/reqstream/sysml2-tools-language/semantic/model/reference-resolver.yaml +++ b/docs/reqstream/sysml2-tools-language/semantic/model/reference-resolver.yaml @@ -27,26 +27,49 @@ sections: tests: - WorkspaceLoader_LoadAsync_CircularImport_ProducesWarningNoInfiniteLoop - - id: SysML2Tools-Language-Semantic-Model-ReferenceResolver-ViewExpose + - id: SysML2Tools-Language-Semantic-Model-ReferenceResolver-ViewExposeResolution title: >- - ReferenceResolver.ResolveAll shall resolve each of a view's ExposedNames into an - Expose-kind edge, and shall produce an unresolved-reference Warning diagnostic (with no - corresponding edge) when an entry cannot resolve — mirroring the existing - SubjectName/EndpointA unresolved-reference diagnostic pattern. RenderTargetName names a - rendering style/format per the SysML v2 grammar (e.g. `asTreeDiagram`, - `asElementTable`), never model content, and is raw metadata that ReferenceResolver never - inspects: no edge is produced and no diagnostic is emitted for it. FilterExpressionText - is likewise raw source text, not a reference, and is never inspected here. + ReferenceResolver.ResolveAll shall resolve each of a view's ExposedNames entries into + an Expose-kind edge. justification: | `expose ;` is the only reference a view's body statements resolve into model - content; a `render ;` member never refers to content per the SysML v2 grammar - (confirmed against the OMG test corpus, where every `render` member names a - rendering-style identifier such as `asTreeDiagram`), so attempting to resolve it as a - reference could only ever produce a false "Unresolved reference" diagnostic or - coincidentally match an unrelated stdlib symbol of the same name. Restricting resolution - to ExposedNames removes that false-diagnostic risk while preserving the genuine - unresolved-reference signal for a bogus `expose` entry. + content; resolving each ExposedNames entry into an Expose-kind edge is the + prerequisite for GeneralViewLayoutStrategy to scope a rendered diagram to that content. tests: - - WorkspaceLoader_LoadAsync_ViewRenderTarget_CapturedRawNeverResolvedNoDiagnostic - WorkspaceLoader_LoadAsync_ViewUsageWithExpose_RecordsExposeEdge + - WorkspaceLoader_LoadAsync_ViewUsageWithBracketedFilterExpose_RecordsExposeEdge + - WorkspaceLoader_LoadAsync_ViewUsageWithPlainWildcardExpose_RecordsExposeEdge + - WorkspaceLoader_LoadAsync_OmgSafetyFeatureViewsFixture_ResolvesBracketedExpose + - RenderSubsystem_ViewsWithDistinctExposeTargets_ProduceDifferingOutputsAndDiagnostic + + - id: SysML2Tools-Language-Semantic-Model-ReferenceResolver-ViewExposeUnresolvedWarning + title: >- + ReferenceResolver.ResolveAll shall produce an unresolved-reference Warning diagnostic + (with no corresponding edge) when a view's ExposedNames entry cannot resolve — + mirroring the existing SubjectName/EndpointA unresolved-reference diagnostic pattern. + justification: | + A bogus `expose ;` entry must be surfaced to the caller as model incompleteness, + the same way an unresolved supertype or import name is surfaced, so the diagnostic + signal for the render subsystem is genuinely useful. + tests: - RenderSubsystem_ViewsWithDistinctExposeTargets_ProduceDifferingOutputsAndDiagnostic + + - id: SysML2Tools-Language-Semantic-Model-ReferenceResolver-ViewRenderFilterNeverResolved + title: >- + ReferenceResolver.ResolveAll shall never inspect or resolve a view's RenderTargetName + or FilterExpressionText: no edge is produced and no diagnostic is emitted for either, + regardless of content. + justification: | + RenderTargetName names a rendering style/format per the SysML v2 grammar (e.g. + `asTreeDiagram`, `asElementTable`), never model content — attempting to resolve it as a + reference could only ever produce a false "Unresolved reference" diagnostic or + coincidentally match an unrelated stdlib symbol of the same name (confirmed against the + OMG test corpus, where every `render` member names a rendering-style identifier). + FilterExpressionText is likewise raw source text, not a reference, and full filter + expression evaluation is deferred future work. Excluding both from resolution removes + that false-diagnostic risk while preserving the genuine unresolved-reference signal for + a bogus `expose` entry. + tests: + - WorkspaceLoader_LoadAsync_ViewRenderTarget_CapturedRawNeverResolvedNoDiagnostic + - WorkspaceLoader_LoadAsync_ViewFilterExpression_CapturesTextVerbatimNoEdge + - RenderSubsystem_OmgSafetyFeatureViewsCorpus_RendersAllNamedViewUsages diff --git a/docs/reqstream/sysml2-tools-language/semantic/model/sysml-node.yaml b/docs/reqstream/sysml2-tools-language/semantic/model/sysml-node.yaml index 5d1988ff..b8664419 100644 --- a/docs/reqstream/sysml2-tools-language/semantic/model/sysml-node.yaml +++ b/docs/reqstream/sysml2-tools-language/semantic/model/sysml-node.yaml @@ -29,21 +29,38 @@ sections: tests: - WorkspaceLoader_LoadAsync_PartDef_RegistersDefinition - - id: SysML2Tools-Language-Semantic-Model-SysmlNode-ViewRenderExposeFilter + - id: SysML2Tools-Language-Semantic-Model-SysmlNode-ViewRenderTarget title: >- - SysmlViewNode shall expose RenderTargetName (the raw reference text of its `render - ;` member, or null when absent), ExposedNames (the raw reference text of - each `expose ;` member, empty when absent), and FilterExpressionText (the raw - source text of its `filter [];` member, or null when absent). + SysmlViewNode shall expose RenderTargetName: the raw reference text of its `render + ;` member, or null when absent. justification: | - These three fields make a view's declared body statements user-observable data, not - merely an internal implementation detail. RenderTargetName is exposed as raw metadata - only: per the SysML v2 grammar it names a rendering style/format (e.g. - `asTreeDiagram`, `asElementTable`), never content, so it is never resolved into an edge - and has no effect on scope. ExposedNames is the field ReferenceResolver resolves into - `Expose`-kind edges that GeneralViewLayoutStrategy uses for scoping. + RenderTargetName is exposed as raw metadata only: per the SysML v2 grammar it names a + rendering style/format (e.g. `asTreeDiagram`, `asElementTable`), never content, so it + is captured but never resolved into an edge and has no effect on scope. tests: - WorkspaceLoader_LoadAsync_ViewRenderTarget_CapturedRawNeverResolvedNoDiagnostic - - WorkspaceLoader_LoadAsync_ViewFilterExpression_CapturesTextVerbatimNoEdge + - WorkspaceLoader_LoadAsync_ViewEmptyBody_AllNewFieldsNullOrEmpty + + - id: SysML2Tools-Language-Semantic-Model-SysmlNode-ViewExposedNames + title: >- + SysmlViewNode shall expose ExposedNames: the raw reference text of each `expose + ;` member, empty when absent. + justification: | + Making a view's `expose` statements user-observable data is the prerequisite for + ReferenceResolver, which resolves ExposedNames into `Expose`-kind edges that + GeneralViewLayoutStrategy uses for scoping. + tests: - WorkspaceLoader_LoadAsync_ViewUsageWithExpose_RecordsExposeEdge - WorkspaceLoader_LoadAsync_ViewEmptyBody_AllNewFieldsNullOrEmpty + + - id: SysML2Tools-Language-Semantic-Model-SysmlNode-ViewFilterExpression + title: >- + SysmlViewNode shall expose FilterExpressionText: the raw source text of its `filter + [];` member, or null when absent. + justification: | + Making a view's declared filter expression user-observable data is required so + callers can observe it as raw source text; full filter expression evaluation is + deferred future work and is out of scope for this field. + tests: + - WorkspaceLoader_LoadAsync_ViewFilterExpression_CapturesTextVerbatimNoEdge + - WorkspaceLoader_LoadAsync_ViewEmptyBody_AllNewFieldsNullOrEmpty diff --git a/docs/user_guide/introduction.md b/docs/user_guide/introduction.md index 846be3bc..1574e21d 100644 --- a/docs/user_guide/introduction.md +++ b/docs/user_guide/introduction.md @@ -110,10 +110,12 @@ sysml2tools render model.sysml --auto --output out --format svg ## View Body Statements -A `view def`/`view` declaration's body may contain `render ;`, `expose ;`, and -`filter [];` statements. For the General View strategy (the diagram produced when no -more specialized view kind applies), `expose` now scopes the rendered diagram instead of always -rendering the entire workspace: +A `view def`/`view` declaration's body may contain `render ;` and `filter [];` +statements; a named `view` usage's body may additionally contain `expose ;` statements +(per the SysML v2 grammar, `expose` is only valid inside a `view` usage's body, not a `view def` +definition's body). For the General View strategy (the diagram produced when no more specialized +view kind applies), `expose` now scopes the rendered diagram instead of always rendering the +entire workspace: - `expose ;` (valid only inside a named `view` usage's body, not a `view def` definition's body) — scopes the diagram to the union of every exposed name's containment diff --git a/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs b/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs index 131f6160..ceeb7f37 100644 --- a/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs +++ b/test/DemaConsulting.SysML2Tools.Tests/Semantic/WorkspaceLoaderTests.cs @@ -2406,7 +2406,7 @@ public async Task WorkspaceLoader_LoadAsync_DocumentationExampleFixture_Captures public async Task WorkspaceLoader_LoadAsync_ViewRenderTarget_CapturedRawNeverResolvedNoDiagnostic() { // Arrange - var tempFile = Path.GetTempFileName() + ".sysml"; + var tempFile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".sysml"); try { await File.WriteAllTextAsync(tempFile, """ @@ -2446,7 +2446,7 @@ view def V { public async Task WorkspaceLoader_LoadAsync_ViewFilterExpression_CapturesTextVerbatimNoEdge() { // Arrange - var tempFile = Path.GetTempFileName() + ".sysml"; + var tempFile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".sysml"); try { await File.WriteAllTextAsync(tempFile, """ @@ -2487,7 +2487,7 @@ view def V { public async Task WorkspaceLoader_LoadAsync_ViewUsageWithExpose_RecordsExposeEdge() { // Arrange - var tempFile = Path.GetTempFileName() + ".sysml"; + var tempFile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".sysml"); try { await File.WriteAllTextAsync(tempFile, """ @@ -2534,7 +2534,7 @@ view V { public async Task WorkspaceLoader_LoadAsync_ViewUsageWithBracketedFilterExpose_RecordsExposeEdge() { // Arrange - var tempFile = Path.GetTempFileName() + ".sysml"; + var tempFile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".sysml"); try { await File.WriteAllTextAsync(tempFile, """ @@ -2577,7 +2577,7 @@ view V { public async Task WorkspaceLoader_LoadAsync_ViewUsageWithPlainWildcardExpose_RecordsExposeEdge() { // Arrange - var tempFile = Path.GetTempFileName() + ".sysml"; + var tempFile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".sysml"); try { await File.WriteAllTextAsync(tempFile, """ @@ -2661,7 +2661,7 @@ public async Task WorkspaceLoader_LoadAsync_OmgSafetyFeatureViewsFixture_Resolve public async Task WorkspaceLoader_LoadAsync_ViewEmptyBody_AllNewFieldsNullOrEmpty() { // Arrange - var tempFile = Path.GetTempFileName() + ".sysml"; + var tempFile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".sysml"); try { await File.WriteAllTextAsync(tempFile, """ From 250742a10f435e1898f9b612bda401c154a31fd1 Mon Sep 17 00:00:00 2001 From: Malcolm Nixon Date: Mon, 6 Jul 2026 13:56:53 -0400 Subject: [PATCH 5/6] docs(gallery): add expose-scoped rendering example (Battery subsystem) Adds a second named view usage (BatterySubsystemView) to the existing drone General View gallery model, demonstrating that 'expose' - not 'render' - scopes diagram content. Regenerated PNG/SVG output and documented the example in docs/gallery/README.md with a link to the user guide's Expose vs. Render section. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/gallery/README.md | 14 +++++++ docs/gallery/models/01-drone-general.sysml | 8 ++++ docs/gallery/png/BatterySubsystemView.png | Bin 0 -> 9302 bytes docs/gallery/svg/BatterySubsystemView.svg | 45 +++++++++++++++++++++ 4 files changed, 67 insertions(+) create mode 100644 docs/gallery/png/BatterySubsystemView.png create mode 100644 docs/gallery/svg/BatterySubsystemView.svg diff --git a/docs/gallery/README.md b/docs/gallery/README.md index c43e817b..e0c89e6c 100644 --- a/docs/gallery/README.md +++ b/docs/gallery/README.md @@ -30,6 +30,20 @@ SVG: [`svg/DroneGeneralView.svg`](svg/DroneGeneralView.svg) ![Drone General View](png/DroneGeneralView.png) +### 1b. View-scoped rendering — `expose` narrows the same model to one subsystem + +The same `01-drone-general.sysml` model also declares a second, named `view` usage +that adds an `expose Battery;` statement. Instead of the full workspace, the diagram +is scoped to just the `Battery` definition's containment subtree — demonstrating +that `expose` (not `render`) is what controls diagram content scope. See the user +guide's [Expose vs. Render](../user_guide/introduction.md#expose-vs-render-worked-examples) +section for the full explanation and more worked examples. + +Model: [`models/01-drone-general.sysml`](models/01-drone-general.sysml) (`BatterySubsystemView`) · +SVG: [`svg/BatterySubsystemView.svg`](svg/BatterySubsystemView.svg) + +![Battery Subsystem View](png/BatterySubsystemView.png) + --- ## 2. Interconnection View — Desktop Workstation diff --git a/docs/gallery/models/01-drone-general.sysml b/docs/gallery/models/01-drone-general.sysml index 3a49ab73..ce5676cb 100644 --- a/docs/gallery/models/01-drone-general.sysml +++ b/docs/gallery/models/01-drone-general.sysml @@ -64,4 +64,12 @@ package QuadcopterDrone { requirement def FlightTimeRequirement; view def DroneGeneralView {} + + // A view-scoped rendering example: `expose` narrows the diagram to just + // the Battery subsystem's containment subtree, instead of the whole + // workspace. See docs/gallery/README.md section 1b and the user guide's + // "Expose vs. Render" section for more detail. + view BatterySubsystemView { + expose Battery; + } } diff --git a/docs/gallery/png/BatterySubsystemView.png b/docs/gallery/png/BatterySubsystemView.png new file mode 100644 index 0000000000000000000000000000000000000000..fa0eb4c953f81586ca8e16aaa768952131ce1ce7 GIT binary patch literal 9302 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 literal 0 HcmV?d00001 diff --git a/docs/gallery/svg/BatterySubsystemView.svg b/docs/gallery/svg/BatterySubsystemView.svg new file mode 100644 index 00000000..249347d5 --- /dev/null +++ b/docs/gallery/svg/BatterySubsystemView.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + «package» + QuadcopterDrone + + «part def» + Battery + + attributes + capacity : Voltage + + ports + output : PowerPort + From ff0cb66347f601e585ed6c3d0b6bee1e842c000b Mon Sep 17 00:00:00 2001 From: Malcolm Nixon Date: Mon, 6 Jul 2026 14:00:48 -0400 Subject: [PATCH 6/6] chore(gallery): remove stale docs/gallery/images/ artifact directories These were accidentally created by a prior gallery-regeneration pass that passed a model-named '.png' path to '--output' (which expects a directory), producing nested directories literally named '.png/' each containing one real PNG inside. Never referenced by README.md or docs/gallery/README.md; superseded entirely by the canonical docs/gallery/png/ and docs/gallery/svg/ output. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../01-drone-general.png/DroneGeneralView.png | Bin 62721 -> 0 bytes .../WorkstationInterconnectionView.png | Bin 25284 -> 0 bytes .../ElevatorStateTransitionView.png | Bin 29568 -> 0 bytes .../PipelineActionFlowView.png | Bin 38693 -> 0 bytes .../05-oauth-sequence.png/OAuthSequenceView.png | Bin 15826 -> 0 bytes .../06-vehicle-grid.png/TaxonomyMatrixView.png | Bin 21288 -> 0 bytes .../AvionicsBrowserView.png | Bin 49527 -> 0 bytes 7 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 docs/gallery/images/01-drone-general.png/DroneGeneralView.png delete mode 100644 docs/gallery/images/02-computer-interconnection.png/WorkstationInterconnectionView.png delete mode 100644 docs/gallery/images/03-elevator-state.png/ElevatorStateTransitionView.png delete mode 100644 docs/gallery/images/04-pipeline-action-flow.png/PipelineActionFlowView.png delete mode 100644 docs/gallery/images/05-oauth-sequence.png/OAuthSequenceView.png delete mode 100644 docs/gallery/images/06-vehicle-grid.png/TaxonomyMatrixView.png delete mode 100644 docs/gallery/images/07-avionics-browser.png/AvionicsBrowserView.png diff --git a/docs/gallery/images/01-drone-general.png/DroneGeneralView.png b/docs/gallery/images/01-drone-general.png/DroneGeneralView.png deleted file mode 100644 index 90093f99e1cd805e45359f1ab9f9ac7a9d6774d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62721 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 z 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 diff --git a/docs/gallery/images/02-computer-interconnection.png/WorkstationInterconnectionView.png b/docs/gallery/images/02-computer-interconnection.png/WorkstationInterconnectionView.png deleted file mode 100644 index ae1b4e14dc0cce71230dd6408a56141fec38b6f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25284 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 BT%!O0 diff --git a/docs/gallery/images/03-elevator-state.png/ElevatorStateTransitionView.png b/docs/gallery/images/03-elevator-state.png/ElevatorStateTransitionView.png deleted file mode 100644 index 7e755c33f4edb2da63e5005385ce9991a467ded5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29568 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 diff --git a/docs/gallery/images/05-oauth-sequence.png/OAuthSequenceView.png b/docs/gallery/images/05-oauth-sequence.png/OAuthSequenceView.png deleted file mode 100644 index 63b707b93c241688ce44b25ea155cafad5786957..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15826 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 UwsPEpVMn1)ojsXz{Nj!O15zi7SpWb4 diff --git a/docs/gallery/images/06-vehicle-grid.png/TaxonomyMatrixView.png b/docs/gallery/images/06-vehicle-grid.png/TaxonomyMatrixView.png deleted file mode 100644 index 5599af414401a2688f98a93e9c421947adfa1e28..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21288 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 diff --git a/docs/gallery/images/07-avionics-browser.png/AvionicsBrowserView.png b/docs/gallery/images/07-avionics-browser.png/AvionicsBrowserView.png deleted file mode 100644 index 91b6b1c1a84d5a51f87ec7de7a3d979068fbe4dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49527 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 Ac>n+a