From 2b9e44fbe9040c6eea2f04e397fbce52a744775c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 28 Nov 2025 10:11:21 +0000 Subject: [PATCH 1/6] Initial plan From e57cca7571b7fee677717685c348e0ba19524002 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 28 Nov 2025 10:21:59 +0000 Subject: [PATCH 2/6] refactor(migrations): use tsUtils for import checks in 21.0.0 migration - Add IG_LICENSED_PACKAGE_NAME constant for @infragistics/igniteui-angular - Add isIgniteuiImport helper function to handle base and subpath imports - Update namedImportFilter to use isIgniteuiImport for subpath support - Update update-21_0_0_import-migration to use constants from tsUtils - Replace hardcoded package names with constants for maintainability Co-authored-by: damyanpetev <3198469+damyanpetev@users.noreply.github.com> --- .../migrations/common/tsUtils.ts | 16 +++++++++++- .../update-21_0_0_import-migration/index.ts | 25 ++++++++++++++----- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/projects/igniteui-angular/migrations/common/tsUtils.ts b/projects/igniteui-angular/migrations/common/tsUtils.ts index 1c74a906bdd..c31373a7bdf 100644 --- a/projects/igniteui-angular/migrations/common/tsUtils.ts +++ b/projects/igniteui-angular/migrations/common/tsUtils.ts @@ -7,11 +7,25 @@ import { Logger } from './tsLogger'; import { TSLanguageService } from './tsPlugin/TSLanguageService'; export const IG_PACKAGE_NAME = 'igniteui-angular'; +export const IG_LICENSED_PACKAGE_NAME = '@infragistics/igniteui-angular'; export const NG_LANG_SERVICE_PACKAGE_NAME = '@angular/language-service'; export const NG_CORE_PACKAGE_NAME = '@angular/core'; export const CUSTOM_TS_PLUGIN_PATH = './tsPlugin'; export const CUSTOM_TS_PLUGIN_NAME = 'igx-ts-plugin'; +/** + * Checks if the given import path is from igniteui-angular package. + * Handles both OSS (igniteui-angular) and licensed (@infragistics/igniteui-angular) versions, + * as well as subpaths (e.g., igniteui-angular/core, @infragistics/igniteui-angular/grids). + * @param importPath The module specifier text to check + * @returns true if the import path is from igniteui-angular package + */ +export const isIgniteuiImport = (importPath: string): boolean => + importPath === IG_PACKAGE_NAME || + importPath === IG_LICENSED_PACKAGE_NAME || + importPath.startsWith(`${IG_PACKAGE_NAME}/`) || + importPath.startsWith(`${IG_LICENSED_PACKAGE_NAME}/`); + enum SyntaxTokens { ClosingParenthesis = ')', MemberAccess = '.', @@ -86,7 +100,7 @@ export const getImportModulePositions = (sourceText: string, startsWith: string) /** Filters out statements to named imports (e.g. `import {x, y}`) from PACKAGE_IMPORT */ export const namedImportFilter = (statement: ts.Statement) => { if (statement.kind === ts.SyntaxKind.ImportDeclaration && - ((statement as ts.ImportDeclaration).moduleSpecifier as ts.StringLiteral).text.endsWith(IG_PACKAGE_NAME)) { + isIgniteuiImport(((statement as ts.ImportDeclaration).moduleSpecifier as ts.StringLiteral).text)) { const clause = (statement as ts.ImportDeclaration).importClause; return clause && clause.namedBindings && clause.namedBindings.kind === ts.SyntaxKind.NamedImports; diff --git a/projects/igniteui-angular/migrations/update-21_0_0_import-migration/index.ts b/projects/igniteui-angular/migrations/update-21_0_0_import-migration/index.ts index ead1b53a399..391511ec27f 100644 --- a/projects/igniteui-angular/migrations/update-21_0_0_import-migration/index.ts +++ b/projects/igniteui-angular/migrations/update-21_0_0_import-migration/index.ts @@ -5,6 +5,7 @@ import type { Tree } from '@angular-devkit/schematics'; import * as ts from 'typescript'; +import { IG_PACKAGE_NAME, IG_LICENSED_PACKAGE_NAME } from '../common/tsUtils'; const version = '21.0.0'; @@ -709,6 +710,19 @@ const TYPE_RENAMES = new Map([ ['IgxColumPatternValidatorDirective', { newName: 'IgxColumnPatternValidatorDirective', entryPoint: 'grids/core' }], ]); +/** + * Quick check if file content has base igniteui-angular imports (without entry point subpaths). + * Uses regex to check for imports like: from 'igniteui-angular' or from "@infragistics/igniteui-angular" + * but not from 'igniteui-angular/core' etc. + */ +const hasBaseIgniteuiImports = (content: string): boolean => { + // Match imports from base package (with single or double quotes) but not subpaths + // Matches: from 'igniteui-angular' or from "igniteui-angular" (end of import) + // Does not match: from 'igniteui-angular/core' (has subpath) + const baseImportPattern = /from\s+['"](@infragistics\/)?igniteui-angular['"]/; + return baseImportPattern.test(content); +}; + function migrateImportDeclaration(node: ts.ImportDeclaration, sourceFile: ts.SourceFile): { start: number, end: number, replacement: string } | null { const moduleSpecifier = node.moduleSpecifier; if (!ts.isStringLiteral(moduleSpecifier)) { @@ -717,8 +731,8 @@ function migrateImportDeclaration(node: ts.ImportDeclaration, sourceFile: ts.Sou const importPath = moduleSpecifier.text; - // Only process igniteui-angular imports (not already using entry points) - if (importPath !== 'igniteui-angular' && importPath !== '@infragistics/igniteui-angular') { + // Only process base igniteui-angular imports (not already using entry points) + if (importPath !== IG_PACKAGE_NAME && importPath !== IG_LICENSED_PACKAGE_NAME) { return null; } @@ -799,7 +813,7 @@ function migrateFile(filePath: string, content: string): string { // Track old type names that were imported const moduleSpecifier = node.moduleSpecifier; - if (ts.isStringLiteral(moduleSpecifier) && (moduleSpecifier.text === 'igniteui-angular' || moduleSpecifier.text === '@infragistics/igniteui-angular')) { + if (ts.isStringLiteral(moduleSpecifier) && (moduleSpecifier.text === IG_PACKAGE_NAME || moduleSpecifier.text === IG_LICENSED_PACKAGE_NAME)) { const importClause = node.importClause; if (importClause?.namedBindings && ts.isNamedImports(importClause.namedBindings)) { for (const element of importClause.namedBindings.elements) { @@ -876,9 +890,8 @@ export default function migrate(): Rule { const originalContent = content.toString(); - // Check if file has igniteui-angular imports - if (!originalContent.includes("from 'igniteui-angular'") && !originalContent.includes('from "igniteui-angular"') && - !originalContent.includes("from '@infragistics/igniteui-angular'") && !originalContent.includes('from "@infragistics/igniteui-angular"')) { + // Check if file has base igniteui-angular imports (not using entry point subpaths) + if (!hasBaseIgniteuiImports(originalContent)) { return; } From 5a27975615dd3bc2f520c50a1d4092b963fe828e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 28 Nov 2025 10:27:33 +0000 Subject: [PATCH 3/6] refactor: optimize regex compilation by moving it outside function Address code review feedback by pre-compiling the BASE_IMPORT_PATTERN regex at module level instead of recompiling on each function call. Co-authored-by: damyanpetev <3198469+damyanpetev@users.noreply.github.com> --- .../update-21_0_0_import-migration/index.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/projects/igniteui-angular/migrations/update-21_0_0_import-migration/index.ts b/projects/igniteui-angular/migrations/update-21_0_0_import-migration/index.ts index 391511ec27f..d746203194a 100644 --- a/projects/igniteui-angular/migrations/update-21_0_0_import-migration/index.ts +++ b/projects/igniteui-angular/migrations/update-21_0_0_import-migration/index.ts @@ -6,6 +6,7 @@ import type { } from '@angular-devkit/schematics'; import * as ts from 'typescript'; import { IG_PACKAGE_NAME, IG_LICENSED_PACKAGE_NAME } from '../common/tsUtils'; +import { escapeRegExp } from '../common/util'; const version = '21.0.0'; @@ -710,18 +711,18 @@ const TYPE_RENAMES = new Map([ ['IgxColumPatternValidatorDirective', { newName: 'IgxColumnPatternValidatorDirective', entryPoint: 'grids/core' }], ]); +// Pre-compiled regex for quick file content checks - matches base package imports only (not subpaths) +const BASE_IMPORT_PATTERN = new RegExp( + `from\\s+['"](${escapeRegExp(IG_LICENSED_PACKAGE_NAME)}|${escapeRegExp(IG_PACKAGE_NAME)})['"]` +); + /** - * Quick check if file content has base igniteui-angular imports (without entry point subpaths). + * Quick regex-based check if file content has base igniteui-angular imports (without entry point subpaths). + * This is a performance optimization to avoid parsing files that don't need migration. * Uses regex to check for imports like: from 'igniteui-angular' or from "@infragistics/igniteui-angular" - * but not from 'igniteui-angular/core' etc. + * but not from 'igniteui-angular/core' etc. which are already using entry points. */ -const hasBaseIgniteuiImports = (content: string): boolean => { - // Match imports from base package (with single or double quotes) but not subpaths - // Matches: from 'igniteui-angular' or from "igniteui-angular" (end of import) - // Does not match: from 'igniteui-angular/core' (has subpath) - const baseImportPattern = /from\s+['"](@infragistics\/)?igniteui-angular['"]/; - return baseImportPattern.test(content); -}; +const hasBaseIgniteuiImports = (content: string): boolean => BASE_IMPORT_PATTERN.test(content); function migrateImportDeclaration(node: ts.ImportDeclaration, sourceFile: ts.SourceFile): { start: number, end: number, replacement: string } | null { const moduleSpecifier = node.moduleSpecifier; From 00b00e1b73a609847a70838b0e6d452ae5b79b7b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 28 Nov 2025 12:08:53 +0000 Subject: [PATCH 4/6] refactor: use namedImportFilter util and restore .includes() checks Address review feedback: - Use namedImportFilter from tsUtils instead of duplicating logic - Restore .includes() checks with constants (faster than regex) - Remove unnecessary regex-based hasBaseIgniteuiImports function Co-authored-by: damyanpetev <3198469+damyanpetev@users.noreply.github.com> --- .../update-21_0_0_import-migration/index.ts | 38 +++++-------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/projects/igniteui-angular/migrations/update-21_0_0_import-migration/index.ts b/projects/igniteui-angular/migrations/update-21_0_0_import-migration/index.ts index d746203194a..4411cdeb38e 100644 --- a/projects/igniteui-angular/migrations/update-21_0_0_import-migration/index.ts +++ b/projects/igniteui-angular/migrations/update-21_0_0_import-migration/index.ts @@ -5,8 +5,7 @@ import type { Tree } from '@angular-devkit/schematics'; import * as ts from 'typescript'; -import { IG_PACKAGE_NAME, IG_LICENSED_PACKAGE_NAME } from '../common/tsUtils'; -import { escapeRegExp } from '../common/util'; +import { IG_PACKAGE_NAME, IG_LICENSED_PACKAGE_NAME, namedImportFilter } from '../common/tsUtils'; const version = '21.0.0'; @@ -711,25 +710,13 @@ const TYPE_RENAMES = new Map([ ['IgxColumPatternValidatorDirective', { newName: 'IgxColumnPatternValidatorDirective', entryPoint: 'grids/core' }], ]); -// Pre-compiled regex for quick file content checks - matches base package imports only (not subpaths) -const BASE_IMPORT_PATTERN = new RegExp( - `from\\s+['"](${escapeRegExp(IG_LICENSED_PACKAGE_NAME)}|${escapeRegExp(IG_PACKAGE_NAME)})['"]` -); - -/** - * Quick regex-based check if file content has base igniteui-angular imports (without entry point subpaths). - * This is a performance optimization to avoid parsing files that don't need migration. - * Uses regex to check for imports like: from 'igniteui-angular' or from "@infragistics/igniteui-angular" - * but not from 'igniteui-angular/core' etc. which are already using entry points. - */ -const hasBaseIgniteuiImports = (content: string): boolean => BASE_IMPORT_PATTERN.test(content); - function migrateImportDeclaration(node: ts.ImportDeclaration, sourceFile: ts.SourceFile): { start: number, end: number, replacement: string } | null { - const moduleSpecifier = node.moduleSpecifier; - if (!ts.isStringLiteral(moduleSpecifier)) { + // Use namedImportFilter to check if this is a valid igniteui-angular named import + if (!namedImportFilter(node)) { return null; } + const moduleSpecifier = node.moduleSpecifier as ts.StringLiteral; const importPath = moduleSpecifier.text; // Only process base igniteui-angular imports (not already using entry points) @@ -737,19 +724,13 @@ function migrateImportDeclaration(node: ts.ImportDeclaration, sourceFile: ts.Sou return null; } - const importClause = node.importClause; - if (!importClause || !importClause.namedBindings) { - return null; - } - - if (!ts.isNamedImports(importClause.namedBindings)) { - return null; - } + const importClause = node.importClause!; + const namedBindings = importClause.namedBindings as ts.NamedImports; // Group imports by entry point const entryPointGroups = new Map(); - for (const element of importClause.namedBindings.elements) { + for (const element of namedBindings.elements) { const name = element.name.text; const alias = element.propertyName?.text; const importName = alias || name; @@ -891,8 +872,9 @@ export default function migrate(): Rule { const originalContent = content.toString(); - // Check if file has base igniteui-angular imports (not using entry point subpaths) - if (!hasBaseIgniteuiImports(originalContent)) { + // Check if file has igniteui-angular imports + if (!originalContent.includes(`from '${IG_PACKAGE_NAME}'`) && !originalContent.includes(`from "${IG_PACKAGE_NAME}"`) && + !originalContent.includes(`from '${IG_LICENSED_PACKAGE_NAME}'`) && !originalContent.includes(`from "${IG_LICENSED_PACKAGE_NAME}"`)) { return; } From b2ff7fffe561726bb548a7731c3fb96303f030a3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 28 Nov 2025 12:11:27 +0000 Subject: [PATCH 5/6] refactor: improve comments to clarify assertions and checks - Add comment explaining non-null assertions are safe due to namedImportFilter validation - Clarify the quick check is for base imports (not subpaths) Co-authored-by: damyanpetev <3198469+damyanpetev@users.noreply.github.com> --- .../migrations/update-21_0_0_import-migration/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/projects/igniteui-angular/migrations/update-21_0_0_import-migration/index.ts b/projects/igniteui-angular/migrations/update-21_0_0_import-migration/index.ts index 4411cdeb38e..98df1fb7554 100644 --- a/projects/igniteui-angular/migrations/update-21_0_0_import-migration/index.ts +++ b/projects/igniteui-angular/migrations/update-21_0_0_import-migration/index.ts @@ -724,6 +724,7 @@ function migrateImportDeclaration(node: ts.ImportDeclaration, sourceFile: ts.Sou return null; } + // namedImportFilter already validates importClause and namedBindings exist with NamedImports type const importClause = node.importClause!; const namedBindings = importClause.namedBindings as ts.NamedImports; @@ -872,7 +873,7 @@ export default function migrate(): Rule { const originalContent = content.toString(); - // Check if file has igniteui-angular imports + // Quick check if file has base igniteui-angular imports (not using entry point subpaths) if (!originalContent.includes(`from '${IG_PACKAGE_NAME}'`) && !originalContent.includes(`from "${IG_PACKAGE_NAME}"`) && !originalContent.includes(`from '${IG_LICENSED_PACKAGE_NAME}'`) && !originalContent.includes(`from "${IG_LICENSED_PACKAGE_NAME}"`)) { return; From 1c15722fd4be444103f7dcec65b1b57f05f135a1 Mon Sep 17 00:00:00 2001 From: damyanpetev Date: Fri, 28 Nov 2025 15:57:48 +0200 Subject: [PATCH 6/6] refactor(migrations): igNamedImportFilter type guard and cleanup --- .../migrations/common/tsUtils.ts | 19 +++++++++--------- .../migrations/update-17_0_0/index.ts | 4 ++-- .../update-21_0_0_import-migration/index.ts | 20 +++++-------------- 3 files changed, 16 insertions(+), 27 deletions(-) diff --git a/projects/igniteui-angular/migrations/common/tsUtils.ts b/projects/igniteui-angular/migrations/common/tsUtils.ts index c31373a7bdf..9a03e7987b6 100644 --- a/projects/igniteui-angular/migrations/common/tsUtils.ts +++ b/projects/igniteui-angular/migrations/common/tsUtils.ts @@ -97,13 +97,13 @@ export const getImportModulePositions = (sourceText: string, startsWith: string) return positions; }; -/** Filters out statements to named imports (e.g. `import {x, y}`) from PACKAGE_IMPORT */ -export const namedImportFilter = (statement: ts.Statement) => { - if (statement.kind === ts.SyntaxKind.ImportDeclaration && - isIgniteuiImport(((statement as ts.ImportDeclaration).moduleSpecifier as ts.StringLiteral).text)) { - - const clause = (statement as ts.ImportDeclaration).importClause; - return clause && clause.namedBindings && clause.namedBindings.kind === ts.SyntaxKind.NamedImports; +/** Filters out statements to named imports (e.g. `import {x, y}`) from IG_PACKAGE_NAME */ +export const igNamedImportFilter = ( + statement: ts.Statement, +): statement is ts.ImportDeclaration & { moduleSpecifier: ts.StringLiteral } & { importClause: { namedBindings: ts.NamedImports } } => { + if (ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier) && isIgniteuiImport(statement.moduleSpecifier.text)) { + const clause = statement.importClause; + return clause?.namedBindings && clause.namedBindings.kind === ts.SyntaxKind.NamedImports; } return false; }; @@ -114,14 +114,13 @@ export const getRenamePositions = (sourcePath: string, name: string, service: ts const source = service.getProgram().getSourceFile(sourcePath); const positions = []; - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const imports = source.statements.filter(<(a: ts.Statement) => a is ts.ImportDeclaration>namedImportFilter); + const imports = source.statements.filter(igNamedImportFilter); if (!imports.length) { return positions; } const elements: ts.NodeArray = imports - .map(x => (x.importClause.namedBindings as ts.NamedImports).elements) + .map(x => x.importClause.namedBindings.elements) .reduce((prev, current) => prev.concat(current) as unknown as ts.NodeArray); for (const elem of elements) { diff --git a/projects/igniteui-angular/migrations/update-17_0_0/index.ts b/projects/igniteui-angular/migrations/update-17_0_0/index.ts index fc63c790366..2dd2d32ca63 100644 --- a/projects/igniteui-angular/migrations/update-17_0_0/index.ts +++ b/projects/igniteui-angular/migrations/update-17_0_0/index.ts @@ -8,7 +8,7 @@ import * as ts from 'typescript'; import { UpdateChanges } from '../common/UpdateChanges'; // use bare specifier to escape the schematics encapsulation for the dynamic import: import { nativeImport } from 'igniteui-angular/migrations/common/import-helper.js'; -import { namedImportFilter } from '../common/tsUtils'; +import { igNamedImportFilter } from '../common/tsUtils'; import { FileChange, findElementNodes, getAttribute, getSourceOffset, hasAttribute, parseFile } from '../common/util'; const version = '17.0.0'; @@ -95,7 +95,7 @@ export default (): Rule => async (host: Tree, context: SchematicContext) => { var fileContent = host.read(filePath).toString(); const source = ts.createSourceFile('', fileContent, ts.ScriptTarget.Latest, true) - const igImports = source.statements.filter(<(a: ts.Statement) => a is ts.ImportDeclaration>namedImportFilter); + const igImports = source.statements.filter(igNamedImportFilter); // Find all animations imported from 'igniteui-angular' and delete them. for (const igImport of igImports) { diff --git a/projects/igniteui-angular/migrations/update-21_0_0_import-migration/index.ts b/projects/igniteui-angular/migrations/update-21_0_0_import-migration/index.ts index 98df1fb7554..2db17b31186 100644 --- a/projects/igniteui-angular/migrations/update-21_0_0_import-migration/index.ts +++ b/projects/igniteui-angular/migrations/update-21_0_0_import-migration/index.ts @@ -5,7 +5,7 @@ import type { Tree } from '@angular-devkit/schematics'; import * as ts from 'typescript'; -import { IG_PACKAGE_NAME, IG_LICENSED_PACKAGE_NAME, namedImportFilter } from '../common/tsUtils'; +import { IG_PACKAGE_NAME, IG_LICENSED_PACKAGE_NAME, igNamedImportFilter } from '../common/tsUtils'; const version = '21.0.0'; @@ -711,22 +711,12 @@ const TYPE_RENAMES = new Map([ ]); function migrateImportDeclaration(node: ts.ImportDeclaration, sourceFile: ts.SourceFile): { start: number, end: number, replacement: string } | null { - // Use namedImportFilter to check if this is a valid igniteui-angular named import - if (!namedImportFilter(node)) { + if (!igNamedImportFilter(node)) { return null; } - const moduleSpecifier = node.moduleSpecifier as ts.StringLiteral; - const importPath = moduleSpecifier.text; - - // Only process base igniteui-angular imports (not already using entry points) - if (importPath !== IG_PACKAGE_NAME && importPath !== IG_LICENSED_PACKAGE_NAME) { - return null; - } - - // namedImportFilter already validates importClause and namedBindings exist with NamedImports type - const importClause = node.importClause!; - const namedBindings = importClause.namedBindings as ts.NamedImports; + const importPath = node.moduleSpecifier.text; + const namedBindings = node.importClause.namedBindings; // Group imports by entry point const entryPointGroups = new Map(); @@ -873,7 +863,7 @@ export default function migrate(): Rule { const originalContent = content.toString(); - // Quick check if file has base igniteui-angular imports (not using entry point subpaths) + // Check if file has base igniteui-angular imports if (!originalContent.includes(`from '${IG_PACKAGE_NAME}'`) && !originalContent.includes(`from "${IG_PACKAGE_NAME}"`) && !originalContent.includes(`from '${IG_LICENSED_PACKAGE_NAME}'`) && !originalContent.includes(`from "${IG_LICENSED_PACKAGE_NAME}"`)) { return;