Skip to content

refactor(type)!: make AbstractGraph.getPlugin return T | undefined#842

Merged
tbouffard merged 3 commits intomainfrom
refactor/graph_getPlugin_return_undefined
Jun 10, 2025
Merged

refactor(type)!: make AbstractGraph.getPlugin return T | undefined#842
tbouffard merged 3 commits intomainfrom
refactor/graph_getPlugin_return_undefined

Conversation

@tbouffard
Copy link
Member

@tbouffard tbouffard commented May 28, 2025

Explicitly state that a plugin may not be available.

BREAKING CHANGE: The AbstractGraph.getPlugin method now explicitly returns undefined when a plugin is not found.
TypeScript users must update their code to handle the undefined case when calling this method.

Summary by CodeRabbit

  • Bug Fixes
    • Improved type safety by asserting plugin presence during retrievals, reducing potential runtime errors.
    • Fixed initialization of drag-and-drop guides to prevent issues from undefined values.
    • Added safeguards for optional plugin handling to avoid runtime errors.
    • Clarified variable names in UI components for better readability.
  • Documentation
    • Updated changelog to clarify breaking changes in plugin management and method signatures.
  • Refactor
    • Enhanced internal plugin storage and access using a map structure for better maintainability.

Explicitly state that a plugin may not be available.

BREAKING CHANGE: The `AbstractGraph.getPlugin` method now explicitly returns `undefined` when a plugin is not found.
TypeScript users must update their code to handle the `undefined` case when calling this method.
@tbouffard tbouffard added the refactor Code refactoring label May 28, 2025
@coderabbitai
Copy link

coderabbitai bot commented May 28, 2025

Walkthrough

This update refactors the internal plugin management of the graph system by changing plugin storage from an object to a Map and updating the getPlugin method to return undefined if a plugin is not found. All usages of plugin retrieval are updated with non-null assertions, and related documentation and variable names are clarified.

Changes

