Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SignalManager: improve type safety and reduce boilerplate #1133

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

williamsyang-work
Copy link
Contributor

@williamsyang-work williamsyang-work commented Oct 22, 2024

⚠️ IMPORTANT: Dependency Warning ⚠️

This PR introduces type changes that will affect the vscode-trace-extension repository.
DO NOT MERGE until maintainers have reviewed and approved the proposed changes.
A corresponding PR will need to be created in vscode-trace-extension to handle these type updates.

👉 Maintainers: Please review and comment on the proposed type system before work begins on the extension repository.

Improve SignalManager Type Safety and Developer Experience

Overview

This PR refactors the SignalManager to provide better TypeScript support and improved developer experience. The changes focus on making the event system more type-safe while reducing boilerplate code.

Key Improvements

💡 Better Type Inference

// Before - No type inference on payload
signalManager.on('THEME_CHANGED', (theme) => {
    console.log(theme.toUpperCase()); // TS doesn't know theme is a string
});

// After - Full type inference
signalManager.on('THEME_CHANGED', (theme) => {
    console.log(theme.toUpperCase()); // TS knows theme is a string
});

🛡️ Compile-Time Type Safety

// Before - TypeScript wouldn't catch this error
signalManager.emit('THEME_CHANGED', 123); // Should be string

// After - TypeScript catches invalid payload types
signalManager.emit('THEME_CHANGED', 123); // Type error!

📝 Better IDE Support

  • Autocomplete for event names
  • Parameter type hints for event handlers
  • Inline documentation for all methods

🧹 Reduced Boilerplate

// Before - Required separate fire* method for each event
class SignalManager {
    fireThemeChangedSignal(theme: string): void {
        this.emit(Signals.THEME_CHANGED, theme);
    }
    fireTraceOpenedSignal(trace: Trace): void {
        this.emit(Signals.TRACE_OPENED, trace);
    }
    // ... many more fire* methods
}

// After - Direct emit with type safety
signalManager.emit('THEME_CHANGED', theme);
signalManager.emit('TRACE_OPENED', trace);

Implementation Details

  • Consolidated signal types into a single Signals interface
  • Added generic type constraints for event handlers
  • Enhanced JSDoc documentation for core methods
  • Maintained backward compatibility with existing event system

@williamsyang-work williamsyang-work force-pushed the signal-type-enforcement branch 2 times, most recently from e381965 to a2e3573 Compare October 22, 2024 19:39
@williamsyang-work williamsyang-work marked this pull request as ready for review October 22, 2024 19:53
@williamsyang-work williamsyang-work marked this pull request as draft October 22, 2024 20:00
@williamsyang-work williamsyang-work marked this pull request as ready for review October 22, 2024 22:27
@williamsyang-work williamsyang-work force-pushed the signal-type-enforcement branch 4 times, most recently from 2720135 to 5b2699a Compare October 23, 2024 18:02
@williamsyang-work williamsyang-work changed the title refactor(SignalManager): improve type safety and reduce boilerplate SignalManager: improve type safety and reduce boilerplate Oct 23, 2024
- Replace individual fire* methods with strongly-typed emit interface
- Consolidate signal payload types into single Signals interface
- Add type-safe event handler registration with overloaded on/off methods
- Remove redundant method declarations in favor of TypeScript inference
- Maintain full backward compatibility with existing event system

The new implementation provides compile-time type checking for event
payloads while significantly reducing code volume and maintenance burden.

Signed-off-by: Will Yang <[email protected]>
Copy link
Collaborator

@bhufmann bhufmann left a comment

Choose a reason for hiding this comment

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

Thanks for the proposal. It looks like a simplification. I had a first glance over the changes and have a general comment using Strings. Please have look.

*
* @example
* // Single argument emission
* signalManager().emit('THEME_CHANGED', 'dark');
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think that a String has to be passed manually instead of a String constant as before (Signals.THEME_CHANGED) is more error-prone, because it's not caught at build time. Think we should have something similar as before.

*
* @example
* // Single argument event
* signalManager().on('THEME_CHANGED', (theme: string) => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think that a String has to be passed manually instead of a String constant as before (Signals.THEME_CHANGED) is more error-prone, because it's not caught at build time. Think we should have something similar as before.

Copy link
Contributor Author

@williamsyang-work williamsyang-work Oct 25, 2024

Choose a reason for hiding this comment

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

The current setup is actually type-safe! The SignalType = keyof Signals type means TypeScript will only accept exact string matches of the events defined in our Signals interface. You can't pass an arbitrary string - TypeScript will catch any typos or invalid events at compile time:

signalManager.on('THEME_CHANGED', ...) // ✅ Works fine
signalManager.on('INVALID_EVENT', ...) // ❌ Compile error
signalManager.on('theme_changed', ...) // ❌ Compile error

Plus, we get the bonus of TypeScript automatically inferring the correct parameter types for our listeners. So we're getting all the safety of constants with better ergonomics.

Here's a screengrab of how this looks in vscode:

Screencast.from.10-25-2024.09.55.57.AM.webm

*
* @example
* const themeHandler = (theme: string) => console.log(theme);
* signalManager().off('THEME_CHANGED', themeHandler);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think that a String has to be passed manually instead of a String constant as before (Signals.THEME_CHANGED) is more error-prone, because it's not caught at build time. Think we should have something similar as before.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants