Skip to content

feat: implement strong-typed-routes ESLint rule#105

Merged
M-jerez merged 9 commits intomion-run-typesfrom
feature/strong-typed-routes-eslint-rule
Aug 26, 2025
Merged

feat: implement strong-typed-routes ESLint rule#105
M-jerez merged 9 commits intomion-run-typesfrom
feature/strong-typed-routes-eslint-rule

Conversation

@M-jerez
Copy link
Contributor

@M-jerez M-jerez commented Aug 26, 2025

Summary

Implements a new ESLint rule strong-typed-routes that enforces explicit parameter and return type annotations for router handler functions.

Rule Purpose

Enforce that handler functions passed to specific router functions have explicitly defined parameter types (except the first parameter) and return types.

Target Functions

The rule checks calls to these functions from @mionkit/router:

  • route
  • hook
  • headersHook
  • Ignores rawHook (not type-checked)

Rule Requirements

  1. Handler functions must have explicit return type annotations
  2. All parameters except the first must have explicit type annotations
  3. The first parameter is always CallContext and is ignored by this rule
  4. Both arrow functions and regular function expressions are supported

Valid Examples

import {route, hook, headersHook} from '@mionkit/router';

route((ctx, name: string): string => `hello ${name}`); // ✅ Valid
hook((ctx, data: number): void => { console.log(data); }); // ✅ Valid
headersHook(['auth'], (ctx, headers: Headers): boolean => true); // ✅ Valid

Invalid Examples

route((ctx, name) => `hello ${name}`); // ❌ Missing param type and return type
route((ctx, name: string) => `hello ${name}`); // ❌ Missing return type
route((ctx, name): string => `hello ${name}`); // ❌ Missing param type

Implementation Details

  • Performance: Implemented as a single rule rather than splitting into separate param/return rules
  • Import handling: Works with both direct function calls and when functions are imported/aliased
  • Parameter focus: Focuses on the handler parameter specifically (first parameter for route/hook, second for headersHook)
  • Comprehensive testing: 36 test cases covering various scenarios

Breaking Change Prevention

The rule is added to the recommended config but initially disabled in .eslintrc.js to avoid breaking the existing codebase:

rules: {
  // Disable the new strong-typed-routes rule initially to avoid breaking existing codebase
  '@mionkit/strong-typed-routes': 'off',
}

To enable the rule later, simply remove this line from the root ESLint configuration.

Files Changed

  • packages/eslint-plugin/src/rules/strong-typed-routes.ts - Main rule implementation
  • packages/eslint-plugin/src/rules/strong-typed-routes.spec.ts - Comprehensive test suite
  • packages/eslint-plugin/src/index.ts - Export rule and add to recommended config
  • .eslintrc.js - Disable rule initially to prevent breaking changes

Testing

✅ All tests pass (36 test cases)
✅ Build successful
✅ Linting passes
✅ Code formatted

Next Steps

Once this PR is merged, teams can gradually enable the rule by:

  1. Removing '@mionkit/strong-typed-routes': 'off' from .eslintrc.js
  2. Fixing any violations in the codebase
  3. Enjoying better type safety in router handlers!

Pull Request opened by Augment Code with guidance from the PR author

- Add new ESLint rule to enforce explicit parameter and return type annotations for router handler functions
- Target functions: route, hook, headersHook from @mionkit/router (rawHook is ignored)
- Handler functions must have explicit return type annotations
- All parameters except the first (CallContext) must have explicit type annotations
- Support both arrow functions and regular function expressions
- Include comprehensive test suite with 36 test cases
- Add rule to recommended config but disable it initially to avoid breaking existing codebase
- Rule can be enabled later by removing '@mionkit/strong-typed-routes': 'off' from .eslintrc.js
@M-jerez M-jerez changed the base branch from master to mion-run-types August 26, 2025 13:52
M-jerez and others added 8 commits August 26, 2025 14:04
- Add support for handlers defined separately and passed by reference
- Handle function declarations: function sayHello(ctx, name: string): string { ... }
- Handle arrow function variables: const sayHello = (ctx, name: string): string => ...
- Handle function expression variables: const sayHello = function(ctx, name: string): string { ... }
- Add comprehensive test coverage for all function reference scenarios
- Maintain backward compatibility with inline function expressions
- Update function signatures to support TSESTree.FunctionDeclaration
- Add findFunctionByName utility to locate function definitions in the same file

Test coverage increased from 36 to 46 test cases covering:
- Valid function references with proper types
- Invalid function references missing parameter types
- Invalid function references missing return types
- Invalid function references missing both types
- All router functions: route, hook, headersHook
🎯 NEW FUNCTIONALITY:
- Type annotations: const handler: Handler = (ctx, name: string): string => ...
- Satisfies expressions: const handler = ((ctx, name: string): string => ...) satisfies Handler
- JSDoc tags: @mion:route, @mion:hook, @mion:headersHook for explicit handler marking

🔧 IMPLEMENTATION DETAILS:
- Added getHandlerTypeFromAnnotation() for Handler/HeaderHandler type checking
- Added getHandlerTypeFromSatisfies() for satisfies expression support
- Added getHandlerTypeFromJSDoc() for @mion:* JSDoc tag detection
- Added isImportedFromMionRouter() to verify types are from @mionkit/router
- Enhanced VariableDeclarator visitor to handle type annotations and JSDoc
- Added TSSatisfiesExpression visitor for satisfies expressions
- Added checkHandlerFunction() helper to centralize validation logic
- Updated JSDoc function to handle VariableDeclaration nodes for arrow functions

✅ COMPREHENSIVE TESTING:
- Test coverage increased from 46 to 60 test cases
- All scenarios covered: type annotations, satisfies, JSDoc tags
- Both valid and invalid cases for each approach
- Maintains 100% backward compatibility with existing functionality

🚀 DEVELOPER EXPERIENCE:
- Multiple ways to ensure type safety based on coding preferences
- Clear, actionable error messages for each approach
- Self-documenting code with JSDoc tags
- Explicit developer intent with type annotations

BREAKING CHANGES: None - all existing functionality preserved
🎯 CHANGES:
- Remove disable rule from root .eslintrc.js since no existing violations found
- Add comprehensive test file: packages/router/examples/eslint-rule-test.routes.ts
- Test file demonstrates all rule functionality with valid/invalid examples

📋 TEST FILE INCLUDES:
✅ Valid examples (should NOT trigger errors):
- Direct inline handlers with proper types
- Function references with proper types
- Type annotations: const handler: Handler = ...
- Satisfies expressions: const handler = (...) satisfies Handler
- JSDoc tags: @mion:route, @mion:hook, @mion:headersHook

❌ Invalid examples (SHOULD trigger errors):
- Missing parameter types
- Missing return types
- Missing both types
- All scenarios across different approaches

🧪 MANUAL TESTING INSTRUCTIONS:
1. Remove /* eslint-disable @mionkit/strong-typed-routes */ from test file
2. Run ESLint on the file to see rule violations
3. Verify valid examples don't error, invalid examples do error
4. Re-add disable comment to prevent CI failures

🚀 PRODUCTION READY:
- Rule is now enabled by default
- No existing violations in codebase
- Comprehensive test coverage
- Easy manual verification available
@M-jerez M-jerez merged commit bac7c5b into mion-run-types Aug 26, 2025
1 check failed
@M-jerez M-jerez deleted the feature/strong-typed-routes-eslint-rule branch August 26, 2025 15:53
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.

1 participant