Files/Paths Change Summary
packages/core/src/view/AbstractGraph.ts Refactored plugin storage from object to Map; getPlugin now returns `T
CHANGELOG.md Updated to clarify breaking changes regarding plugin management and method signatures.
packages/core/__tests__/view/plugins/FitPlugin.test.ts Added non-null assertions to plugin retrievals for type safety.
packages/core/src/editor/Editor.ts Simplified plugin retrieval variable typing in setMode method.
packages/core/src/view/other/DragSource.ts Ensured currentGuide is always constructed with a defined array argument.
packages/html/stories/AutoLayout.stories.ts
.../Codec.stories.ts
.../ContextIcons.stories.ts
.../CustomHandlesConfiguration.stories.ts
.../CustomStyleDefaults.stories.ts
.../DynamicToolbar.stories.ts
.../Events.stories.ts
.../FileIO.stories.ts
.../FixedPoints.stories.ts
.../Manhattan.stories.ts
.../Orthogonal.stories.ts
.../PageBreaks.stories.ts
.../ShowRegion.stories.ts
.../SwimLanes.stories.ts
.../Toolbar.stories.ts
Added non-null assertion operators (!) to plugin retrievals; renamed some variables for clarity.
packages/ts-support/src/index.ts Added null check before setting properties on rubberBandHandler to prevent runtime errors.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant AbstractGraph
    participant PluginMap

    Client->>AbstractGraph: getPlugin<SomePlugin>('PluginId')
    AbstractGraph->>PluginMap: Map.get('PluginId')
    PluginMap-->>AbstractGraph: Plugin instance or undefined
    AbstractGraph-->>Client: Plugin instance or undefined

    alt Plugin is undefined
        Client->>Client: Must handle undefined plugin
    else Plugin is defined
        Client->>Plugin: Use plugin methods/properties
    end
Loading

Possibly related PRs

  • maxGraph/maxGraph#818: Both PRs modify the getPlugin method in AbstractGraph, focusing on internal handling and return types.
  • maxGraph/maxGraph#733: Both involve changes to plugin storage and retrieval, with this related PR introducing the new FitPlugin and its integration.
  • maxGraph/maxGraph#734: Both PRs move fit-related methods and properties to the FitPlugin and update getPlugin to return undefined when missing.

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between adebfaf and 0e4241a.

📒 Files selected for processing (1)
  • CHANGELOG.md (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • CHANGELOG.md
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: build (macos-14)
  • GitHub Check: build (windows-2022)
  • GitHub Check: build (ubuntu-22.04)
  • GitHub Check: build

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
packages/core/__tests__/view/plugins/FitPlugin.test.ts (1)

56-56: Non-null assertions are safe in test context.

The addition of non-null assertion operators (!) is appropriate in this test environment since the FitPlugin is explicitly added to the graph during initialization. However, consider using optional chaining or explicit null checks in production code to maintain type safety.

For production code, consider this safer alternative:

-const scale = graph.getPlugin<FitPlugin>('fit')!.fitCenter();
+const fitPlugin = graph.getPlugin<FitPlugin>('fit');
+if (!fitPlugin) throw new Error('FitPlugin not available');
+const scale = fitPlugin.fitCenter();

Also applies to: 72-72, 88-88, 104-104, 118-118, 133-133, 149-149, 167-167, 183-183

packages/core/src/view/other/DragSource.ts (1)

529-529: Excellent defensive programming approach.

This implementation handles the potential undefined selectionHandler gracefully by using optional chaining (?.) and providing a sensible fallback (?? []). This is more robust than the non-null assertions used elsewhere in this PR.

However, this creates an inconsistency with the approach taken in the story files. Consider establishing a consistent pattern across the codebase for handling plugin retrieval.

packages/html/stories/ContextIcons.stories.ts (1)

121-122: Consider runtime safety of non-null assertions.

The non-null assertions assume plugins will always be available. While this may be true for story demonstrations, consider whether defensive programming would be more appropriate to prevent potential runtime errors.

The approach in DragSource.ts (line 529) demonstrates a more defensive pattern using optional chaining and nullish coalescing.

Also applies to: 205-205

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 22d8c9e and 01fb58b.

📒 Files selected for processing (20)
  • CHANGELOG.md (1 hunks)
  • packages/core/__tests__/view/plugins/FitPlugin.test.ts (9 hunks)
  • packages/core/src/editor/Editor.ts (1 hunks)
  • packages/core/src/view/AbstractGraph.ts (3 hunks)
  • packages/core/src/view/other/DragSource.ts (1 hunks)
  • packages/html/stories/AutoLayout.stories.ts (2 hunks)
  • packages/html/stories/Codec.stories.ts (1 hunks)
  • packages/html/stories/ContextIcons.stories.ts (2 hunks)
  • packages/html/stories/CustomHandlesConfiguration.stories.ts (1 hunks)
  • packages/html/stories/CustomStyleDefaults.stories.ts (1 hunks)
  • packages/html/stories/DynamicToolbar.stories.ts (1 hunks)
  • packages/html/stories/Events.stories.ts (2 hunks)
  • packages/html/stories/FileIO.stories.ts (1 hunks)
  • packages/html/stories/FixedPoints.stories.ts (1 hunks)
  • packages/html/stories/Manhattan.stories.ts (1 hunks)
  • packages/html/stories/Orthogonal.stories.ts (2 hunks)
  • packages/html/stories/PageBreaks.stories.ts (1 hunks)
  • packages/html/stories/ShowRegion.stories.ts (1 hunks)
  • packages/html/stories/SwimLanes.stories.ts (1 hunks)
  • packages/html/stories/Toolbar.stories.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (5)
packages/html/stories/Events.stories.ts (1)
packages/html/stories/OrgChart.stories.js (1)
  • popupMenuHandler (167-167)
packages/core/__tests__/view/plugins/FitPlugin.test.ts (1)
packages/core/src/view/plugins/FitPlugin.ts (1)
  • FitPlugin (87-281)
packages/html/stories/AutoLayout.stories.ts (1)
packages/html/stories/OrgChart.stories.js (1)
  • popupMenuHandler (167-167)
packages/html/stories/PageBreaks.stories.ts (1)
packages/html/stories/Guides.stories.js (1)
  • selectionHandler (76-76)
packages/core/src/view/AbstractGraph.ts (1)
packages/core/src/types.ts (2)
  • GraphPlugin (1178-1180)
  • PluginId (1169-1169)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build (windows-2022)
🔇 Additional comments (24)
packages/core/src/view/AbstractGraph.ts (4)

113-113: Excellent choice to use Map for plugin storage.

Using Map<string, GraphPlugin> instead of a plain object provides better semantics for key-value storage and clearer intent for plugin management.


222-222: Correct JSDoc reference fix.

Good correction from mxConstants.PAGE_FORMAT_A4_PORTRAIT to PAGE_FORMAT_A4_PORTRAIT to match the actual import and constant name.


479-479: Proper Map initialization for plugins.

The change to use Map.set() is consistent with the new Map-based plugin storage and maintains the same functionality.


485-486: Breaking change improves type safety and API clarity.

The return type change from T to T | undefined is a well-justified breaking change that:

  • Makes the API more explicit about potential undefined returns
  • Forces consumers to handle the case where plugins might not be available
  • Aligns with the Map.get() behavior which returns undefined for missing keys

This change improves type safety and makes the plugin retrieval behavior more predictable.

packages/html/stories/CustomHandlesConfiguration.stories.ts (1)

108-108: Non-null assertion is appropriate for this story context.

The ! assertion on getPlugin<ConnectionHandler>('ConnectionHandler') is justified here because:

  • The story setup uses default plugins which include ConnectionHandler
  • This maintains the previous behavior while adapting to the new T | undefined return type
  • The story context guarantees plugin availability
packages/html/stories/CustomStyleDefaults.stories.ts (1)

82-82: Consistent and appropriate non-null assertion usage.

The ! assertion follows the same pattern as other story files and is appropriate because:

  • The story uses default plugins that include ConnectionHandler
  • The assertion maintains existing functionality while satisfying new type requirements
  • Consistent with the adaptation pattern across all story files
packages/html/stories/Codec.stories.ts (1)

114-114: Appropriate non-null assertion for PanningHandler.

The ! assertion on getPlugin<PanningHandler>('PanningHandler') is justified because:

  • PanningHandler is part of the default plugin set
  • The story context guarantees plugin availability
  • Maintains consistency with the adaptation pattern used across all story files

This change successfully adapts to the breaking change while preserving existing functionality.

packages/html/stories/FileIO.stories.ts (1)

73-73: Non-null assertion applied correctly for plugin retrieval
The addition of ! on getPlugin<PanningHandler>('PanningHandler') properly reflects the updated return type T | undefined and avoids compile-time errors.

packages/html/stories/Manhattan.stories.ts (1)

48-48: Non-null assertion applied correctly for plugin retrieval
Using ! on getPlugin<SelectionHandler>('SelectionHandler') aligns with the updated signature and ensures guidesEnabled can be accessed without TypeScript errors.

packages/html/stories/Events.stories.ts (2)

101-101: Non-null assertion applied correctly for plugin retrieval
The ! after getPlugin<ConnectionHandler>('ConnectionHandler') safely satisfies the new T | undefined return type before setting connectImage.


131-131: Non-null assertion applied correctly for plugin retrieval
Applying ! on getPlugin<PopupMenuHandler>('PopupMenuHandler') ensures the handler exists at runtime and removes TypeScript errors when assigning factoryMethod.

packages/html/stories/Orthogonal.stories.ts (2)

99-100: Non-null assertion applied correctly for plugin retrieval
The non-null assertion on getPlugin<SelectionHandler>('SelectionHandler') correctly adapts to the updated signature and enables guidesEnabled.


112-112: Non-null assertion applied correctly for plugin retrieval
Using ! on getPlugin<ConnectionHandler>('ConnectionHandler') properly handles the T | undefined return and allows overriding createEdgeState.

packages/html/stories/ShowRegion.stories.ts (1)

109-109: Non-null assertion applied correctly for plugin retrieval
The ! on getPlugin<PopupMenuHandler>('PopupMenuHandler') ensures the menu handler is defined before calling isMenuShowing(), satisfying the updated return type.

packages/html/stories/FixedPoints.stories.ts (1)

148-151: LGTM: Non-null assertion properly handles the new getPlugin signature.

The addition of the non-null assertion operator (!) correctly addresses the breaking change where getPlugin now returns T | undefined. This assumes the ConnectionHandler plugin is always available, which is reasonable for this story context.

However, consider adding a defensive check if there's any possibility the plugin might not be registered:

  override isConnectableCell(cell: Cell) {
-   return this.graph
-     .getPlugin<ConnectionHandler>('ConnectionHandler')!
-     .isConnectableCell(cell);
+   const connectionHandler = this.graph.getPlugin<ConnectionHandler>('ConnectionHandler');
+   return connectionHandler?.isConnectableCell(cell) ?? false;
  }
packages/html/stories/PageBreaks.stories.ts (1)

79-82: LGTM: Improved naming and proper handling of new getPlugin signature.

The changes improve code clarity in two ways:

  1. Better variable naming: Renaming graphHandler to selectionHandler is more descriptive and aligns with the actual plugin type being retrieved.

  2. Proper null handling: The non-null assertion (!) correctly addresses the breaking change where getPlugin now returns T | undefined.

The variable name change is consistent with similar updates across other story files mentioned in the AI summary, promoting consistent naming conventions.

packages/core/src/editor/Editor.ts (1)

2392-2402: LGTM: Clean simplification with proper undefined handling.

The removal of the explicit type annotation : PanningHandler is a good simplification since TypeScript can infer the type from the generic parameter.

Unlike other files in this PR that use non-null assertions, this implementation correctly handles the potential undefined return value using optional chaining (panningHandler &&) in the subsequent conditional statements (lines 2394, 2397, 2400). This is a more defensive approach that gracefully handles cases where the PanningHandler plugin might not be available.

CHANGELOG.md (1)

15-16: Clear documentation of breaking change.

The changelog entry accurately describes the breaking change and its impact on TypeScript users. The explanation clearly communicates that getPlugin now explicitly returns undefined when a plugin is not found, requiring developers to handle this case.

packages/html/stories/DynamicToolbar.stories.ts (1)

91-91:

Details

❓ Verification inconclusive

Verify ConnectionHandler is included in default plugins.

The non-null assertion is appropriate in this story context, but please ensure that ConnectionHandler is actually included in the plugins returned by getDefaultPlugins().


🏁 Script executed:

#!/bin/bash
# Verify that ConnectionHandler is included in getDefaultPlugins
ast-grep --pattern 'function getDefaultPlugins() {
  $$$
}'

Length of output: 61


#!/bin/bash
set -e

# Locate definition or references of getDefaultPlugins to inspect its returned plugin list
rg -n -C5 "getDefaultPlugins" .

# Check if ConnectionHandler is instantiated in the default plugins array
rg -n -C5 "new ConnectionHandler" .

Please confirm that ConnectionHandler is registered in getDefaultPlugins()

The non-null assertion on line 91 of the story is fine in this controlled context, but you should verify that ConnectionHandler is actually part of the default plugin set:

  • File: packages/html/stories/DynamicToolbar.stories.ts (line 91)
  • Inspect the getDefaultPlugins() implementation (e.g. in packages/html/src/graph/plugins/…) to ensure it returns an instance of ConnectionHandler.
packages/html/stories/Toolbar.stories.ts (1)

101-101: Consistent with other story files.

The non-null assertion follows the same pattern as other story files and is appropriate in this controlled environment. The change maintains consistency across the codebase.

packages/html/stories/SwimLanes.stories.ts (2)

84-85: Good variable renaming for clarity.

The renaming from graphHandler to selectionHandler improves code readability by making the variable purpose more explicit.


84-84: Verify plugin availability assumptions with non-null assertions.

The non-null assertions (!) assume that both SelectionHandler and ConnectionHandler plugins will always be available. If these plugins are not registered or available, this will cause runtime errors.

Consider whether these assumptions are safe in this context, or if defensive programming with optional chaining would be more appropriate.

Also applies to: 89-89

packages/html/stories/AutoLayout.stories.ts (1)

150-150:

Details

✅ Verification successful

Verify plugin availability for non-null assertions.

The non-null assertions assume ConnectionHandler and PopupMenuHandler plugins are always available. Ensure these plugins are guaranteed to be present in the graph configuration, or consider defensive programming approaches.

Also applies to: 222-222


🏁 Script executed:

#!/bin/bash
# Description: Check if these plugins are part of default plugins or explicitly added
# Expected: Find evidence that these plugins are reliably available

# Search for plugin registrations and default plugin configurations
ast-grep --pattern $'getDefaultPlugins'
echo "--- Searching for ConnectionHandler plugin references ---"
rg -A 3 -B 3 "ConnectionHandler" --type ts
echo "--- Searching for PopupMenuHandler plugin references ---"  
rg -A 3 -B 3 "PopupMenuHandler" --type ts

Length of output: 54159


Non-null assertions are safe: ConnectionHandler & PopupMenuHandler are included in getDefaultPlugins

  • The default plugin list (core/src/view/plugins/index.tsgetDefaultPlugins()) contains both ConnectionHandler and PopupMenuHandler.
  • All of the stories under packages/html/stories use getDefaultPlugins(), so graph.getPlugin<…>('…')! will never be undefined in these contexts.

No changes required.

packages/html/stories/ContextIcons.stories.ts (1)

121-121: Good variable renaming for clarity.

The renaming from graphHandler to selectionHandler improves code readability and aligns with similar improvements in other story files.

@tbouffard
Copy link
Member Author

ℹ️ SonarQube detects duplication but they are not introduced by this PR.

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
4.7% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@tbouffard tbouffard merged commit 3f92ac2 into main Jun 10, 2025
6 of 7 checks passed
@tbouffard tbouffard deleted the refactor/graph_getPlugin_return_undefined branch June 10, 2025 09:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactor Code refactoring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant