|
| 1 | +import type { |
| 2 | + Rule, |
| 3 | + SchematicContext, |
| 4 | + Tree |
| 5 | +} from '@angular-devkit/schematics'; |
| 6 | +import * as ts from 'typescript'; |
| 7 | +import * as path from 'path'; |
| 8 | + |
| 9 | +const version = '21.0.0'; |
| 10 | + |
| 11 | +// Entry point mapping for components |
| 12 | +const ENTRY_POINT_MAP = new Map<string, string>([ |
| 13 | + // Components |
| 14 | + ['IgxAccordionComponent', 'accordion'], |
| 15 | + ['IgxAccordionModule', 'accordion'], |
| 16 | + ['IgxActionStripComponent', 'action-strip'], |
| 17 | + ['IgxActionStripModule', 'action-strip'], |
| 18 | + ['IgxAvatarComponent', 'avatar'], |
| 19 | + ['IgxAvatarModule', 'avatar'], |
| 20 | + ['IgxBadgeComponent', 'badge'], |
| 21 | + ['IgxBadgeModule', 'badge'], |
| 22 | + ['IgxBannerComponent', 'banner'], |
| 23 | + ['IgxBannerModule', 'banner'], |
| 24 | + ['IgxButtonGroupComponent', 'buttonGroup'], |
| 25 | + ['IgxButtonGroupModule', 'buttonGroup'], |
| 26 | + ['IgxCalendarComponent', 'calendar'], |
| 27 | + ['IgxCalendarModule', 'calendar'], |
| 28 | + ['IgxCardComponent', 'card'], |
| 29 | + ['IgxCardModule', 'card'], |
| 30 | + ['IgxCarouselComponent', 'carousel'], |
| 31 | + ['IgxCarouselModule', 'carousel'], |
| 32 | + ['IgxCheckboxComponent', 'checkbox'], |
| 33 | + ['IgxCheckboxModule', 'checkbox'], |
| 34 | + ['IgxChipsComponent', 'chips'], |
| 35 | + ['IgxChipsModule', 'chips'], |
| 36 | + ['IgxComboComponent', 'combo'], |
| 37 | + ['IgxComboModule', 'combo'], |
| 38 | + ['IgxDatePickerComponent', 'date-picker'], |
| 39 | + ['IgxDatePickerModule', 'date-picker'], |
| 40 | + ['IgxDateRangePickerComponent', 'date-range-picker'], |
| 41 | + ['IgxDateRangePickerModule', 'date-range-picker'], |
| 42 | + ['IgxDialogComponent', 'dialog'], |
| 43 | + ['IgxDialogModule', 'dialog'], |
| 44 | + ['IgxDropDownComponent', 'drop-down'], |
| 45 | + ['IgxDropDownModule', 'drop-down'], |
| 46 | + ['IgxAutocompleteDirective', 'drop-down'], // Breaking change |
| 47 | + ['IgxExpansionPanelComponent', 'expansion-panel'], |
| 48 | + ['IgxExpansionPanelModule', 'expansion-panel'], |
| 49 | + ['IgxGridComponent', 'grids'], |
| 50 | + ['IgxTreeGridComponent', 'grids'], |
| 51 | + ['IgxHierarchicalGridComponent', 'grids'], |
| 52 | + ['IgxPivotGridComponent', 'grids'], |
| 53 | + ['IgxGridModule', 'grids'], |
| 54 | + ['IgxIconComponent', 'icon'], |
| 55 | + ['IgxIconModule', 'icon'], |
| 56 | + ['IgxInputGroupComponent', 'input-group'], |
| 57 | + ['IgxInputGroupModule', 'input-group'], |
| 58 | + ['IgxInputDirective', 'input-group'], // Breaking change |
| 59 | + ['IgxLabelDirective', 'input-group'], // Breaking change |
| 60 | + ['IgxHintDirective', 'input-group'], // Breaking change |
| 61 | + ['IgxPrefixDirective', 'input-group'], // Breaking change |
| 62 | + ['IgxSuffixDirective', 'input-group'], // Breaking change |
| 63 | + ['IgxListComponent', 'list'], |
| 64 | + ['IgxListModule', 'list'], |
| 65 | + ['IgxNavbarComponent', 'navbar'], |
| 66 | + ['IgxNavbarModule', 'navbar'], |
| 67 | + ['IgxNavigationDrawerComponent', 'navigation-drawer'], |
| 68 | + ['IgxNavigationDrawerModule', 'navigation-drawer'], |
| 69 | + ['IgxPaginatorComponent', 'paginator'], |
| 70 | + ['IgxPaginatorModule', 'paginator'], |
| 71 | + ['IgxCircularProgressBarComponent', 'progressbar'], |
| 72 | + ['IgxLinearProgressBarComponent', 'progressbar'], |
| 73 | + ['IgxProgressBarModule', 'progressbar'], |
| 74 | + ['IgxQueryBuilderComponent', 'query-builder'], |
| 75 | + ['IgxQueryBuilderModule', 'query-builder'], |
| 76 | + ['IgxRadioComponent', 'radio'], |
| 77 | + ['IgxRadioModule', 'radio'], |
| 78 | + ['IgxRadioGroupDirective', 'radio'], // Breaking change |
| 79 | + ['IgxSelectComponent', 'select'], |
| 80 | + ['IgxSelectModule', 'select'], |
| 81 | + ['IgxSimpleComboComponent', 'simple-combo'], |
| 82 | + ['IgxSimpleComboModule', 'simple-combo'], |
| 83 | + ['IgxSliderComponent', 'slider'], |
| 84 | + ['IgxSliderModule', 'slider'], |
| 85 | + ['IgxSnackbarComponent', 'snackbar'], |
| 86 | + ['IgxSnackbarModule', 'snackbar'], |
| 87 | + ['IgxSplitterComponent', 'splitter'], |
| 88 | + ['IgxSplitterModule', 'splitter'], |
| 89 | + ['IgxStepperComponent', 'stepper'], |
| 90 | + ['IgxStepperModule', 'stepper'], |
| 91 | + ['IgxSwitchComponent', 'switch'], |
| 92 | + ['IgxSwitchModule', 'switch'], |
| 93 | + ['IgxTabsComponent', 'tabs'], |
| 94 | + ['IgxTabsModule', 'tabs'], |
| 95 | + ['IgxTimePickerComponent', 'time-picker'], |
| 96 | + ['IgxTimePickerModule', 'time-picker'], |
| 97 | + ['IgxToastComponent', 'toast'], |
| 98 | + ['IgxToastModule', 'toast'], |
| 99 | + ['IgxTreeComponent', 'tree'], |
| 100 | + ['IgxTreeModule', 'tree'], |
| 101 | +]); |
| 102 | + |
| 103 | +// Core exports that stay in core |
| 104 | +const CORE_EXPORTS = new Set([ |
| 105 | + 'DisplayDensity', |
| 106 | + 'Size', |
| 107 | + 'OverlaySettings', |
| 108 | + 'PositionSettings', |
| 109 | + 'ConnectedPositioningStrategy', |
| 110 | + 'AbsoluteScrollStrategy', |
| 111 | + 'CancelableEventArgs', |
| 112 | + 'IBaseEventArgs', |
| 113 | +]); |
| 114 | + |
| 115 | +function migrateImportDeclaration(node: ts.ImportDeclaration, sourceFile: ts.SourceFile): { start: number, end: number, replacement: string } | null { |
| 116 | + const moduleSpecifier = node.moduleSpecifier; |
| 117 | + if (!ts.isStringLiteral(moduleSpecifier)) { |
| 118 | + return null; |
| 119 | + } |
| 120 | + |
| 121 | + const importPath = moduleSpecifier.text; |
| 122 | + |
| 123 | + // Only process igniteui-angular imports (not already using entry points) |
| 124 | + if (importPath !== 'igniteui-angular') { |
| 125 | + return null; |
| 126 | + } |
| 127 | + |
| 128 | + const importClause = node.importClause; |
| 129 | + if (!importClause || !importClause.namedBindings) { |
| 130 | + return null; |
| 131 | + } |
| 132 | + |
| 133 | + if (!ts.isNamedImports(importClause.namedBindings)) { |
| 134 | + return null; |
| 135 | + } |
| 136 | + |
| 137 | + // Group imports by entry point |
| 138 | + const entryPointGroups = new Map<string, string[]>(); |
| 139 | + |
| 140 | + for (const element of importClause.namedBindings.elements) { |
| 141 | + const name = element.name.text; |
| 142 | + const alias = element.propertyName?.text; |
| 143 | + const importName = alias || name; |
| 144 | + const fullImport = alias ? `${alias} as ${name}` : name; |
| 145 | + |
| 146 | + // Determine target entry point |
| 147 | + let targetEntryPoint = 'core'; // Default to core |
| 148 | + |
| 149 | + if (ENTRY_POINT_MAP.has(importName)) { |
| 150 | + targetEntryPoint = ENTRY_POINT_MAP.get(importName)!; |
| 151 | + } |
| 152 | + |
| 153 | + if (!entryPointGroups.has(targetEntryPoint)) { |
| 154 | + entryPointGroups.set(targetEntryPoint, []); |
| 155 | + } |
| 156 | + entryPointGroups.get(targetEntryPoint)!.push(fullImport); |
| 157 | + } |
| 158 | + |
| 159 | + // Generate new import statements |
| 160 | + const newImports: string[] = []; |
| 161 | + for (const [entryPoint, imports] of entryPointGroups) { |
| 162 | + const sortedImports = imports.sort(); |
| 163 | + newImports.push(`import { ${sortedImports.join(', ')} } from 'igniteui-angular/${entryPoint}';`); |
| 164 | + } |
| 165 | + |
| 166 | + return { |
| 167 | + start: node.getStart(sourceFile), |
| 168 | + end: node.getEnd(), |
| 169 | + replacement: newImports.join('\n') |
| 170 | + }; |
| 171 | +} |
| 172 | + |
| 173 | +function migrateFile(filePath: string, content: string): string { |
| 174 | + const sourceFile = ts.createSourceFile( |
| 175 | + filePath, |
| 176 | + content, |
| 177 | + ts.ScriptTarget.Latest, |
| 178 | + true |
| 179 | + ); |
| 180 | + |
| 181 | + const changes: { start: number, end: number, replacement: string }[] = []; |
| 182 | + |
| 183 | + function visit(node: ts.Node) { |
| 184 | + if (ts.isImportDeclaration(node)) { |
| 185 | + const change = migrateImportDeclaration(node, sourceFile); |
| 186 | + if (change) { |
| 187 | + changes.push(change); |
| 188 | + } |
| 189 | + } |
| 190 | + ts.forEachChild(node, visit); |
| 191 | + } |
| 192 | + |
| 193 | + visit(sourceFile); |
| 194 | + |
| 195 | + // Apply changes in reverse order to maintain positions |
| 196 | + changes.sort((a, b) => b.start - a.start); |
| 197 | + |
| 198 | + let result = content; |
| 199 | + for (const change of changes) { |
| 200 | + result = result.substring(0, change.start) + change.replacement + result.substring(change.end); |
| 201 | + } |
| 202 | + |
| 203 | + return result; |
| 204 | +} |
| 205 | + |
| 206 | +export default (): Rule => async (host: Tree, context: SchematicContext) => { |
| 207 | + context.logger.info(`Applying migration for Ignite UI for Angular to version ${version}`); |
| 208 | + context.logger.info('Migrating imports to new entry points...'); |
| 209 | + |
| 210 | + const visit: FileVisitor = (filePath) => { |
| 211 | + // Only process TypeScript files |
| 212 | + if (!filePath.endsWith('.ts')) { |
| 213 | + return; |
| 214 | + } |
| 215 | + |
| 216 | + // Skip node_modules and dist |
| 217 | + if (filePath.includes('node_modules') || filePath.includes('dist')) { |
| 218 | + return; |
| 219 | + } |
| 220 | + |
| 221 | + const content = host.read(filePath); |
| 222 | + if (!content) { |
| 223 | + return; |
| 224 | + } |
| 225 | + |
| 226 | + const originalContent = content.toString(); |
| 227 | + |
| 228 | + // Check if file has igniteui-angular imports |
| 229 | + if (!originalContent.includes("from 'igniteui-angular'") && !originalContent.includes('from "igniteui-angular"')) { |
| 230 | + return; |
| 231 | + } |
| 232 | + |
| 233 | + const migratedContent = migrateFile(filePath, originalContent); |
| 234 | + |
| 235 | + if (migratedContent !== originalContent) { |
| 236 | + host.overwrite(filePath, migratedContent); |
| 237 | + context.logger.info(` ✓ Migrated ${filePath}`); |
| 238 | + } |
| 239 | + }; |
| 240 | + |
| 241 | + host.visit(visit); |
| 242 | + |
| 243 | + context.logger.info('Migration complete!'); |
| 244 | + context.logger.info('Breaking changes:'); |
| 245 | + context.logger.info(' - Input directives moved to igniteui-angular/input-group'); |
| 246 | + context.logger.info(' - IgxAutocompleteDirective moved to igniteui-angular/drop-down'); |
| 247 | + context.logger.info(' - IgxRadioGroupDirective moved to igniteui-angular/radio'); |
| 248 | +}; |
0 commit comments