UNPKG

au-rogue

Version:

Conservative Aurelia 1 to 2 codemods. Changes only what is safe, reports everything.

213 lines (212 loc) 10.3 kB
/** * Enhanced router configuration migration for Aurelia 1 → 2 * * Transforms: * 1. configureRouter() methods to static routes or @route decorators * 2. Router lifecycle methods to new patterns * 3. Navigation model usage to new router APIs */ export function transformRouter(project, reporter) { for (const sourceFile of project.getSourceFiles()) { const classes = sourceFile.getClasses(); for (const cls of classes) { const className = cls.getName() || '(anonymous)'; // Find configureRouter methods const configureRouterMethod = cls.getMethod('configureRouter'); if (configureRouterMethod) { analyzeRouterConfiguration(configureRouterMethod, cls, sourceFile, reporter, className); } // Check for router lifecycle methods analyzeRouterLifecycleMethods(cls, sourceFile, reporter, className); // Check for NavigationInstruction usage analyzeNavigationInstructions(cls, sourceFile, reporter, className); } } } /** * Analyze configureRouter method and suggest migration strategies */ function analyzeRouterConfiguration(method, cls, sourceFile, reporter, className) { const methodText = method.getBodyText() || ''; const filePath = sourceFile.getFilePath(); reporter.warn(filePath, `configureRouter() method in ${className} needs manual migration to Aurelia 2.`); reporter.note(filePath, `Migration options: 1) Use static routes in main.ts, 2) Use @route decorators on components, 3) Use router-lite for simpler apps.`); // Check for complex routing patterns analyzeComplexRoutingPatterns(methodText, filePath, reporter, className); // Check for router title and navigation model configuration if (methodText.includes('router.title') || methodText.includes('config.title')) { reporter.note(filePath, `Router title configuration found in ${className}. In Aurelia 2, set titles using @route({ title: 'Page Title' }) or page metadata.`); } } /** * Extract route configurations from configureRouter method */ function extractRouteConfigurations(methodText) { const routes = []; // Look for config.map patterns const mapMatch = methodText.match(/config\.map\(\[([\s\S]*?)\]\)/); if (mapMatch) { const routesText = mapMatch[1]; // Parse individual route objects (simplified parsing) const routeMatches = routesText.match(/\{[^}]*\}/g); if (routeMatches) { for (const routeMatch of routeMatches) { const route = parseRouteObject(routeMatch); if (route) routes.push(route); } } } return routes; } /** * Parse individual route object from text */ function parseRouteObject(routeText) { try { // Extract key properties using regex (simplified) const route = routeText.match(/route:\s*['"`]([^'"`]*)['"`]/)?.[1]; const moduleId = routeText.match(/moduleId:\s*['"`]([^'"`]*)['"`]/)?.[1]; const name = routeText.match(/name:\s*['"`]([^'"`]*)['"`]/)?.[1]; const title = routeText.match(/title:\s*['"`]([^'"`]*)['"`]/)?.[1]; const nav = routeText.includes('nav: true') || routeText.match(/nav:\s*\d+/); if (route && moduleId) { return { route, moduleId, name, title, nav: !!nav }; } } catch (e) { // Parsing failed, return null } return null; } /** * Generate static routes configuration for Aurelia 2 */ function generateStaticRoutes(routes) { const staticRoutes = routes.map(route => { const component = route.moduleId.replace(/^\.\//, '').replace(/\.ts$/, '').replace(/\.js$/, ''); return ` { path: '${route.route === '' ? '/' : route.route}', component: () => import('./${component}'), title: '${route.title || route.name || component}'${route.name ? `, name: '${route.name}'` : ''} }`; }).join(',\n'); return `[\n${staticRoutes}\n]`; } /** * Generate @route decorator suggestions */ function generateDecoratorRoutes(routes) { return routes.map(route => { const options = []; if (route.title) options.push(`title: '${route.title}'`); if (route.name) options.push(`name: '${route.name}'`); const optionsStr = options.length > 0 ? `{ ${options.join(', ')} }` : `'${route.route}'`; return `@route(${optionsStr})`; }).join('\n'); } /** * Analyze complex routing patterns that need special attention */ function analyzeComplexRoutingPatterns(methodText, filePath, reporter, className) { // Check for child routes if (methodText.includes('childRoutes') || methodText.includes('settings: { childRoutes')) { reporter.warn(filePath, `${className} uses child routes. Aurelia 2 handles nested routing differently - review nested routing documentation.`); } // Check for route parameters if (methodText.includes(':') && methodText.match(/route:\s*['"`][^'"`]*:[^'"`]*['"`]/)) { reporter.note(filePath, `${className} uses route parameters. Aurelia 2 supports parameters but syntax may differ: use {id} instead of :id`); } // Check for wildcard routes if (methodText.includes('*') && methodText.match(/route:\s*['"`][^'"`]*\*[^'"`]*['"`]/)) { reporter.note(filePath, `${className} uses wildcard routes. Review Aurelia 2 wildcard syntax: use {...rest} for catch-all routes`); } // Check for route href generation if (methodText.includes('router.generate') || methodText.includes('generateUrl')) { reporter.warn(filePath, `${className} generates route URLs programmatically. Aurelia 2 router has different URL generation APIs.`); } } /** * Analyze router lifecycle methods */ function analyzeRouterLifecycleMethods(cls, sourceFile, reporter, className) { const filePath = sourceFile.getFilePath(); const routerLifecycleMethods = ['canActivate', 'activate', 'canDeactivate', 'deactivate']; for (const methodName of routerLifecycleMethods) { const method = cls.getMethod(methodName); if (method) { const methodText = method.getBodyText() || ''; // Analyze method parameters and return types const params = method.getParameters(); const hasNavigationInstruction = params.some(p => p.getTypeNode()?.getText().includes('NavigationInstruction')); if (hasNavigationInstruction) { reporter.warn(filePath, `${methodName}() in ${className} uses NavigationInstruction. Aurelia 2 router has different parameter types.`); } // Get full method text to check for instruction usage patterns const fullMethodText = method.getFullText(); // Check for specific patterns that need migration if (fullMethodText.includes('instruction.config') || fullMethodText.includes('navigationInstruction.config')) { reporter.warn(filePath, `${methodName}() in ${className} accesses instruction.config. Route configuration access has changed in Aurelia 2.`); } if (fullMethodText.includes('instruction.params') || fullMethodText.includes('instruction.queryParams') || fullMethodText.includes('navigationInstruction.params') || fullMethodText.includes('navigationInstruction.queryParams')) { reporter.note(filePath, `${methodName}() in ${className} accesses route parameters. Aurelia 2 injects parameters differently - use @newInstanceForScope or resolve IRouteContext.`); } } } } /** * Analyze NavigationInstruction usage throughout the class */ function analyzeNavigationInstructions(cls, sourceFile, reporter, className) { const classText = cls.getFullText(); const filePath = sourceFile.getFilePath(); if (classText.includes('NavigationInstruction')) { reporter.warn(filePath, `${className} imports or uses NavigationInstruction. This interface has changed significantly in Aurelia 2.`); } // Check for router navigation calls if (classText.includes('router.navigate') || classText.includes('router.navigateToRoute')) { reporter.note(filePath, `${className} calls router navigation methods. Aurelia 2 router navigation APIs are similar but may have different options.`); } // Check for router URL generation calls if (classText.includes('router.generate') || classText.includes('generateUrl')) { reporter.warn(filePath, `${className} generates route URLs programmatically. Aurelia 2 router has different URL generation APIs.`); } // Check for router events if (classText.includes('router:navigation:') || classText.includes('RouterEvent')) { reporter.warn(filePath, `${className} uses router events. Aurelia 2 has a different event system for router navigation.`); } } /** * Generate migration guidance for common router patterns */ export function generateRouterMigrationGuide(project, reporter) { let hasRouterUsage = false; for (const sourceFile of project.getSourceFiles()) { const content = sourceFile.getFullText(); if (content.includes('configureRouter') || content.includes('NavigationInstruction') || content.includes('canActivate') || content.includes('router.navigate')) { hasRouterUsage = true; break; } } if (hasRouterUsage) { reporter.note('ROUTER_MIGRATION', 'Router Migration Guide:'); reporter.note('ROUTER_MIGRATION', '1. Replace configureRouter() with static routes in main.ts or @route decorators on components'); reporter.note('ROUTER_MIGRATION', '2. Update router lifecycle methods - parameters and context have changed'); reporter.note('ROUTER_MIGRATION', '3. Install new router: npm install @aurelia/router'); reporter.note('ROUTER_MIGRATION', '4. Consider @aurelia/router-lite for simpler applications'); reporter.note('ROUTER_MIGRATION', '5. Review Aurelia 2 router documentation for viewport and navigation changes'); } }