@eui/core 22.0.0-alpha.32 → 22.0.0-alpha.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -909,12 +909,12 @@
909
909
  },
910
910
  {
911
911
  "name": "Edit",
912
- "id": "interface-Edit-e1cd02924eb82a618c71a0b26c081bdd020519d2699aa0e2dc98640c4e0f347c3649b967c5fc87543e41bbbfabd1304835850ccc38f40684d6521c242b2030ed-2",
913
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
912
+ "id": "interface-Edit-cac2ac952d8245647e8a48b450e8f127a36bcffb394cf2cd81fe9c0810ed02c19e5e1629dbf29a68a5c6f675aed99570e9ac04ae470e13315e20c92cc0c6c453-2",
913
+ "file": "packages/core/schematics/migrate-to-signals/index.ts",
914
914
  "deprecated": false,
915
915
  "deprecationMessage": "",
916
916
  "type": "interface",
917
- "sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\nconst OLD_TAG = 'eui-toolbar-menu';\nconst NEW_TAG = 'eui-toolbar-mega-menu';\nconst OLD_COMPONENT = 'EuiToolbarMenuComponent';\nconst NEW_COMPONENT = 'EuiToolbarMegaMenuComponent';\nconst OLD_INTERFACE = 'ToolbarItem';\nconst NEW_INTERFACE = 'EuiMenuItem';\nconst NEW_COMPONENT_PATH = '@eui/components/layout';\nconst NEW_INTERFACE_PATH = '@eui/core';\nconst REMOVED_OUTPUT = 'menuItemClick';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\ninterface Edit {\n start: number;\n end: number;\n replacement: string;\n}\n\nexport function migrateEuiToolbarMenu(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let fileCount = 0;\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(OLD_TAG) && !original.includes(OLD_COMPONENT) && !original.includes(OLD_INTERFACE)) return;\n\n let result: string;\n\n if (path.endsWith('.html')) {\n result = migrateTemplate(original, path, context);\n } else {\n result = migrateTypeScript(original, path, context);\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would migrate eui-toolbar-menu → eui-toolbar-mega-menu in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n fileCount++;\n }\n });\n\n context.logger.info(`Migrated eui-toolbar-menu → eui-toolbar-mega-menu in ${fileCount} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction migrateTemplate(source: string, filePath: string, context: SchematicContext): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: Edit[] = [];\n\n visitNodes(parsed.nodes, source, edits, filePath, context);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateTypeScript(source: string, filePath: string, context: SchematicContext): string {\n let result = migrateInlineTemplates(source, filePath, context);\n result = migrateImportsAndTypes(result, filePath, context);\n return result;\n}\n\nfunction migrateInlineTemplates(source: string, filePath: string, context: SchematicContext): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: Edit[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(OLD_TAG)) {\n ts.forEachChild(node, visit); return; \n}\n const migrated = migrateTemplate(rawTemplate, filePath, context);\n if (migrated !== rawTemplate) changes.push({ start, end, replacement: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n return applyEdits(source, changes);\n}\n\nfunction migrateImportsAndTypes(source: string, filePath: string, context: SchematicContext): string {\n if (!source.includes(OLD_COMPONENT) && !source.includes(OLD_INTERFACE)) return source;\n\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: Edit[] = [];\n\n // Track if EuiMenuItem is already imported from @eui/core\n let hasEuiMenuItemImport = false;\n\n // First pass: analyze imports\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const moduleSpec = (stmt.moduleSpecifier as ts.StringLiteral).text;\n const namedBindings = stmt.importClause?.namedBindings;\n if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;\n\n for (const specifier of namedBindings.elements) {\n if (specifier.name.text === NEW_INTERFACE && moduleSpec === NEW_INTERFACE_PATH) {\n hasEuiMenuItemImport = true;\n }\n }\n }\n\n // Second pass: collect edits for import declarations\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const namedBindings = stmt.importClause?.namedBindings;\n if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;\n\n const moduleSpec = stmt.moduleSpecifier as ts.StringLiteral;\n const specifiers = namedBindings.elements;\n const hasComponent = specifiers.some((s) => s.name.text === OLD_COMPONENT);\n const hasInterface = specifiers.some((s) => s.name.text === OLD_INTERFACE);\n\n if (hasComponent && hasInterface) {\n // Both are in the same import → must split into two different paths\n const others = specifiers.filter((s) => s.name.text !== OLD_COMPONENT && s.name.text !== OLD_INTERFACE);\n const lines: string[] = [];\n lines.push(`import { ${NEW_COMPONENT} } from '${NEW_COMPONENT_PATH}';`);\n if (!hasEuiMenuItemImport) {\n lines.push(`import { ${NEW_INTERFACE} } from '${NEW_INTERFACE_PATH}';`);\n }\n if (others.length > 0) {\n const otherNames = others.map((s) => s.name.text).join(', ');\n lines.push(`import { ${otherNames} } from '${moduleSpec.text}';`);\n }\n edits.push({\n start: stmt.getStart(sourceFile),\n end: stmt.getEnd(),\n replacement: lines.join('\\n'),\n });\n } else if (hasComponent) {\n edits.push({\n start: moduleSpec.getStart(sourceFile) + 1,\n end: moduleSpec.getEnd() - 1,\n replacement: NEW_COMPONENT_PATH,\n });\n for (const specifier of specifiers) {\n if (specifier.name.text === OLD_COMPONENT) {\n edits.push({\n start: specifier.name.getStart(sourceFile),\n end: specifier.name.getEnd(),\n replacement: NEW_COMPONENT,\n });\n }\n }\n } else if (hasInterface) {\n if (hasEuiMenuItemImport) {\n removeImportSpecifier(namedBindings, specifiers.find((s) => s.name.text === OLD_INTERFACE)!, sourceFile, edits);\n } else {\n edits.push({\n start: moduleSpec.getStart(sourceFile) + 1,\n end: moduleSpec.getEnd() - 1,\n replacement: NEW_INTERFACE_PATH,\n });\n for (const specifier of specifiers) {\n if (specifier.name.text === OLD_INTERFACE) {\n edits.push({\n start: specifier.name.getStart(sourceFile),\n end: specifier.name.getEnd(),\n replacement: NEW_INTERFACE,\n });\n }\n }\n }\n }\n }\n\n // Third pass: rename identifier references in non-import positions\n const visitRefs = (node: ts.Node): void => {\n if (ts.isImportDeclaration(node)) return; // skip imports (already handled)\n if (ts.isIdentifier(node)) {\n if (node.text === OLD_COMPONENT) {\n edits.push({ start: node.getStart(sourceFile), end: node.getEnd(), replacement: NEW_COMPONENT });\n }\n if (node.text === OLD_INTERFACE) {\n edits.push({ start: node.getStart(sourceFile), end: node.getEnd(), replacement: NEW_INTERFACE });\n }\n }\n ts.forEachChild(node, visitRefs);\n };\n\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) {\n visitRefs(stmt);\n }\n }\n\n // Warn about ToolbarItem-specific properties\n warnRemovedProperties(sourceFile, filePath, context);\n\n return applyEdits(source, edits);\n}\n\nfunction removeImportSpecifier(\n namedImports: ts.NamedImports,\n specifier: ts.ImportSpecifier,\n sourceFile: ts.SourceFile,\n edits: Edit[],\n): void {\n const elements = namedImports.elements;\n if (elements.length === 1) {\n // Remove the entire import declaration\n const importDecl = namedImports.parent.parent;\n edits.push({\n start: importDecl.getStart(sourceFile),\n end: importDecl.getEnd(),\n replacement: '',\n });\n } else {\n // Remove just this specifier with surrounding comma/whitespace\n const idx = elements.indexOf(specifier);\n let start: number;\n let end: number;\n if (idx < elements.length - 1) {\n start = specifier.getStart(sourceFile);\n end = elements[idx + 1].getStart(sourceFile);\n } else {\n start = elements[idx - 1].getEnd();\n end = specifier.getEnd();\n }\n edits.push({ start, end, replacement: '' });\n }\n}\n\nfunction warnRemovedProperties(sourceFile: ts.SourceFile, filePath: string, context: SchematicContext): void {\n const deprecated = ['isHome', 'isSeparator'];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && deprecated.includes(node.name.text)) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${filePath}:${line + 1} - \"${node.name.text}\" was part of ToolbarItem but does not exist on EuiMenuItem. Review manually.`,\n );\n }\n if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && deprecated.includes(node.name.text)) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${filePath}:${line + 1} - \"${node.name.text}\" was part of ToolbarItem but does not exist on EuiMenuItem. Review manually.`,\n );\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n}\n\nfunction visitNodes(nodes: TmplAstNode[], source: string, edits: Edit[], filePath: string, context: SchematicContext): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === OLD_TAG) {\n collectTagRenames(node, source, edits);\n collectOutputRemovals(node, source, edits, filePath, context);\n }\n visitNodes(node.children, source, edits, filePath, context);\n }\n }\n}\n\nfunction collectTagRenames(element: TmplAstElement, source: string, edits: Edit[]): void {\n // Rename opening tag\n const openStart = element.startSourceSpan.start.offset + 1; // skip '<'\n edits.push({ start: openStart, end: openStart + OLD_TAG.length, replacement: NEW_TAG });\n\n // Rename closing tag\n if (element.endSourceSpan) {\n const closeStart = element.endSourceSpan.start.offset + 2; // skip '</'\n edits.push({ start: closeStart, end: closeStart + OLD_TAG.length, replacement: NEW_TAG });\n }\n}\n\nfunction collectOutputRemovals(\n element: TmplAstElement,\n source: string,\n edits: Edit[],\n filePath: string,\n context: SchematicContext,\n): void {\n for (const output of element.outputs) {\n if (output.name === REMOVED_OUTPUT) {\n let start = output.sourceSpan.start.offset;\n // Remove leading whitespace\n while (start > 0 && (source[start - 1] === ' ' || source[start - 1] === '\\t')) {\n start--;\n }\n edits.push({ start, end: output.sourceSpan.end.offset, replacement: '' });\n\n const { line } = element.startSourceSpan.start;\n context.logger.warn(\n `${filePath}:${line + 1} - \"(menuItemClick)\" has been removed. There is no equivalent on eui-toolbar-mega-menu.`,\n );\n }\n }\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction applyEdits(source: string, edits: Edit[]): string {\n // Deduplicate edits at same position (e.g. module path edits when both Component and ToolbarItem are from same source)\n const unique = deduplicateEdits(edits);\n let result = source;\n for (const edit of unique.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n\nfunction deduplicateEdits(edits: Edit[]): Edit[] {\n const seen = new Map<string, Edit>();\n for (const edit of edits) {\n const key = `${edit.start}:${edit.end}`;\n // Last wins for same range\n seen.set(key, edit);\n }\n return Array.from(seen.values());\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n",
917
+ "sourceCode": "import { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\nimport { EUI_SIGNAL_MAP, EUI_SELECTOR_TO_CLASS } from './signal-map';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\ninterface Edit {\n start: number;\n end: number;\n replacement: string;\n}\n\n/**\n * Migrates consumer code that accesses EUI component instances programmatically.\n *\n * When EUI migrated @Input() → input(), @ViewChild() → viewChild(), and\n * @ContentChild() → contentChild(), properties that were plain class fields\n * became signal getters. Consumers who access them via @ViewChild need to\n * change `comp.prop` to `comp.prop()`.\n *\n * This schematic:\n * 1. Finds @ViewChild / @ContentChild fields typed as EUI component classes\n * 2. Resolves untyped @ViewChild('refName') by reading the component template\n * and matching #refName to an EUI element tag/selector\n * 3. Tracks which local variable names hold EUI component references (incl. aliases)\n * 4. Rewrites property accesses on those references: `ref.prop` → `ref.prop()`\n * (only for properties listed in the signal map)\n * 5. Does NOT touch template bindings — those are unaffected by signal migrations\n */\nexport function migrateToSignals(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let fileCount = 0;\n let propCount = 0;\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n if (!path.endsWith('.ts')) return;\n // Skip spec files — test code may use different patterns\n if (path.endsWith('.spec.ts')) return;\n\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n\n // Fast bail: file must mention at least one EUI class name or have\n // ViewChild/ContentChild with string refs (which might reference EUI elements)\n if (!hasEuiReference(original) && !hasStringRefQuery(original)) return;\n\n const result = migrateTypeScript(original, path, tree, context); if (result.source !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would migrate ${result.editCount} signal property access(es) in ${path}`);\n } else {\n tree.overwrite(path, result.source);\n }\n fileCount++;\n propCount += result.editCount;\n }\n });\n\n context.logger.info(`Migrated ${propCount} property access(es) to signal getters in ${fileCount} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\n/** Quick check: does the file reference any EUI class name from the signal map? */\nfunction hasEuiReference(source: string): boolean {\n for (const className of Object.keys(EUI_SIGNAL_MAP)) {\n if (source.includes(className)) return true;\n }\n return false;\n}\n\n/** Quick check: does the file have @ViewChild('...') or @ContentChild('...') with string refs? */\nfunction hasStringRefQuery(source: string): boolean {\n return /(@ViewChild|@ContentChild|@ViewChildren|@ContentChildren)\\s*\\(\\s*['\"]/.test(source);\n}\n\ninterface MigrateResult {\n source: string;\n editCount: number;\n}\n\nfunction migrateTypeScript(source: string, filePath: string, tree: Tree, context: SchematicContext): MigrateResult {\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: Edit[] = [];\n\n // Phase 0: Build template ref map (#refName → EUI class name) from the component's template\n const templateRefMap = buildTemplateRefMap(sourceFile, source, filePath, tree);\n\n // Phase 1: Collect all variables/properties typed as EUI classes\n const euiRefs = collectEuiReferences(sourceFile, source, templateRefMap);\n\n if (euiRefs.size === 0) {\n return { source, editCount: 0 };\n }\n\n // Phase 2: Find property accesses on those references and rewrite signal props\n const visitNode = (node: ts.Node): void => {\n // Match: ref.prop (PropertyAccessExpression)\n if (ts.isPropertyAccessExpression(node)) {\n const rewrite = tryRewritePropertyAccess(node, euiRefs, sourceFile, source);\n if (rewrite) {\n edits.push(rewrite);\n }\n }\n ts.forEachChild(node, visitNode);\n };\n\n ts.forEachChild(sourceFile, visitNode);\n\n if (edits.length === 0) {\n return { source, editCount: 0 };\n }\n\n return {\n source: applyEdits(source, edits),\n editCount: edits.length,\n };\n}\n\n/**\n * Scans the source file for class properties or variables typed as EUI component classes.\n * Returns a map: localName → Set of signal property names.\n *\n * Handles:\n * @ViewChild(EuiXxxComponent) name: EuiXxxComponent;\n * @ViewChild(EuiXxxComponent) name!: EuiXxxComponent;\n * @ViewChild('ref') name: EuiXxxComponent;\n * @ContentChild(EuiXxxComponent) name: EuiXxxComponent;\n * @ViewChild('ref') name: ElementRef; (resolved via template #ref → EUI selector)\n * someVar: EuiXxxComponent; (typed class field, even without decorator)\n * const alias = this.viewChildRef; (alias tracking)\n */\nfunction collectEuiReferences(\n sourceFile: ts.SourceFile,\n source: string,\n templateRefMap: Map<string, string>,\n): Map<string, ReadonlySet<string>> {\n const refs = new Map<string, ReadonlySet<string>>();\n\n // Pass 1: collect directly-typed references\n const visitDirect = (node: ts.Node): void => {\n // Class property declarations\n if (ts.isPropertyDeclaration(node) && node.name && ts.isIdentifier(node.name)) {\n const euiClass = resolveEuiClassName(node, sourceFile, templateRefMap);\n if (euiClass && EUI_SIGNAL_MAP[euiClass]) {\n refs.set(node.name.text, EUI_SIGNAL_MAP[euiClass]);\n }\n }\n\n // Variable declarations (const x: EuiXxxComponent = ...)\n if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) {\n const typeName = getTypeAnnotationName(node.type);\n if (typeName && EUI_SIGNAL_MAP[typeName]) {\n refs.set(node.name.text, EUI_SIGNAL_MAP[typeName]);\n }\n }\n\n // Parameter declarations (method(ref: EuiXxxComponent))\n if (ts.isParameter(node) && ts.isIdentifier(node.name)) {\n const typeName = getTypeAnnotationName(node.type);\n if (typeName && EUI_SIGNAL_MAP[typeName]) {\n refs.set(node.name.text, EUI_SIGNAL_MAP[typeName]);\n }\n }\n\n ts.forEachChild(node, visitDirect);\n };\n\n ts.forEachChild(sourceFile, visitDirect);\n\n // Pass 2: track aliases — variables assigned from already-tracked references\n // e.g. const alias = this.viewChildRef; or const alias = someParam;\n const visitAlias = (node: ts.Node): void => {\n if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) {\n // Skip if already tracked (has explicit type annotation)\n if (refs.has(node.name.text)) {\n ts.forEachChild(node, visitAlias);\n return;\n }\n\n const aliasedRef = resolveRefName(node.initializer);\n if (aliasedRef && refs.has(aliasedRef)) {\n refs.set(node.name.text, refs.get(aliasedRef)!);\n }\n }\n\n ts.forEachChild(node, visitAlias);\n };\n\n ts.forEachChild(sourceFile, visitAlias);\n return refs;\n}\n\n/**\n * Resolves the EUI class name from a property declaration.\n * Checks: type annotation → decorator class argument → template ref map.\n */\nfunction resolveEuiClassName(\n node: ts.PropertyDeclaration,\n sourceFile: ts.SourceFile,\n templateRefMap: Map<string, string>,\n): string | null {\n // Check type annotation first\n const typeName = getTypeAnnotationName(node.type);\n if (typeName && EUI_SIGNAL_MAP[typeName]) {\n return typeName;\n }\n\n // Check generic type argument: ElementRef<EuiXxxComponent>\n if (node.type && ts.isTypeReferenceNode(node.type) && node.type.typeArguments?.length) {\n const genericArg = getTypeAnnotationName(node.type.typeArguments[0]);\n if (genericArg && EUI_SIGNAL_MAP[genericArg]) {\n return genericArg;\n }\n }\n\n // Check decorator arguments: @ViewChild(EuiXxxComponent) or @ViewChild('refName')\n if (node.modifiers) {\n for (const modifier of node.modifiers) {\n if (!ts.isDecorator(modifier)) continue;\n if (!ts.isCallExpression(modifier.expression)) continue;\n\n const decoratorName = getDecoratorName(modifier.expression);\n if (!decoratorName) continue;\n if (!['ViewChild', 'ViewChildren', 'ContentChild', 'ContentChildren'].includes(decoratorName)) continue;\n\n const args = modifier.expression.arguments;\n if (args.length > 0) {\n const firstArg = args[0];\n\n // @ViewChild(EuiXxxComponent) — class reference\n if (ts.isIdentifier(firstArg) && EUI_SIGNAL_MAP[firstArg.text]) {\n return firstArg.text;\n }\n\n // @ViewChild('refName') — string template ref, resolve via template\n if (ts.isStringLiteralLike(firstArg)) {\n const refName = firstArg.text;\n const euiClass = templateRefMap.get(refName);\n if (euiClass && EUI_SIGNAL_MAP[euiClass]) {\n return euiClass;\n }\n }\n }\n }\n }\n\n return null;\n}\n\nfunction getDecoratorName(callExpr: ts.CallExpression): string | null {\n if (ts.isIdentifier(callExpr.expression)) {\n return callExpr.expression.text;\n }\n return null;\n}\n\nfunction getTypeAnnotationName(typeNode: ts.TypeNode | undefined): string | null {\n if (!typeNode) return null;\n if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {\n return typeNode.typeName.text;\n }\n return null;\n}\n\n/**\n * Checks if a property access `ref.prop` should be rewritten to `ref.prop()`.\n *\n * We rewrite if:\n * - `ref` is tracked in euiRefs\n * - `prop` is in the signal set for that EUI class\n * - The access is NOT already a call expression (not `ref.prop()` or `ref.prop(args)`)\n * - The access is NOT a write target (not `ref.prop = value`)\n * - The access is NOT a template binding argument (those are unaffected)\n */\nfunction tryRewritePropertyAccess(\n node: ts.PropertyAccessExpression,\n euiRefs: Map<string, ReadonlySet<string>>,\n sourceFile: ts.SourceFile,\n source: string,\n): Edit | null {\n const propName = node.name.text;\n\n // Resolve the reference name — handle `this.ref.prop` and `ref.prop`\n const refName = resolveRefName(node.expression);\n if (!refName) return null;\n\n const signalProps = euiRefs.get(refName);\n if (!signalProps || !signalProps.has(propName)) return null;\n\n // Skip if parent is already a call expression on this property: ref.prop(...)\n if (ts.isCallExpression(node.parent) && node.parent.expression === node) {\n return null;\n }\n\n // Skip if this is the left side of an assignment: ref.prop = value\n if (ts.isBinaryExpression(node.parent) &&\n node.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken &&\n node.parent.left === node) {\n return null;\n }\n\n // Skip if this is a compound assignment: ref.prop += value, ref.prop &&= value, etc.\n if (ts.isBinaryExpression(node.parent) && isAssignmentOperator(node.parent.operatorToken.kind) && node.parent.left === node) {\n return null;\n }\n\n // Skip if this is used with delete: delete ref.prop\n if (ts.isDeleteExpression(node.parent) && node.parent.expression === node) {\n return null;\n }\n\n // Skip if inside a typeof: typeof ref.prop\n if (ts.isTypeOfExpression(node.parent) && node.parent.expression === node) {\n return null;\n }\n\n // Skip property access chains that go deeper: ref.prop.something → ref.prop().something\n // This IS correct — we still want to add ()\n\n // Rewrite: insert () after the property name\n const end = node.name.getEnd();\n return {\n start: end,\n end: end,\n replacement: '()',\n };\n}\n\nfunction isAssignmentOperator(kind: ts.SyntaxKind): boolean {\n return kind >= ts.SyntaxKind.FirstAssignment && kind <= ts.SyntaxKind.LastAssignment;\n}\n\n/**\n * Resolves a reference name from an expression.\n * Handles: `ref`, `this.ref`, `this._ref`\n */\nfunction resolveRefName(expr: ts.Expression): string | null {\n // Direct identifier: ref.prop\n if (ts.isIdentifier(expr)) {\n return expr.text;\n }\n\n // this.ref.prop\n if (ts.isPropertyAccessExpression(expr) && expr.expression.kind === ts.SyntaxKind.ThisKeyword) {\n return expr.name.text;\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Template ref resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Builds a map of template reference variable names (#ref) to EUI class names.\n *\n * Parses the component's template (inline or external) and finds elements like:\n * <eui-alert #myAlert> → myAlert → EuiAlertComponent\n * <input euiAutocomplete #ac> → ac → EuiAutocompleteComponent\n *\n * Returns Map<refName, euiClassName>.\n */\nfunction buildTemplateRefMap(\n sourceFile: ts.SourceFile,\n source: string,\n filePath: string,\n tree: Tree,\n): Map<string, string> {\n const refMap = new Map<string, string>();\n\n // Find @Component decorator\n const componentMeta = findComponentMetadata(sourceFile, source);\n if (!componentMeta) return refMap;\n\n let templateSource: string | null = null;\n\n if (componentMeta.inlineTemplate !== null) {\n templateSource = componentMeta.inlineTemplate;\n } else if (componentMeta.templateUrl) {\n // Resolve templateUrl relative to the TS file\n const dir = filePath.substring(0, filePath.lastIndexOf('/'));\n const templatePath = `${dir}/${componentMeta.templateUrl}`;\n const buffer = tree.read(templatePath);\n if (buffer) {\n templateSource = buffer.toString('utf-8');\n }\n }\n\n if (!templateSource) return refMap;\n\n // Parse the template and collect #refs\n try {\n const parsed = parseTemplate(templateSource, '', { preserveWhitespaces: true });\n collectTemplateRefs(parsed.nodes, refMap);\n } catch {\n // Template parse failure — silently skip (might be malformed or use third-party syntax)\n }\n\n return refMap;\n}\n\n/**\n * Extracts @Component metadata: templateUrl or inline template content.\n */\nfunction findComponentMetadata(\n sourceFile: ts.SourceFile,\n source: string,\n): { templateUrl: string | null; inlineTemplate: string | null } | null {\n let result: { templateUrl: string | null; inlineTemplate: string | null } | null = null;\n\n const visit = (node: ts.Node): void => {\n if (result) return;\n\n if (ts.isDecorator(node) && ts.isCallExpression(node.expression)) {\n const name = getDecoratorName(node.expression);\n if (name !== 'Component') {\n ts.forEachChild(node, visit);\n return;\n }\n\n const args = node.expression.arguments;\n if (args.length === 0 || !ts.isObjectLiteralExpression(args[0])) {\n ts.forEachChild(node, visit);\n return;\n }\n\n const meta: { templateUrl: string | null; inlineTemplate: string | null } = {\n templateUrl: null,\n inlineTemplate: null,\n };\n\n for (const prop of args[0].properties) {\n if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) continue;\n\n if (prop.name.text === 'templateUrl') {\n const init = unwrapExpression(prop.initializer);\n if (ts.isStringLiteralLike(init)) {\n meta.templateUrl = init.text;\n }\n }\n\n if (prop.name.text === 'template') {\n const init = unwrapExpression(prop.initializer);\n if (ts.isStringLiteralLike(init)) {\n meta.inlineTemplate = init.text;\n } else if (ts.isNoSubstitutionTemplateLiteral(init)) {\n meta.inlineTemplate = init.text;\n }\n }\n }\n\n result = meta;\n return;\n }\n\n ts.forEachChild(node, visit);\n };\n\n ts.forEachChild(sourceFile, visit);\n return result;\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\n/**\n * Recursively walks template AST nodes, collecting #refName → EUI class name mappings.\n *\n * For each element with a template reference (#ref), we try to resolve its EUI class:\n * 1. Match element tag name against EUI_SELECTOR_TO_CLASS (e.g. 'eui-alert' → EuiAlertComponent)\n * 2. Match element attributes against attribute selectors (e.g. [euiAutocomplete] → EuiAutocompleteComponent)\n */\nfunction collectTemplateRefs(nodes: TmplAstNode[], refMap: Map<string, string>): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.references && node.references.length > 0) {\n const euiClass = resolveElementToEuiClass(node);\n if (euiClass) {\n for (const ref of node.references) {\n refMap.set(ref.name, euiClass);\n }\n }\n }\n collectTemplateRefs(node.children, refMap);\n }\n }\n}\n\n/**\n * Given a template element, resolves it to an EUI class name by checking:\n * 1. Tag name: <eui-alert> → look up 'eui-alert' in EUI_SELECTOR_TO_CLASS\n * 2. Attribute selectors: <input euiAutocomplete> → look up 'input[euiAutocomplete]' or '[euiAutocomplete]'\n */\nfunction resolveElementToEuiClass(element: TmplAstElement): string | null {\n const tag = element.name;\n\n // Direct tag match: eui-alert, eui-dialog, etc.\n if (EUI_SELECTOR_TO_CLASS[tag]) {\n return EUI_SELECTOR_TO_CLASS[tag];\n }\n\n // Attribute selector match: check all attributes against known patterns\n const allAttrNames = [\n ...element.attributes.map((a) => a.name),\n ...element.inputs.map((i) => i.name),\n ];\n\n for (const attrName of allAttrNames) {\n // Try tag[attr] pattern first (e.g. 'button[euiButton]', 'input[euiAutocomplete]')\n const tagAttrSelector = `${tag}[${attrName}]`;\n if (EUI_SELECTOR_TO_CLASS[tagAttrSelector]) {\n return EUI_SELECTOR_TO_CLASS[tagAttrSelector];\n }\n\n // Try [attr] pattern (e.g. '[euiTooltip]', '[euiLoading]')\n const attrSelector = `[${attrName}]`;\n if (EUI_SELECTOR_TO_CLASS[attrSelector]) {\n return EUI_SELECTOR_TO_CLASS[attrSelector];\n }\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Edit helpers\n// ---------------------------------------------------------------------------\n\nfunction applyEdits(source: string, edits: Edit[]): string {\n const unique = deduplicateEdits(edits);\n let result = source;\n for (const edit of unique.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n\nfunction deduplicateEdits(edits: Edit[]): Edit[] {\n const seen = new Map<string, Edit>();\n for (const edit of edits) {\n const key = `${edit.start}:${edit.end}`;\n seen.set(key, edit);\n }\n return Array.from(seen.values());\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n",
918
918
  "displayName": "Edit",
919
919
  "properties": [
920
920
  {
@@ -926,7 +926,7 @@
926
926
  "indexKey": "",
927
927
  "optional": false,
928
928
  "description": "",
929
- "line": 23,
929
+ "line": 14,
930
930
  "rawdescription": "\n"
931
931
  },
932
932
  {
@@ -938,7 +938,7 @@
938
938
  "indexKey": "",
939
939
  "optional": false,
940
940
  "description": "",
941
- "line": 24,
941
+ "line": 15,
942
942
  "rawdescription": "\n"
943
943
  },
944
944
  {
@@ -950,7 +950,7 @@
950
950
  "indexKey": "",
951
951
  "optional": false,
952
952
  "description": "",
953
- "line": 22,
953
+ "line": 13,
954
954
  "rawdescription": "\n"
955
955
  }
956
956
  ],
@@ -1027,12 +1027,12 @@
1027
1027
  },
1028
1028
  {
1029
1029
  "name": "Edit",
1030
- "id": "interface-Edit-cac2ac952d8245647e8a48b450e8f127a36bcffb394cf2cd81fe9c0810ed02c19e5e1629dbf29a68a5c6f675aed99570e9ac04ae470e13315e20c92cc0c6c453-4",
1031
- "file": "packages/core/schematics/migrate-to-signals/index.ts",
1030
+ "id": "interface-Edit-e1cd02924eb82a618c71a0b26c081bdd020519d2699aa0e2dc98640c4e0f347c3649b967c5fc87543e41bbbfabd1304835850ccc38f40684d6521c242b2030ed-4",
1031
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
1032
1032
  "deprecated": false,
1033
1033
  "deprecationMessage": "",
1034
1034
  "type": "interface",
1035
- "sourceCode": "import { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\nimport { EUI_SIGNAL_MAP, EUI_SELECTOR_TO_CLASS } from './signal-map';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\ninterface Edit {\n start: number;\n end: number;\n replacement: string;\n}\n\n/**\n * Migrates consumer code that accesses EUI component instances programmatically.\n *\n * When EUI migrated @Input() → input(), @ViewChild() → viewChild(), and\n * @ContentChild() → contentChild(), properties that were plain class fields\n * became signal getters. Consumers who access them via @ViewChild need to\n * change `comp.prop` to `comp.prop()`.\n *\n * This schematic:\n * 1. Finds @ViewChild / @ContentChild fields typed as EUI component classes\n * 2. Resolves untyped @ViewChild('refName') by reading the component template\n * and matching #refName to an EUI element tag/selector\n * 3. Tracks which local variable names hold EUI component references (incl. aliases)\n * 4. Rewrites property accesses on those references: `ref.prop` → `ref.prop()`\n * (only for properties listed in the signal map)\n * 5. Does NOT touch template bindings — those are unaffected by signal migrations\n */\nexport function migrateToSignals(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let fileCount = 0;\n let propCount = 0;\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n if (!path.endsWith('.ts')) return;\n // Skip spec files — test code may use different patterns\n if (path.endsWith('.spec.ts')) return;\n\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n\n // Fast bail: file must mention at least one EUI class name or have\n // ViewChild/ContentChild with string refs (which might reference EUI elements)\n if (!hasEuiReference(original) && !hasStringRefQuery(original)) return;\n\n const result = migrateTypeScript(original, path, tree, context); if (result.source !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would migrate ${result.editCount} signal property access(es) in ${path}`);\n } else {\n tree.overwrite(path, result.source);\n }\n fileCount++;\n propCount += result.editCount;\n }\n });\n\n context.logger.info(`Migrated ${propCount} property access(es) to signal getters in ${fileCount} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\n/** Quick check: does the file reference any EUI class name from the signal map? */\nfunction hasEuiReference(source: string): boolean {\n for (const className of Object.keys(EUI_SIGNAL_MAP)) {\n if (source.includes(className)) return true;\n }\n return false;\n}\n\n/** Quick check: does the file have @ViewChild('...') or @ContentChild('...') with string refs? */\nfunction hasStringRefQuery(source: string): boolean {\n return /(@ViewChild|@ContentChild|@ViewChildren|@ContentChildren)\\s*\\(\\s*['\"]/.test(source);\n}\n\ninterface MigrateResult {\n source: string;\n editCount: number;\n}\n\nfunction migrateTypeScript(source: string, filePath: string, tree: Tree, context: SchematicContext): MigrateResult {\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: Edit[] = [];\n\n // Phase 0: Build template ref map (#refName → EUI class name) from the component's template\n const templateRefMap = buildTemplateRefMap(sourceFile, source, filePath, tree);\n\n // Phase 1: Collect all variables/properties typed as EUI classes\n const euiRefs = collectEuiReferences(sourceFile, source, templateRefMap);\n\n if (euiRefs.size === 0) {\n return { source, editCount: 0 };\n }\n\n // Phase 2: Find property accesses on those references and rewrite signal props\n const visitNode = (node: ts.Node): void => {\n // Match: ref.prop (PropertyAccessExpression)\n if (ts.isPropertyAccessExpression(node)) {\n const rewrite = tryRewritePropertyAccess(node, euiRefs, sourceFile, source);\n if (rewrite) {\n edits.push(rewrite);\n }\n }\n ts.forEachChild(node, visitNode);\n };\n\n ts.forEachChild(sourceFile, visitNode);\n\n if (edits.length === 0) {\n return { source, editCount: 0 };\n }\n\n return {\n source: applyEdits(source, edits),\n editCount: edits.length,\n };\n}\n\n/**\n * Scans the source file for class properties or variables typed as EUI component classes.\n * Returns a map: localName → Set of signal property names.\n *\n * Handles:\n * @ViewChild(EuiXxxComponent) name: EuiXxxComponent;\n * @ViewChild(EuiXxxComponent) name!: EuiXxxComponent;\n * @ViewChild('ref') name: EuiXxxComponent;\n * @ContentChild(EuiXxxComponent) name: EuiXxxComponent;\n * @ViewChild('ref') name: ElementRef; (resolved via template #ref → EUI selector)\n * someVar: EuiXxxComponent; (typed class field, even without decorator)\n * const alias = this.viewChildRef; (alias tracking)\n */\nfunction collectEuiReferences(\n sourceFile: ts.SourceFile,\n source: string,\n templateRefMap: Map<string, string>,\n): Map<string, ReadonlySet<string>> {\n const refs = new Map<string, ReadonlySet<string>>();\n\n // Pass 1: collect directly-typed references\n const visitDirect = (node: ts.Node): void => {\n // Class property declarations\n if (ts.isPropertyDeclaration(node) && node.name && ts.isIdentifier(node.name)) {\n const euiClass = resolveEuiClassName(node, sourceFile, templateRefMap);\n if (euiClass && EUI_SIGNAL_MAP[euiClass]) {\n refs.set(node.name.text, EUI_SIGNAL_MAP[euiClass]);\n }\n }\n\n // Variable declarations (const x: EuiXxxComponent = ...)\n if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) {\n const typeName = getTypeAnnotationName(node.type);\n if (typeName && EUI_SIGNAL_MAP[typeName]) {\n refs.set(node.name.text, EUI_SIGNAL_MAP[typeName]);\n }\n }\n\n // Parameter declarations (method(ref: EuiXxxComponent))\n if (ts.isParameter(node) && ts.isIdentifier(node.name)) {\n const typeName = getTypeAnnotationName(node.type);\n if (typeName && EUI_SIGNAL_MAP[typeName]) {\n refs.set(node.name.text, EUI_SIGNAL_MAP[typeName]);\n }\n }\n\n ts.forEachChild(node, visitDirect);\n };\n\n ts.forEachChild(sourceFile, visitDirect);\n\n // Pass 2: track aliases — variables assigned from already-tracked references\n // e.g. const alias = this.viewChildRef; or const alias = someParam;\n const visitAlias = (node: ts.Node): void => {\n if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) {\n // Skip if already tracked (has explicit type annotation)\n if (refs.has(node.name.text)) {\n ts.forEachChild(node, visitAlias);\n return;\n }\n\n const aliasedRef = resolveRefName(node.initializer);\n if (aliasedRef && refs.has(aliasedRef)) {\n refs.set(node.name.text, refs.get(aliasedRef)!);\n }\n }\n\n ts.forEachChild(node, visitAlias);\n };\n\n ts.forEachChild(sourceFile, visitAlias);\n return refs;\n}\n\n/**\n * Resolves the EUI class name from a property declaration.\n * Checks: type annotation → decorator class argument → template ref map.\n */\nfunction resolveEuiClassName(\n node: ts.PropertyDeclaration,\n sourceFile: ts.SourceFile,\n templateRefMap: Map<string, string>,\n): string | null {\n // Check type annotation first\n const typeName = getTypeAnnotationName(node.type);\n if (typeName && EUI_SIGNAL_MAP[typeName]) {\n return typeName;\n }\n\n // Check generic type argument: ElementRef<EuiXxxComponent>\n if (node.type && ts.isTypeReferenceNode(node.type) && node.type.typeArguments?.length) {\n const genericArg = getTypeAnnotationName(node.type.typeArguments[0]);\n if (genericArg && EUI_SIGNAL_MAP[genericArg]) {\n return genericArg;\n }\n }\n\n // Check decorator arguments: @ViewChild(EuiXxxComponent) or @ViewChild('refName')\n if (node.modifiers) {\n for (const modifier of node.modifiers) {\n if (!ts.isDecorator(modifier)) continue;\n if (!ts.isCallExpression(modifier.expression)) continue;\n\n const decoratorName = getDecoratorName(modifier.expression);\n if (!decoratorName) continue;\n if (!['ViewChild', 'ViewChildren', 'ContentChild', 'ContentChildren'].includes(decoratorName)) continue;\n\n const args = modifier.expression.arguments;\n if (args.length > 0) {\n const firstArg = args[0];\n\n // @ViewChild(EuiXxxComponent) — class reference\n if (ts.isIdentifier(firstArg) && EUI_SIGNAL_MAP[firstArg.text]) {\n return firstArg.text;\n }\n\n // @ViewChild('refName') — string template ref, resolve via template\n if (ts.isStringLiteralLike(firstArg)) {\n const refName = firstArg.text;\n const euiClass = templateRefMap.get(refName);\n if (euiClass && EUI_SIGNAL_MAP[euiClass]) {\n return euiClass;\n }\n }\n }\n }\n }\n\n return null;\n}\n\nfunction getDecoratorName(callExpr: ts.CallExpression): string | null {\n if (ts.isIdentifier(callExpr.expression)) {\n return callExpr.expression.text;\n }\n return null;\n}\n\nfunction getTypeAnnotationName(typeNode: ts.TypeNode | undefined): string | null {\n if (!typeNode) return null;\n if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {\n return typeNode.typeName.text;\n }\n return null;\n}\n\n/**\n * Checks if a property access `ref.prop` should be rewritten to `ref.prop()`.\n *\n * We rewrite if:\n * - `ref` is tracked in euiRefs\n * - `prop` is in the signal set for that EUI class\n * - The access is NOT already a call expression (not `ref.prop()` or `ref.prop(args)`)\n * - The access is NOT a write target (not `ref.prop = value`)\n * - The access is NOT a template binding argument (those are unaffected)\n */\nfunction tryRewritePropertyAccess(\n node: ts.PropertyAccessExpression,\n euiRefs: Map<string, ReadonlySet<string>>,\n sourceFile: ts.SourceFile,\n source: string,\n): Edit | null {\n const propName = node.name.text;\n\n // Resolve the reference name — handle `this.ref.prop` and `ref.prop`\n const refName = resolveRefName(node.expression);\n if (!refName) return null;\n\n const signalProps = euiRefs.get(refName);\n if (!signalProps || !signalProps.has(propName)) return null;\n\n // Skip if parent is already a call expression on this property: ref.prop(...)\n if (ts.isCallExpression(node.parent) && node.parent.expression === node) {\n return null;\n }\n\n // Skip if this is the left side of an assignment: ref.prop = value\n if (ts.isBinaryExpression(node.parent) &&\n node.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken &&\n node.parent.left === node) {\n return null;\n }\n\n // Skip if this is a compound assignment: ref.prop += value, ref.prop &&= value, etc.\n if (ts.isBinaryExpression(node.parent) && isAssignmentOperator(node.parent.operatorToken.kind) && node.parent.left === node) {\n return null;\n }\n\n // Skip if this is used with delete: delete ref.prop\n if (ts.isDeleteExpression(node.parent) && node.parent.expression === node) {\n return null;\n }\n\n // Skip if inside a typeof: typeof ref.prop\n if (ts.isTypeOfExpression(node.parent) && node.parent.expression === node) {\n return null;\n }\n\n // Skip property access chains that go deeper: ref.prop.something → ref.prop().something\n // This IS correct — we still want to add ()\n\n // Rewrite: insert () after the property name\n const end = node.name.getEnd();\n return {\n start: end,\n end: end,\n replacement: '()',\n };\n}\n\nfunction isAssignmentOperator(kind: ts.SyntaxKind): boolean {\n return kind >= ts.SyntaxKind.FirstAssignment && kind <= ts.SyntaxKind.LastAssignment;\n}\n\n/**\n * Resolves a reference name from an expression.\n * Handles: `ref`, `this.ref`, `this._ref`\n */\nfunction resolveRefName(expr: ts.Expression): string | null {\n // Direct identifier: ref.prop\n if (ts.isIdentifier(expr)) {\n return expr.text;\n }\n\n // this.ref.prop\n if (ts.isPropertyAccessExpression(expr) && expr.expression.kind === ts.SyntaxKind.ThisKeyword) {\n return expr.name.text;\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Template ref resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Builds a map of template reference variable names (#ref) to EUI class names.\n *\n * Parses the component's template (inline or external) and finds elements like:\n * <eui-alert #myAlert> → myAlert → EuiAlertComponent\n * <input euiAutocomplete #ac> → ac → EuiAutocompleteComponent\n *\n * Returns Map<refName, euiClassName>.\n */\nfunction buildTemplateRefMap(\n sourceFile: ts.SourceFile,\n source: string,\n filePath: string,\n tree: Tree,\n): Map<string, string> {\n const refMap = new Map<string, string>();\n\n // Find @Component decorator\n const componentMeta = findComponentMetadata(sourceFile, source);\n if (!componentMeta) return refMap;\n\n let templateSource: string | null = null;\n\n if (componentMeta.inlineTemplate !== null) {\n templateSource = componentMeta.inlineTemplate;\n } else if (componentMeta.templateUrl) {\n // Resolve templateUrl relative to the TS file\n const dir = filePath.substring(0, filePath.lastIndexOf('/'));\n const templatePath = `${dir}/${componentMeta.templateUrl}`;\n const buffer = tree.read(templatePath);\n if (buffer) {\n templateSource = buffer.toString('utf-8');\n }\n }\n\n if (!templateSource) return refMap;\n\n // Parse the template and collect #refs\n try {\n const parsed = parseTemplate(templateSource, '', { preserveWhitespaces: true });\n collectTemplateRefs(parsed.nodes, refMap);\n } catch {\n // Template parse failure — silently skip (might be malformed or use third-party syntax)\n }\n\n return refMap;\n}\n\n/**\n * Extracts @Component metadata: templateUrl or inline template content.\n */\nfunction findComponentMetadata(\n sourceFile: ts.SourceFile,\n source: string,\n): { templateUrl: string | null; inlineTemplate: string | null } | null {\n let result: { templateUrl: string | null; inlineTemplate: string | null } | null = null;\n\n const visit = (node: ts.Node): void => {\n if (result) return;\n\n if (ts.isDecorator(node) && ts.isCallExpression(node.expression)) {\n const name = getDecoratorName(node.expression);\n if (name !== 'Component') {\n ts.forEachChild(node, visit);\n return;\n }\n\n const args = node.expression.arguments;\n if (args.length === 0 || !ts.isObjectLiteralExpression(args[0])) {\n ts.forEachChild(node, visit);\n return;\n }\n\n const meta: { templateUrl: string | null; inlineTemplate: string | null } = {\n templateUrl: null,\n inlineTemplate: null,\n };\n\n for (const prop of args[0].properties) {\n if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) continue;\n\n if (prop.name.text === 'templateUrl') {\n const init = unwrapExpression(prop.initializer);\n if (ts.isStringLiteralLike(init)) {\n meta.templateUrl = init.text;\n }\n }\n\n if (prop.name.text === 'template') {\n const init = unwrapExpression(prop.initializer);\n if (ts.isStringLiteralLike(init)) {\n meta.inlineTemplate = init.text;\n } else if (ts.isNoSubstitutionTemplateLiteral(init)) {\n meta.inlineTemplate = init.text;\n }\n }\n }\n\n result = meta;\n return;\n }\n\n ts.forEachChild(node, visit);\n };\n\n ts.forEachChild(sourceFile, visit);\n return result;\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\n/**\n * Recursively walks template AST nodes, collecting #refName → EUI class name mappings.\n *\n * For each element with a template reference (#ref), we try to resolve its EUI class:\n * 1. Match element tag name against EUI_SELECTOR_TO_CLASS (e.g. 'eui-alert' → EuiAlertComponent)\n * 2. Match element attributes against attribute selectors (e.g. [euiAutocomplete] → EuiAutocompleteComponent)\n */\nfunction collectTemplateRefs(nodes: TmplAstNode[], refMap: Map<string, string>): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.references && node.references.length > 0) {\n const euiClass = resolveElementToEuiClass(node);\n if (euiClass) {\n for (const ref of node.references) {\n refMap.set(ref.name, euiClass);\n }\n }\n }\n collectTemplateRefs(node.children, refMap);\n }\n }\n}\n\n/**\n * Given a template element, resolves it to an EUI class name by checking:\n * 1. Tag name: <eui-alert> → look up 'eui-alert' in EUI_SELECTOR_TO_CLASS\n * 2. Attribute selectors: <input euiAutocomplete> → look up 'input[euiAutocomplete]' or '[euiAutocomplete]'\n */\nfunction resolveElementToEuiClass(element: TmplAstElement): string | null {\n const tag = element.name;\n\n // Direct tag match: eui-alert, eui-dialog, etc.\n if (EUI_SELECTOR_TO_CLASS[tag]) {\n return EUI_SELECTOR_TO_CLASS[tag];\n }\n\n // Attribute selector match: check all attributes against known patterns\n const allAttrNames = [\n ...element.attributes.map((a) => a.name),\n ...element.inputs.map((i) => i.name),\n ];\n\n for (const attrName of allAttrNames) {\n // Try tag[attr] pattern first (e.g. 'button[euiButton]', 'input[euiAutocomplete]')\n const tagAttrSelector = `${tag}[${attrName}]`;\n if (EUI_SELECTOR_TO_CLASS[tagAttrSelector]) {\n return EUI_SELECTOR_TO_CLASS[tagAttrSelector];\n }\n\n // Try [attr] pattern (e.g. '[euiTooltip]', '[euiLoading]')\n const attrSelector = `[${attrName}]`;\n if (EUI_SELECTOR_TO_CLASS[attrSelector]) {\n return EUI_SELECTOR_TO_CLASS[attrSelector];\n }\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Edit helpers\n// ---------------------------------------------------------------------------\n\nfunction applyEdits(source: string, edits: Edit[]): string {\n const unique = deduplicateEdits(edits);\n let result = source;\n for (const edit of unique.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n\nfunction deduplicateEdits(edits: Edit[]): Edit[] {\n const seen = new Map<string, Edit>();\n for (const edit of edits) {\n const key = `${edit.start}:${edit.end}`;\n seen.set(key, edit);\n }\n return Array.from(seen.values());\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n",
1035
+ "sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\nconst OLD_TAG = 'eui-toolbar-menu';\nconst NEW_TAG = 'eui-toolbar-mega-menu';\nconst OLD_COMPONENT = 'EuiToolbarMenuComponent';\nconst NEW_COMPONENT = 'EuiToolbarMegaMenuComponent';\nconst OLD_INTERFACE = 'ToolbarItem';\nconst NEW_INTERFACE = 'EuiMenuItem';\nconst NEW_COMPONENT_PATH = '@eui/components/layout';\nconst NEW_INTERFACE_PATH = '@eui/core';\nconst REMOVED_OUTPUT = 'menuItemClick';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\ninterface Edit {\n start: number;\n end: number;\n replacement: string;\n}\n\nexport function migrateEuiToolbarMenu(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let fileCount = 0;\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(OLD_TAG) && !original.includes(OLD_COMPONENT) && !original.includes(OLD_INTERFACE)) return;\n\n let result: string;\n\n if (path.endsWith('.html')) {\n result = migrateTemplate(original, path, context);\n } else {\n result = migrateTypeScript(original, path, context);\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would migrate eui-toolbar-menu → eui-toolbar-mega-menu in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n fileCount++;\n }\n });\n\n context.logger.info(`Migrated eui-toolbar-menu → eui-toolbar-mega-menu in ${fileCount} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction migrateTemplate(source: string, filePath: string, context: SchematicContext): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: Edit[] = [];\n\n visitNodes(parsed.nodes, source, edits, filePath, context);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateTypeScript(source: string, filePath: string, context: SchematicContext): string {\n let result = migrateInlineTemplates(source, filePath, context);\n result = migrateImportsAndTypes(result, filePath, context);\n return result;\n}\n\nfunction migrateInlineTemplates(source: string, filePath: string, context: SchematicContext): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: Edit[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(OLD_TAG)) {\n ts.forEachChild(node, visit); return; \n}\n const migrated = migrateTemplate(rawTemplate, filePath, context);\n if (migrated !== rawTemplate) changes.push({ start, end, replacement: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n return applyEdits(source, changes);\n}\n\nfunction migrateImportsAndTypes(source: string, filePath: string, context: SchematicContext): string {\n if (!source.includes(OLD_COMPONENT) && !source.includes(OLD_INTERFACE)) return source;\n\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: Edit[] = [];\n\n // Track if EuiMenuItem is already imported from @eui/core\n let hasEuiMenuItemImport = false;\n\n // First pass: analyze imports\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const moduleSpec = (stmt.moduleSpecifier as ts.StringLiteral).text;\n const namedBindings = stmt.importClause?.namedBindings;\n if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;\n\n for (const specifier of namedBindings.elements) {\n if (specifier.name.text === NEW_INTERFACE && moduleSpec === NEW_INTERFACE_PATH) {\n hasEuiMenuItemImport = true;\n }\n }\n }\n\n // Second pass: collect edits for import declarations\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const namedBindings = stmt.importClause?.namedBindings;\n if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;\n\n const moduleSpec = stmt.moduleSpecifier as ts.StringLiteral;\n const specifiers = namedBindings.elements;\n const hasComponent = specifiers.some((s) => s.name.text === OLD_COMPONENT);\n const hasInterface = specifiers.some((s) => s.name.text === OLD_INTERFACE);\n\n if (hasComponent && hasInterface) {\n // Both are in the same import → must split into two different paths\n const others = specifiers.filter((s) => s.name.text !== OLD_COMPONENT && s.name.text !== OLD_INTERFACE);\n const lines: string[] = [];\n lines.push(`import { ${NEW_COMPONENT} } from '${NEW_COMPONENT_PATH}';`);\n if (!hasEuiMenuItemImport) {\n lines.push(`import { ${NEW_INTERFACE} } from '${NEW_INTERFACE_PATH}';`);\n }\n if (others.length > 0) {\n const otherNames = others.map((s) => s.name.text).join(', ');\n lines.push(`import { ${otherNames} } from '${moduleSpec.text}';`);\n }\n edits.push({\n start: stmt.getStart(sourceFile),\n end: stmt.getEnd(),\n replacement: lines.join('\\n'),\n });\n } else if (hasComponent) {\n edits.push({\n start: moduleSpec.getStart(sourceFile) + 1,\n end: moduleSpec.getEnd() - 1,\n replacement: NEW_COMPONENT_PATH,\n });\n for (const specifier of specifiers) {\n if (specifier.name.text === OLD_COMPONENT) {\n edits.push({\n start: specifier.name.getStart(sourceFile),\n end: specifier.name.getEnd(),\n replacement: NEW_COMPONENT,\n });\n }\n }\n } else if (hasInterface) {\n if (hasEuiMenuItemImport) {\n removeImportSpecifier(namedBindings, specifiers.find((s) => s.name.text === OLD_INTERFACE)!, sourceFile, edits);\n } else {\n edits.push({\n start: moduleSpec.getStart(sourceFile) + 1,\n end: moduleSpec.getEnd() - 1,\n replacement: NEW_INTERFACE_PATH,\n });\n for (const specifier of specifiers) {\n if (specifier.name.text === OLD_INTERFACE) {\n edits.push({\n start: specifier.name.getStart(sourceFile),\n end: specifier.name.getEnd(),\n replacement: NEW_INTERFACE,\n });\n }\n }\n }\n }\n }\n\n // Third pass: rename identifier references in non-import positions\n const visitRefs = (node: ts.Node): void => {\n if (ts.isImportDeclaration(node)) return; // skip imports (already handled)\n if (ts.isIdentifier(node)) {\n if (node.text === OLD_COMPONENT) {\n edits.push({ start: node.getStart(sourceFile), end: node.getEnd(), replacement: NEW_COMPONENT });\n }\n if (node.text === OLD_INTERFACE) {\n edits.push({ start: node.getStart(sourceFile), end: node.getEnd(), replacement: NEW_INTERFACE });\n }\n }\n ts.forEachChild(node, visitRefs);\n };\n\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) {\n visitRefs(stmt);\n }\n }\n\n // Warn about ToolbarItem-specific properties\n warnRemovedProperties(sourceFile, filePath, context);\n\n return applyEdits(source, edits);\n}\n\nfunction removeImportSpecifier(\n namedImports: ts.NamedImports,\n specifier: ts.ImportSpecifier,\n sourceFile: ts.SourceFile,\n edits: Edit[],\n): void {\n const elements = namedImports.elements;\n if (elements.length === 1) {\n // Remove the entire import declaration\n const importDecl = namedImports.parent.parent;\n edits.push({\n start: importDecl.getStart(sourceFile),\n end: importDecl.getEnd(),\n replacement: '',\n });\n } else {\n // Remove just this specifier with surrounding comma/whitespace\n const idx = elements.indexOf(specifier);\n let start: number;\n let end: number;\n if (idx < elements.length - 1) {\n start = specifier.getStart(sourceFile);\n end = elements[idx + 1].getStart(sourceFile);\n } else {\n start = elements[idx - 1].getEnd();\n end = specifier.getEnd();\n }\n edits.push({ start, end, replacement: '' });\n }\n}\n\nfunction warnRemovedProperties(sourceFile: ts.SourceFile, filePath: string, context: SchematicContext): void {\n const deprecated = ['isHome', 'isSeparator'];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && deprecated.includes(node.name.text)) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${filePath}:${line + 1} - \"${node.name.text}\" was part of ToolbarItem but does not exist on EuiMenuItem. Review manually.`,\n );\n }\n if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && deprecated.includes(node.name.text)) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${filePath}:${line + 1} - \"${node.name.text}\" was part of ToolbarItem but does not exist on EuiMenuItem. Review manually.`,\n );\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n}\n\nfunction visitNodes(nodes: TmplAstNode[], source: string, edits: Edit[], filePath: string, context: SchematicContext): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === OLD_TAG) {\n collectTagRenames(node, source, edits);\n collectOutputRemovals(node, source, edits, filePath, context);\n }\n visitNodes(node.children, source, edits, filePath, context);\n }\n }\n}\n\nfunction collectTagRenames(element: TmplAstElement, source: string, edits: Edit[]): void {\n // Rename opening tag\n const openStart = element.startSourceSpan.start.offset + 1; // skip '<'\n edits.push({ start: openStart, end: openStart + OLD_TAG.length, replacement: NEW_TAG });\n\n // Rename closing tag\n if (element.endSourceSpan) {\n const closeStart = element.endSourceSpan.start.offset + 2; // skip '</'\n edits.push({ start: closeStart, end: closeStart + OLD_TAG.length, replacement: NEW_TAG });\n }\n}\n\nfunction collectOutputRemovals(\n element: TmplAstElement,\n source: string,\n edits: Edit[],\n filePath: string,\n context: SchematicContext,\n): void {\n for (const output of element.outputs) {\n if (output.name === REMOVED_OUTPUT) {\n let start = output.sourceSpan.start.offset;\n // Remove leading whitespace\n while (start > 0 && (source[start - 1] === ' ' || source[start - 1] === '\\t')) {\n start--;\n }\n edits.push({ start, end: output.sourceSpan.end.offset, replacement: '' });\n\n const { line } = element.startSourceSpan.start;\n context.logger.warn(\n `${filePath}:${line + 1} - \"(menuItemClick)\" has been removed. There is no equivalent on eui-toolbar-mega-menu.`,\n );\n }\n }\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction applyEdits(source: string, edits: Edit[]): string {\n // Deduplicate edits at same position (e.g. module path edits when both Component and ToolbarItem are from same source)\n const unique = deduplicateEdits(edits);\n let result = source;\n for (const edit of unique.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n\nfunction deduplicateEdits(edits: Edit[]): Edit[] {\n const seen = new Map<string, Edit>();\n for (const edit of edits) {\n const key = `${edit.start}:${edit.end}`;\n // Last wins for same range\n seen.set(key, edit);\n }\n return Array.from(seen.values());\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n",
1036
1036
  "displayName": "Edit",
1037
1037
  "properties": [
1038
1038
  {
@@ -1044,7 +1044,7 @@
1044
1044
  "indexKey": "",
1045
1045
  "optional": false,
1046
1046
  "description": "",
1047
- "line": 14,
1047
+ "line": 23,
1048
1048
  "rawdescription": "\n"
1049
1049
  },
1050
1050
  {
@@ -1056,7 +1056,7 @@
1056
1056
  "indexKey": "",
1057
1057
  "optional": false,
1058
1058
  "description": "",
1059
- "line": 15,
1059
+ "line": 24,
1060
1060
  "rawdescription": "\n"
1061
1061
  },
1062
1062
  {
@@ -1068,7 +1068,7 @@
1068
1068
  "indexKey": "",
1069
1069
  "optional": false,
1070
1070
  "description": "",
1071
- "line": 13,
1071
+ "line": 22,
1072
1072
  "rawdescription": "\n"
1073
1073
  }
1074
1074
  ],
@@ -2139,63 +2139,7 @@
2139
2139
  },
2140
2140
  {
2141
2141
  "name": "Schema",
2142
- "id": "interface-Schema-9c5e016857e1416ac7bbb881973e644c0f578a53bd432bb951c1676ac3f2a6631bfe3344860346a081b841217278723e0bb0bf2fa35fb869de67cb0cc8d99849",
2143
- "file": "packages/core/schematics/add-eui-imports/index.ts",
2144
- "deprecated": false,
2145
- "deprecationMessage": "",
2146
- "type": "interface",
2147
- "sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode, TmplAstTemplate } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\nimport { SELECTOR_MAP, SelectorEntry, getClassNamesForArray } from './selector-map';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n useClassArray?: boolean;\n}\n\ninterface ImportToAdd {\n /** The symbol to add to imports array (class name or array name for spread) */\n symbol: string;\n /** Whether this should be spread (...EUI_BUTTON) */\n isSpread: boolean;\n /** ES import path */\n importPath: string;\n}\n\nexport function addEuiImports(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n const useClassArray = options.useClassArray ?? false;\n let filesUpdated = 0;\n\n // Index NgModules for standalone:false support\n const ngModuleIndex = buildNgModuleIndex(tree, tree.getDir(scanPath || '/'));\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n if (!path.endsWith('.ts') || path.endsWith('.spec.ts')) return;\n\n const buffer = tree.read(path);\n if (!buffer) return;\n const source = buffer.toString('utf-8');\n\n const sourceFile = ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true);\n const components = findComponentDecorators(sourceFile);\n if (components.length === 0) return;\n\n let modified = false;\n\n for (const { decorator, className: componentClassName, isNonStandalone } of components) {\n const templateHtml = getTemplateContent(tree, path, decorator, source);\n if (!templateHtml) continue;\n\n const matched = matchSelectorsInTemplate(templateHtml);\n if (matched.length === 0) continue;\n\n const importsToAdd = resolveImports(matched, useClassArray);\n if (importsToAdd.length === 0) continue;\n\n if (isNonStandalone) {\n // Find the NgModule that declares this component and add imports there\n const moduleInfo = findDeclaringModule(ngModuleIndex, componentClassName);\n if (!moduleInfo) {\n context.logger.warn(`⚠ Could not find declaring NgModule for ${componentClassName} in ${path}`);\n continue;\n }\n const moduleBuffer = tree.read(moduleInfo.path);\n if (!moduleBuffer) continue;\n const moduleSource = moduleBuffer.toString('utf-8');\n const result = addImportsToFile(moduleSource, moduleInfo.path, moduleInfo.decoratorPos, importsToAdd, useClassArray);\n if (result !== moduleSource) {\n if (options.dryRun) {\n logDryRun(context, `Would add EUI imports to NgModule in ${moduleInfo.path} for component ${componentClassName}`);\n } else {\n tree.overwrite(moduleInfo.path, result);\n }\n modified = true;\n }\n } else {\n // Standalone component — add imports directly\n const currentSource = tree.read(path)!.toString('utf-8');\n const result = addImportsToFile(currentSource, path, decorator.getStart(), importsToAdd, useClassArray);\n if (result !== currentSource) {\n if (options.dryRun) {\n logDryRun(context, `Would add EUI imports to ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n modified = true;\n }\n }\n }\n\n if (modified) filesUpdated++;\n });\n\n context.logger.info(`add-eui-imports: ${filesUpdated} file(s) updated.`);\n if (options.dryRun) logDryRunNote(context);\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\n// --- Selector Matching ---\n\nfunction matchSelectorsInTemplate(html: string): SelectorEntry[] {\n const parsed = parseTemplate(html, '', { preserveWhitespaces: true });\n if (parsed.errors?.length) return [];\n\n const matched: SelectorEntry[] = [];\n visitTemplateNodes(parsed.nodes, matched);\n return matched;\n}\n\nfunction visitTemplateNodes(nodes: TmplAstNode[], matched: SelectorEntry[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n matchElement(node, matched);\n visitTemplateNodes(node.children, matched);\n } else if (node instanceof TmplAstTemplate) {\n visitTemplateNodes(node.children, matched);\n }\n }\n}\n\nfunction matchElement(element: TmplAstElement, matched: SelectorEntry[]): void {\n const tagName = element.name;\n const attrNames = new Set([\n ...element.attributes.map(a => a.name),\n ...element.inputs.map(i => i.name),\n ]);\n\n for (const entry of SELECTOR_MAP) {\n if (entry.element && entry.element !== tagName) continue;\n if (!entry.element && entry.attributes.length === 0) continue;\n if (!entry.attributes.every(attr => attrNames.has(attr))) continue;\n // If no element specified, at least one attribute must match on this element\n if (!entry.element && entry.attributes.length > 0 && !entry.attributes.some(attr => attrNames.has(attr))) continue;\n matched.push(entry);\n }\n}\n\n// --- Import Resolution ---\n\nfunction resolveImports(matched: SelectorEntry[], useClassArray: boolean): ImportToAdd[] {\n const seen = new Set<string>();\n const result: ImportToAdd[] = [];\n\n for (const entry of matched) {\n if (useClassArray && entry.classArray) {\n if (seen.has(entry.classArray)) continue;\n seen.add(entry.classArray);\n result.push({ symbol: entry.classArray, isSpread: true, importPath: entry.importPath });\n } else {\n if (seen.has(entry.className)) continue;\n seen.add(entry.className);\n result.push({ symbol: entry.className, isSpread: false, importPath: entry.importPath });\n }\n }\n\n return result;\n}\n\n// --- Template Extraction ---\n\nfunction getTemplateContent(tree: Tree, tsPath: string, decorator: ts.Decorator, source: string): string | null {\n const call = decorator.expression as ts.CallExpression;\n if (!call.arguments[0] || !ts.isObjectLiteralExpression(call.arguments[0])) return null;\n const metadata = call.arguments[0];\n\n for (const prop of metadata.properties) {\n if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) continue;\n if (prop.name.text === 'template') {\n const init = prop.initializer;\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n return init.text;\n }\n }\n if (prop.name.text === 'templateUrl') {\n if (ts.isStringLiteral(prop.initializer)) {\n const dir = tsPath.substring(0, tsPath.lastIndexOf('/'));\n const templateBuffer = tree.read(`${dir}/${prop.initializer.text}`);\n if (templateBuffer) return templateBuffer.toString('utf-8');\n }\n }\n }\n return null;\n}\n\n// --- Component Decorator Detection ---\n\ninterface ComponentInfo {\n decorator: ts.Decorator;\n className: string;\n isNonStandalone: boolean;\n}\n\nfunction findComponentDecorators(sourceFile: ts.SourceFile): ComponentInfo[] {\n const results: ComponentInfo[] = [];\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node) && node.name) {\n const decs = ts.getDecorators(node);\n if (decs) {\n for (const dec of decs) {\n if (ts.isCallExpression(dec.expression) && ts.isIdentifier(dec.expression.expression) && dec.expression.expression.text === 'Component') {\n const isNonStandalone = hasStandaloneFalse(dec);\n results.push({ decorator: dec, className: node.name.text, isNonStandalone });\n }\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n return results;\n}\n\nfunction hasStandaloneFalse(decorator: ts.Decorator): boolean {\n const call = decorator.expression as ts.CallExpression;\n if (!call.arguments[0] || !ts.isObjectLiteralExpression(call.arguments[0])) return false;\n for (const prop of call.arguments[0].properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'standalone') {\n return prop.initializer.kind === ts.SyntaxKind.FalseKeyword;\n }\n }\n return false;\n}\n\n// --- NgModule Index ---\n\ninterface NgModuleInfo {\n path: string;\n declarations: string[];\n decoratorPos: number;\n}\n\nfunction buildNgModuleIndex(tree: Tree, dir: DirEntry): NgModuleInfo[] {\n const modules: NgModuleInfo[] = [];\n\n visitDir(dir, (path) => {\n if (!path.endsWith('.ts') || path.endsWith('.spec.ts')) return;\n\n const buffer = tree.read(path);\n if (!buffer) return;\n const source = buffer.toString('utf-8');\n if (!source.includes('NgModule')) return;\n\n const sf = ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true);\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node)) {\n const decs = ts.getDecorators(node);\n if (decs) {\n for (const dec of decs) {\n if (ts.isCallExpression(dec.expression) && ts.isIdentifier(dec.expression.expression) && dec.expression.expression.text === 'NgModule') {\n const declarations = extractArrayProperty(dec, 'declarations', source);\n modules.push({ path, declarations, decoratorPos: dec.getStart() });\n }\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sf);\n });\n\n return modules;\n}\n\nfunction extractArrayProperty(decorator: ts.Decorator, propName: string, source: string): string[] {\n const call = decorator.expression as ts.CallExpression;\n if (!call.arguments[0] || !ts.isObjectLiteralExpression(call.arguments[0])) return [];\n for (const prop of call.arguments[0].properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === propName) {\n if (ts.isArrayLiteralExpression(prop.initializer)) {\n return prop.initializer.elements\n .filter(ts.isIdentifier)\n .map(id => id.text);\n }\n }\n }\n return [];\n}\n\nfunction findDeclaringModule(modules: NgModuleInfo[], componentClassName: string): NgModuleInfo | undefined {\n return modules.find(m => m.declarations.includes(componentClassName));\n}\n\n// --- Import Addition ---\n\nfunction addImportsToFile(source: string, filePath: string, decoratorStartHint: number, imports: ImportToAdd[], useClassArray: boolean): string {\n const sf = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);\n\n // Find the imports array in the decorator closest to decoratorStartHint\n const importsArrayInfo = findDecoratorImportsArray(sf, source, decoratorStartHint);\n if (!importsArrayInfo) return source;\n\n const { arrayNode, decoratorType } = importsArrayInfo;\n\n // Determine what's already in the imports array\n const existingSymbols = new Set<string>();\n const existingSpreads = new Set<string>();\n for (const el of arrayNode.elements) {\n if (ts.isSpreadElement(el) && ts.isIdentifier(el.expression)) {\n existingSpreads.add(el.expression.text);\n } else if (ts.isIdentifier(el)) {\n existingSymbols.add(el.text);\n }\n }\n\n // Filter out already-present imports and compute what to add/remove\n const toAdd: ImportToAdd[] = [];\n const toRemoveFromArray: string[] = []; // individual class names to consolidate\n\n for (const imp of imports) {\n if (imp.isSpread) {\n if (existingSpreads.has(imp.symbol)) continue; // Already has ...EUI_X\n toAdd.push(imp);\n // Consolidate: remove individual class names covered by this array\n if (useClassArray) {\n const coveredClasses = getClassNamesForArray(imp.symbol);\n for (const cls of coveredClasses) {\n if (existingSymbols.has(cls)) toRemoveFromArray.push(cls);\n }\n }\n } else {\n if (existingSymbols.has(imp.symbol)) continue;\n // Also skip if a spread already covers this class\n const coveringArray = imports.find(i => i.isSpread && getClassNamesForArray(i.symbol).includes(imp.symbol));\n if (coveringArray && (existingSpreads.has(coveringArray.symbol) || toAdd.some(a => a.symbol === coveringArray.symbol))) continue;\n toAdd.push(imp);\n }\n }\n\n if (toAdd.length === 0 && toRemoveFromArray.length === 0) return source;\n\n // Build new array content\n let result = source;\n result = updateDecoratorImportsArray(result, filePath, arrayNode, toAdd, toRemoveFromArray);\n\n // Add ES imports\n result = addEsImports(result, filePath, toAdd);\n\n // Remove consolidated class names from ES imports\n if (toRemoveFromArray.length > 0) {\n result = removeFromEsImports(result, filePath, toRemoveFromArray);\n }\n\n return result;\n}\n\ninterface ImportsArrayInfo {\n arrayNode: ts.ArrayLiteralExpression;\n decoratorType: 'Component' | 'NgModule';\n}\n\nfunction findDecoratorImportsArray(sf: ts.SourceFile, source: string, decoratorStartHint: number): ImportsArrayInfo | null {\n let found: ImportsArrayInfo | null = null;\n\n const visit = (node: ts.Node): void => {\n if (found) return;\n if (ts.isClassDeclaration(node)) {\n const decs = ts.getDecorators(node);\n if (!decs) return;\n for (const dec of decs) {\n if (!ts.isCallExpression(dec.expression)) continue;\n if (!ts.isIdentifier(dec.expression.expression)) continue;\n const decName = dec.expression.expression.text;\n if (decName !== 'Component' && decName !== 'NgModule') continue;\n if (Math.abs(dec.getStart() - decoratorStartHint) > 5) continue; // Match by position\n\n const metadata = dec.expression.arguments[0];\n if (!ts.isObjectLiteralExpression(metadata)) continue;\n\n for (const prop of metadata.properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'imports') {\n if (ts.isArrayLiteralExpression(prop.initializer)) {\n found = { arrayNode: prop.initializer, decoratorType: decName as 'Component' | 'NgModule' };\n return;\n }\n }\n }\n\n // No imports array found — create one\n if (!found && decName === 'Component') {\n // We need to add `imports: []` to the decorator\n // Insert after the last property\n const lastProp = metadata.properties[metadata.properties.length - 1];\n if (lastProp) {\n const insertPos = lastProp.getEnd();\n const indent = detectIndent(source, metadata.getStart());\n const insertion = `,\\n${indent} imports: []`;\n const newSource = source.slice(0, insertPos) + insertion + source.slice(insertPos);\n // Re-parse to get the array node\n const newSf = ts.createSourceFile('', newSource, ts.ScriptTarget.Latest, true);\n const newArray = findImportsArrayInSource(newSf);\n if (newArray) {\n // We can't return a node from a different source file in the general case.\n // Instead, we'll handle the \"no imports array\" case by adding it inline.\n found = null; // Will be handled separately\n }\n }\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sf);\n return found;\n}\n\nfunction findImportsArrayInSource(sf: ts.SourceFile): ts.ArrayLiteralExpression | null {\n let found: ts.ArrayLiteralExpression | null = null;\n const visit = (node: ts.Node): void => {\n if (found) return;\n if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && node.name.text === 'imports' && ts.isArrayLiteralExpression(node.initializer)) {\n found = node.initializer;\n }\n ts.forEachChild(node, visit);\n };\n visit(sf);\n return found;\n}\n\nfunction updateDecoratorImportsArray(source: string, filePath: string, arrayNode: ts.ArrayLiteralExpression, toAdd: ImportToAdd[], toRemove: string[]): string {\n const sf = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);\n\n // Rebuild the array content\n const existingElements: string[] = [];\n for (const el of arrayNode.elements) {\n const text = source.slice(el.getStart(sf), el.getEnd()).trim();\n // Check if this element should be removed (consolidation)\n if (ts.isIdentifier(el) && toRemove.includes(el.text)) continue;\n existingElements.push(text);\n }\n\n // Add new entries\n for (const imp of toAdd) {\n const entry = imp.isSpread ? `...${imp.symbol}` : imp.symbol;\n if (!existingElements.includes(entry)) {\n existingElements.push(entry);\n }\n }\n\n // Determine formatting\n const arrayStart = arrayNode.getStart(sf);\n const arrayEnd = arrayNode.getEnd();\n const originalText = source.slice(arrayStart, arrayEnd);\n const isMultiline = originalText.includes('\\n');\n\n let newArrayText: string;\n if (isMultiline || existingElements.length > 3) {\n const indent = detectIndent(source, arrayStart);\n const itemIndent = indent + ' ';\n newArrayText = `[\\n${existingElements.map(e => `${itemIndent}${e},`).join('\\n')}\\n${indent}]`;\n } else {\n newArrayText = `[${existingElements.join(', ')}]`;\n }\n\n return source.slice(0, arrayStart) + newArrayText + source.slice(arrayEnd);\n}\n\nfunction addEsImports(source: string, filePath: string, imports: ImportToAdd[]): string {\n let result = source;\n\n // Group by import path\n const byPath = new Map<string, string[]>();\n for (const imp of imports) {\n const existing = byPath.get(imp.importPath) || [];\n existing.push(imp.symbol);\n byPath.set(imp.importPath, existing);\n }\n\n for (const [importPath, symbols] of byPath) {\n const sf = ts.createSourceFile(filePath, result, ts.ScriptTarget.Latest, true);\n\n // Check if there's already an import from this path\n const existingImport = sf.statements.find(\n (s): s is ts.ImportDeclaration =>\n ts.isImportDeclaration(s) && ts.isStringLiteral(s.moduleSpecifier) && s.moduleSpecifier.text === importPath,\n );\n\n if (existingImport?.importClause?.namedBindings && ts.isNamedImports(existingImport.importClause.namedBindings)) {\n // Extend existing import\n const namedBindings = existingImport.importClause.namedBindings;\n const existingNames = namedBindings.elements.map(el => el.name.text);\n const newNames = symbols.filter(s => !existingNames.includes(s));\n if (newNames.length === 0) continue;\n\n const allNames = [...existingNames, ...newNames].sort();\n const newClause = `{ ${allNames.join(', ')} }`;\n result = result.slice(0, namedBindings.getStart(sf)) + newClause + result.slice(namedBindings.getEnd());\n } else {\n // Add new import statement\n const sortedSymbols = [...symbols].sort();\n const newImport = `import { ${sortedSymbols.join(', ')} } from '${importPath}';\\n`;\n\n // Insert after the last existing import\n const lastImport = [...sf.statements].reverse().find(ts.isImportDeclaration);\n if (lastImport) {\n const pos = lastImport.getEnd();\n result = result.slice(0, pos) + '\\n' + newImport.trimEnd() + result.slice(pos);\n } else {\n result = newImport + result;\n }\n }\n }\n\n return result;\n}\n\nfunction detectIndent(source: string, pos: number): string {\n const lineStart = source.lastIndexOf('\\n', pos - 1) + 1;\n const match = source.slice(lineStart, pos).match(/^(\\s*)/);\n return match ? match[1] : '';\n}\n\nfunction removeFromEsImports(source: string, filePath: string, symbolsToRemove: string[]): string {\n let result = source;\n const sf = ts.createSourceFile(filePath, result, ts.ScriptTarget.Latest, true);\n\n for (const stmt of sf.statements) {\n if (!ts.isImportDeclaration(stmt) || !stmt.importClause?.namedBindings || !ts.isNamedImports(stmt.importClause.namedBindings)) continue;\n const namedBindings = stmt.importClause.namedBindings;\n const existingNames = namedBindings.elements.map(el => el.name.text);\n const remaining = existingNames.filter(n => !symbolsToRemove.includes(n));\n\n if (remaining.length === existingNames.length) continue; // Nothing to remove from this import\n\n if (remaining.length === 0) {\n // Remove the entire import statement\n result = result.slice(0, stmt.getStart(sf)) + result.slice(stmt.getEnd()).replace(/^\\r?\\n/, '');\n } else {\n const newClause = `{ ${remaining.join(', ')} }`;\n result = result.slice(0, namedBindings.getStart(sf)) + newClause + result.slice(namedBindings.getEnd());\n }\n break; // Only process the first matching import for the consolidated symbols\n }\n\n return result;\n}\n",
2148
- "displayName": "Schema",
2149
- "properties": [
2150
- {
2151
- "name": "dryRun",
2152
- "coverageIgnore": false,
2153
- "deprecated": false,
2154
- "deprecationMessage": "",
2155
- "type": "boolean",
2156
- "indexKey": "",
2157
- "optional": true,
2158
- "description": "",
2159
- "line": 9,
2160
- "rawdescription": "\n"
2161
- },
2162
- {
2163
- "name": "path",
2164
- "coverageIgnore": false,
2165
- "deprecated": false,
2166
- "deprecationMessage": "",
2167
- "type": "string",
2168
- "indexKey": "",
2169
- "optional": true,
2170
- "description": "",
2171
- "line": 8,
2172
- "rawdescription": "\n"
2173
- },
2174
- {
2175
- "name": "useClassArray",
2176
- "coverageIgnore": false,
2177
- "deprecated": false,
2178
- "deprecationMessage": "",
2179
- "type": "boolean",
2180
- "indexKey": "",
2181
- "optional": true,
2182
- "description": "",
2183
- "line": 10,
2184
- "rawdescription": "\n"
2185
- }
2186
- ],
2187
- "indexSignatures": [],
2188
- "kind": 172,
2189
- "methods": [],
2190
- "extends": [],
2191
- "relationships": {
2192
- "incoming": [],
2193
- "outgoing": []
2194
- }
2195
- },
2196
- {
2197
- "name": "Schema",
2198
- "id": "interface-Schema-4fe31ff3e9f1d34845a6b865d605e215f33552094b88c3d0eab0b180187fe64ce4d68d687516cb3d62c57d2678a103969b2dacbb18a49b26060f78096678fcce-1",
2142
+ "id": "interface-Schema-4fe31ff3e9f1d34845a6b865d605e215f33552094b88c3d0eab0b180187fe64ce4d68d687516cb3d62c57d2678a103969b2dacbb18a49b26060f78096678fcce",
2199
2143
  "file": "packages/core/schematics/fix-no-multiple-empty-lines/index.ts",
2200
2144
  "deprecated": false,
2201
2145
  "deprecationMessage": "",
@@ -2232,9 +2176,6 @@
2232
2176
  "kind": 172,
2233
2177
  "methods": [],
2234
2178
  "extends": [],
2235
- "isDuplicate": true,
2236
- "duplicateId": 1,
2237
- "duplicateName": "Schema-1",
2238
2179
  "relationships": {
2239
2180
  "incoming": [],
2240
2181
  "outgoing": []
@@ -2242,7 +2183,7 @@
2242
2183
  },
2243
2184
  {
2244
2185
  "name": "Schema",
2245
- "id": "interface-Schema-5cd6db1920bd5b70a44c0b8a7f7e30f600bfd16a9950f218e6d451a1755ced95b462ef9a62ee87e39bcb8c392981b8d2597895bf0a272fd8aea71f03429ed976-2",
2186
+ "id": "interface-Schema-5cd6db1920bd5b70a44c0b8a7f7e30f600bfd16a9950f218e6d451a1755ced95b462ef9a62ee87e39bcb8c392981b8d2597895bf0a272fd8aea71f03429ed976-1",
2246
2187
  "file": "packages/core/schematics/icon-migrate/schema.ts",
2247
2188
  "deprecated": false,
2248
2189
  "deprecationMessage": "",
@@ -2280,8 +2221,8 @@
2280
2221
  "methods": [],
2281
2222
  "extends": [],
2282
2223
  "isDuplicate": true,
2283
- "duplicateId": 2,
2284
- "duplicateName": "Schema-2",
2224
+ "duplicateId": 1,
2225
+ "duplicateName": "Schema-1",
2285
2226
  "relationships": {
2286
2227
  "incoming": [],
2287
2228
  "outgoing": []
@@ -2289,7 +2230,7 @@
2289
2230
  },
2290
2231
  {
2291
2232
  "name": "Schema",
2292
- "id": "interface-Schema-56b9fe60701ca349dc90e152829b0a18bb7a8a9bb303c4bac05f9764cd2e933cce0879775ca17168b4c881e202d0258af3668a2a3a1b940e561f7e4b2349b5cc-3",
2233
+ "id": "interface-Schema-56b9fe60701ca349dc90e152829b0a18bb7a8a9bb303c4bac05f9764cd2e933cce0879775ca17168b4c881e202d0258af3668a2a3a1b940e561f7e4b2349b5cc-2",
2293
2234
  "file": "packages/core/schematics/migrate/schema.ts",
2294
2235
  "deprecated": false,
2295
2236
  "deprecationMessage": "",
@@ -2339,8 +2280,8 @@
2339
2280
  "methods": [],
2340
2281
  "extends": [],
2341
2282
  "isDuplicate": true,
2342
- "duplicateId": 3,
2343
- "duplicateName": "Schema-3",
2283
+ "duplicateId": 2,
2284
+ "duplicateName": "Schema-2",
2344
2285
  "relationships": {
2345
2286
  "incoming": [],
2346
2287
  "outgoing": []
@@ -2348,7 +2289,7 @@
2348
2289
  },
2349
2290
  {
2350
2291
  "name": "Schema",
2351
- "id": "interface-Schema-e96d29c1785a16147de773cebe7ab5d4ed82aa23c6b1f7737113252a14d156e236f46f7a16340432dec900b664c8005be0aef5d0a628c2f5b8dd2ec7e41edf56-4",
2292
+ "id": "interface-Schema-e96d29c1785a16147de773cebe7ab5d4ed82aa23c6b1f7737113252a14d156e236f46f7a16340432dec900b664c8005be0aef5d0a628c2f5b8dd2ec7e41edf56-3",
2352
2293
  "file": "packages/core/schematics/migrate-eui-accent/index.ts",
2353
2294
  "deprecated": false,
2354
2295
  "deprecationMessage": "",
@@ -2386,8 +2327,8 @@
2386
2327
  "methods": [],
2387
2328
  "extends": [],
2388
2329
  "isDuplicate": true,
2389
- "duplicateId": 4,
2390
- "duplicateName": "Schema-4",
2330
+ "duplicateId": 3,
2331
+ "duplicateName": "Schema-3",
2391
2332
  "relationships": {
2392
2333
  "incoming": [],
2393
2334
  "outgoing": []
@@ -2395,7 +2336,7 @@
2395
2336
  },
2396
2337
  {
2397
2338
  "name": "Schema",
2398
- "id": "interface-Schema-585b211d989563bbec5bd67bcdb58d5f264396adbe0ed2e51d0b4a0ecaf8b3ec36d821647c170d616bb409fc603c5a4c0e699eb7511e02c6add8d8670fc9cb9f-5",
2339
+ "id": "interface-Schema-585b211d989563bbec5bd67bcdb58d5f264396adbe0ed2e51d0b4a0ecaf8b3ec36d821647c170d616bb409fc603c5a4c0e699eb7511e02c6add8d8670fc9cb9f-4",
2399
2340
  "file": "packages/core/schematics/migrate-eui-alert/index.ts",
2400
2341
  "deprecated": false,
2401
2342
  "deprecationMessage": "",
@@ -2433,8 +2374,8 @@
2433
2374
  "methods": [],
2434
2375
  "extends": [],
2435
2376
  "isDuplicate": true,
2436
- "duplicateId": 5,
2437
- "duplicateName": "Schema-5",
2377
+ "duplicateId": 4,
2378
+ "duplicateName": "Schema-4",
2438
2379
  "relationships": {
2439
2380
  "incoming": [],
2440
2381
  "outgoing": []
@@ -2442,7 +2383,7 @@
2442
2383
  },
2443
2384
  {
2444
2385
  "name": "Schema",
2445
- "id": "interface-Schema-c7e2cc0add09c97d83ec2675d59c86951681f8e2d49f133fb2f216ca98d0da1fdf892d45af31f19ecbbfd0d32f4ad70dd99e862f75725d3812f66a73ec34362c-6",
2386
+ "id": "interface-Schema-c7e2cc0add09c97d83ec2675d59c86951681f8e2d49f133fb2f216ca98d0da1fdf892d45af31f19ecbbfd0d32f4ad70dd99e862f75725d3812f66a73ec34362c-5",
2446
2387
  "file": "packages/core/schematics/migrate-eui-avatar/index.ts",
2447
2388
  "deprecated": false,
2448
2389
  "deprecationMessage": "",
@@ -2480,8 +2421,8 @@
2480
2421
  "methods": [],
2481
2422
  "extends": [],
2482
2423
  "isDuplicate": true,
2483
- "duplicateId": 6,
2484
- "duplicateName": "Schema-6",
2424
+ "duplicateId": 5,
2425
+ "duplicateName": "Schema-5",
2485
2426
  "relationships": {
2486
2427
  "incoming": [],
2487
2428
  "outgoing": []
@@ -2489,12 +2430,12 @@
2489
2430
  },
2490
2431
  {
2491
2432
  "name": "Schema",
2492
- "id": "interface-Schema-c0c08f5e83da9afa13ce8375f982fb280b9bde854e00c914f41b04d44deecd531496c2cee264b50314e92a6260c26cebe35741a2cf5ec068d8b6da5c5267539e-7",
2493
- "file": "packages/core/schematics/migrate-eui-button/index.ts",
2433
+ "id": "interface-Schema-9c5e016857e1416ac7bbb881973e644c0f578a53bd432bb951c1676ac3f2a6631bfe3344860346a081b841217278723e0bb0bf2fa35fb869de67cb0cc8d99849-6",
2434
+ "file": "packages/core/schematics/add-eui-imports/index.ts",
2494
2435
  "deprecated": false,
2495
2436
  "deprecationMessage": "",
2496
2437
  "type": "interface",
2497
- "sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst OLD_NAME = 'euiButtonCall';\nconst NEW_NAME = 'euiCTAButton';\n\nexport function migrateEuiButton(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(OLD_NAME)) return;\n\n let result: string;\n\n if (path.endsWith('.html')) {\n result = migrateTemplate(original);\n } else {\n result = migrateInlineTemplates(original);\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would rename '${OLD_NAME}' → '${NEW_NAME}' in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n\n // Warn about TS property access usages\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts')) {\n if (!original.includes(OLD_NAME)) return;\n\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === OLD_NAME) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(`${path}:${line + 1} - \"${OLD_NAME}\" has been renamed to \"${NEW_NAME}\". Update this reference manually.`);\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n });\n\n context.logger.info(`Renamed '${OLD_NAME}' → '${NEW_NAME}' on elements with euiButton in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: { start: number; end: number; replacement: string }[] = [];\n\n visitNodes(parsed.nodes, edits);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: { start: number; end: number; text: string }[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(OLD_NAME)) {\n ts.forEachChild(node, visit); return; \n}\n const migrated = migrateTemplate(rawTemplate);\n if (migrated !== rawTemplate) changes.push({ start, end, text: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n let result = source;\n for (const change of changes.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, change.start) + change.text + result.slice(change.end);\n }\n return result;\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction hasEuiButtonAttribute(element: TmplAstElement): boolean {\n return element.attributes.some((a) => a.name === 'euiButton') ||\n element.inputs.some((i) => i.name === 'euiButton');\n}\n\nfunction visitNodes(nodes: TmplAstNode[], edits: { start: number; end: number; replacement: string }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (hasEuiButtonAttribute(node)) collectRenames(node, edits);\n visitNodes(node.children, edits);\n }\n }\n}\n\nfunction collectRenames(element: TmplAstElement, edits: { start: number; end: number; replacement: string }[]): void {\n for (const attr of element.attributes) {\n if (attr.name === OLD_NAME) {\n edits.push({ start: attr.keySpan!.start.offset, end: attr.keySpan!.end.offset, replacement: NEW_NAME });\n }\n }\n for (const input of element.inputs) {\n if (input.name === OLD_NAME) {\n edits.push({ start: input.keySpan!.start.offset, end: input.keySpan!.end.offset, replacement: NEW_NAME });\n }\n }\n}\n\nfunction applyEdits(source: string, edits: { start: number; end: number; replacement: string }[]): string {\n let result = source;\n for (const edit of edits.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n",
2438
+ "sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode, TmplAstTemplate } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\nimport { SELECTOR_MAP, SelectorEntry, getClassNamesForArray } from './selector-map';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n useClassArray?: boolean;\n}\n\ninterface ImportToAdd {\n /** The symbol to add to imports array (class name or array name for spread) */\n symbol: string;\n /** Whether this should be spread (...EUI_BUTTON) */\n isSpread: boolean;\n /** ES import path */\n importPath: string;\n}\n\nexport function addEuiImports(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n const useClassArray = options.useClassArray ?? false;\n let filesUpdated = 0;\n\n // Index NgModules for standalone:false support\n const ngModuleIndex = buildNgModuleIndex(tree, tree.getDir(scanPath || '/'));\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n if (!path.endsWith('.ts') || path.endsWith('.spec.ts')) return;\n\n const buffer = tree.read(path);\n if (!buffer) return;\n const source = buffer.toString('utf-8');\n\n const sourceFile = ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true);\n const components = findComponentDecorators(sourceFile);\n if (components.length === 0) return;\n\n let modified = false;\n\n for (const { decorator, className: componentClassName, isNonStandalone } of components) {\n const templateHtml = getTemplateContent(tree, path, decorator, source);\n if (!templateHtml) continue;\n\n const matched = matchSelectorsInTemplate(templateHtml);\n if (matched.length === 0) continue;\n\n const importsToAdd = resolveImports(matched, useClassArray);\n if (importsToAdd.length === 0) continue;\n\n if (isNonStandalone) {\n // Find the NgModule that declares this component and add imports there\n const moduleInfo = findDeclaringModule(ngModuleIndex, componentClassName);\n if (!moduleInfo) {\n context.logger.warn(`⚠ Could not find declaring NgModule for ${componentClassName} in ${path}`);\n continue;\n }\n const moduleBuffer = tree.read(moduleInfo.path);\n if (!moduleBuffer) continue;\n const moduleSource = moduleBuffer.toString('utf-8');\n const result = addImportsToFile(moduleSource, moduleInfo.path, moduleInfo.decoratorPos, importsToAdd, useClassArray);\n if (result !== moduleSource) {\n if (options.dryRun) {\n logDryRun(context, `Would add EUI imports to NgModule in ${moduleInfo.path} for component ${componentClassName}`);\n } else {\n tree.overwrite(moduleInfo.path, result);\n }\n modified = true;\n }\n } else {\n // Standalone component — add imports directly\n const currentSource = tree.read(path)!.toString('utf-8');\n const result = addImportsToFile(currentSource, path, decorator.getStart(), importsToAdd, useClassArray);\n if (result !== currentSource) {\n if (options.dryRun) {\n logDryRun(context, `Would add EUI imports to ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n modified = true;\n }\n }\n }\n\n if (modified) filesUpdated++;\n });\n\n context.logger.info(`add-eui-imports: ${filesUpdated} file(s) updated.`);\n if (options.dryRun) logDryRunNote(context);\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\n// --- Selector Matching ---\n\nfunction matchSelectorsInTemplate(html: string): SelectorEntry[] {\n const parsed = parseTemplate(html, '', { preserveWhitespaces: true });\n if (parsed.errors?.length) return [];\n\n const matched: SelectorEntry[] = [];\n visitTemplateNodes(parsed.nodes, matched);\n return matched;\n}\n\nfunction visitTemplateNodes(nodes: TmplAstNode[], matched: SelectorEntry[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n matchElement(node, matched);\n visitTemplateNodes(node.children, matched);\n } else if (node instanceof TmplAstTemplate) {\n visitTemplateNodes(node.children, matched);\n }\n }\n}\n\nfunction matchElement(element: TmplAstElement, matched: SelectorEntry[]): void {\n const tagName = element.name;\n const attrNames = new Set([\n ...element.attributes.map(a => a.name),\n ...element.inputs.map(i => i.name),\n ]);\n\n for (const entry of SELECTOR_MAP) {\n if (entry.element && entry.element !== tagName) continue;\n if (!entry.element && entry.attributes.length === 0) continue;\n if (!entry.attributes.every(attr => attrNames.has(attr))) continue;\n // If no element specified, at least one attribute must match on this element\n if (!entry.element && entry.attributes.length > 0 && !entry.attributes.some(attr => attrNames.has(attr))) continue;\n matched.push(entry);\n }\n}\n\n// --- Import Resolution ---\n\nfunction resolveImports(matched: SelectorEntry[], useClassArray: boolean): ImportToAdd[] {\n const seen = new Set<string>();\n const result: ImportToAdd[] = [];\n\n for (const entry of matched) {\n if (useClassArray && entry.classArray) {\n if (seen.has(entry.classArray)) continue;\n seen.add(entry.classArray);\n result.push({ symbol: entry.classArray, isSpread: true, importPath: entry.importPath });\n } else {\n if (seen.has(entry.className)) continue;\n seen.add(entry.className);\n result.push({ symbol: entry.className, isSpread: false, importPath: entry.importPath });\n }\n }\n\n return result;\n}\n\n// --- Template Extraction ---\n\nfunction getTemplateContent(tree: Tree, tsPath: string, decorator: ts.Decorator, source: string): string | null {\n const call = decorator.expression as ts.CallExpression;\n if (!call.arguments[0] || !ts.isObjectLiteralExpression(call.arguments[0])) return null;\n const metadata = call.arguments[0];\n\n for (const prop of metadata.properties) {\n if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) continue;\n if (prop.name.text === 'template') {\n const init = prop.initializer;\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n return init.text;\n }\n }\n if (prop.name.text === 'templateUrl') {\n if (ts.isStringLiteral(prop.initializer)) {\n const dir = tsPath.substring(0, tsPath.lastIndexOf('/'));\n const templateBuffer = tree.read(`${dir}/${prop.initializer.text}`);\n if (templateBuffer) return templateBuffer.toString('utf-8');\n }\n }\n }\n return null;\n}\n\n// --- Component Decorator Detection ---\n\ninterface ComponentInfo {\n decorator: ts.Decorator;\n className: string;\n isNonStandalone: boolean;\n}\n\nfunction findComponentDecorators(sourceFile: ts.SourceFile): ComponentInfo[] {\n const results: ComponentInfo[] = [];\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node) && node.name) {\n const decs = ts.getDecorators(node);\n if (decs) {\n for (const dec of decs) {\n if (ts.isCallExpression(dec.expression) && ts.isIdentifier(dec.expression.expression) && dec.expression.expression.text === 'Component') {\n const isNonStandalone = hasStandaloneFalse(dec);\n results.push({ decorator: dec, className: node.name.text, isNonStandalone });\n }\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n return results;\n}\n\nfunction hasStandaloneFalse(decorator: ts.Decorator): boolean {\n const call = decorator.expression as ts.CallExpression;\n if (!call.arguments[0] || !ts.isObjectLiteralExpression(call.arguments[0])) return false;\n for (const prop of call.arguments[0].properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'standalone') {\n return prop.initializer.kind === ts.SyntaxKind.FalseKeyword;\n }\n }\n return false;\n}\n\n// --- NgModule Index ---\n\ninterface NgModuleInfo {\n path: string;\n declarations: string[];\n decoratorPos: number;\n}\n\nfunction buildNgModuleIndex(tree: Tree, dir: DirEntry): NgModuleInfo[] {\n const modules: NgModuleInfo[] = [];\n\n visitDir(dir, (path) => {\n if (!path.endsWith('.ts') || path.endsWith('.spec.ts')) return;\n\n const buffer = tree.read(path);\n if (!buffer) return;\n const source = buffer.toString('utf-8');\n if (!source.includes('NgModule')) return;\n\n const sf = ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true);\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node)) {\n const decs = ts.getDecorators(node);\n if (decs) {\n for (const dec of decs) {\n if (ts.isCallExpression(dec.expression) && ts.isIdentifier(dec.expression.expression) && dec.expression.expression.text === 'NgModule') {\n const declarations = extractArrayProperty(dec, 'declarations', source);\n modules.push({ path, declarations, decoratorPos: dec.getStart() });\n }\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sf);\n });\n\n return modules;\n}\n\nfunction extractArrayProperty(decorator: ts.Decorator, propName: string, source: string): string[] {\n const call = decorator.expression as ts.CallExpression;\n if (!call.arguments[0] || !ts.isObjectLiteralExpression(call.arguments[0])) return [];\n for (const prop of call.arguments[0].properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === propName) {\n if (ts.isArrayLiteralExpression(prop.initializer)) {\n return prop.initializer.elements\n .filter(ts.isIdentifier)\n .map(id => id.text);\n }\n }\n }\n return [];\n}\n\nfunction findDeclaringModule(modules: NgModuleInfo[], componentClassName: string): NgModuleInfo | undefined {\n return modules.find(m => m.declarations.includes(componentClassName));\n}\n\n// --- Import Addition ---\n\nfunction addImportsToFile(source: string, filePath: string, decoratorStartHint: number, imports: ImportToAdd[], useClassArray: boolean): string {\n const sf = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);\n\n // Find the imports array in the decorator closest to decoratorStartHint\n const importsArrayInfo = findDecoratorImportsArray(sf, source, decoratorStartHint);\n if (!importsArrayInfo) return source;\n\n const { arrayNode, decoratorType } = importsArrayInfo;\n\n // Determine what's already in the imports array\n const existingSymbols = new Set<string>();\n const existingSpreads = new Set<string>();\n for (const el of arrayNode.elements) {\n if (ts.isSpreadElement(el) && ts.isIdentifier(el.expression)) {\n existingSpreads.add(el.expression.text);\n } else if (ts.isIdentifier(el)) {\n existingSymbols.add(el.text);\n }\n }\n\n // Filter out already-present imports and compute what to add/remove\n const toAdd: ImportToAdd[] = [];\n const toRemoveFromArray: string[] = []; // individual class names to consolidate\n\n for (const imp of imports) {\n if (imp.isSpread) {\n if (existingSpreads.has(imp.symbol)) continue; // Already has ...EUI_X\n toAdd.push(imp);\n // Consolidate: remove individual class names covered by this array\n if (useClassArray) {\n const coveredClasses = getClassNamesForArray(imp.symbol);\n for (const cls of coveredClasses) {\n if (existingSymbols.has(cls)) toRemoveFromArray.push(cls);\n }\n }\n } else {\n if (existingSymbols.has(imp.symbol)) continue;\n // Also skip if a spread already covers this class\n const coveringArray = imports.find(i => i.isSpread && getClassNamesForArray(i.symbol).includes(imp.symbol));\n if (coveringArray && (existingSpreads.has(coveringArray.symbol) || toAdd.some(a => a.symbol === coveringArray.symbol))) continue;\n toAdd.push(imp);\n }\n }\n\n if (toAdd.length === 0 && toRemoveFromArray.length === 0) return source;\n\n // Build new array content\n let result = source;\n result = updateDecoratorImportsArray(result, filePath, arrayNode, toAdd, toRemoveFromArray);\n\n // Add ES imports\n result = addEsImports(result, filePath, toAdd);\n\n // Remove consolidated class names from ES imports\n if (toRemoveFromArray.length > 0) {\n result = removeFromEsImports(result, filePath, toRemoveFromArray);\n }\n\n return result;\n}\n\ninterface ImportsArrayInfo {\n arrayNode: ts.ArrayLiteralExpression;\n decoratorType: 'Component' | 'NgModule';\n}\n\nfunction findDecoratorImportsArray(sf: ts.SourceFile, source: string, decoratorStartHint: number): ImportsArrayInfo | null {\n let found: ImportsArrayInfo | null = null;\n\n const visit = (node: ts.Node): void => {\n if (found) return;\n if (ts.isClassDeclaration(node)) {\n const decs = ts.getDecorators(node);\n if (!decs) return;\n for (const dec of decs) {\n if (!ts.isCallExpression(dec.expression)) continue;\n if (!ts.isIdentifier(dec.expression.expression)) continue;\n const decName = dec.expression.expression.text;\n if (decName !== 'Component' && decName !== 'NgModule') continue;\n if (Math.abs(dec.getStart() - decoratorStartHint) > 5) continue; // Match by position\n\n const metadata = dec.expression.arguments[0];\n if (!ts.isObjectLiteralExpression(metadata)) continue;\n\n for (const prop of metadata.properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'imports') {\n if (ts.isArrayLiteralExpression(prop.initializer)) {\n found = { arrayNode: prop.initializer, decoratorType: decName as 'Component' | 'NgModule' };\n return;\n }\n }\n }\n\n // No imports array found — create one\n if (!found && decName === 'Component') {\n // We need to add `imports: []` to the decorator\n // Insert after the last property\n const lastProp = metadata.properties[metadata.properties.length - 1];\n if (lastProp) {\n const insertPos = lastProp.getEnd();\n const indent = detectIndent(source, metadata.getStart());\n const insertion = `,\\n${indent} imports: []`;\n const newSource = source.slice(0, insertPos) + insertion + source.slice(insertPos);\n // Re-parse to get the array node\n const newSf = ts.createSourceFile('', newSource, ts.ScriptTarget.Latest, true);\n const newArray = findImportsArrayInSource(newSf);\n if (newArray) {\n // We can't return a node from a different source file in the general case.\n // Instead, we'll handle the \"no imports array\" case by adding it inline.\n found = null; // Will be handled separately\n }\n }\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sf);\n return found;\n}\n\nfunction findImportsArrayInSource(sf: ts.SourceFile): ts.ArrayLiteralExpression | null {\n let found: ts.ArrayLiteralExpression | null = null;\n const visit = (node: ts.Node): void => {\n if (found) return;\n if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && node.name.text === 'imports' && ts.isArrayLiteralExpression(node.initializer)) {\n found = node.initializer;\n }\n ts.forEachChild(node, visit);\n };\n visit(sf);\n return found;\n}\n\nfunction updateDecoratorImportsArray(source: string, filePath: string, arrayNode: ts.ArrayLiteralExpression, toAdd: ImportToAdd[], toRemove: string[]): string {\n const sf = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);\n\n // Rebuild the array content\n const existingElements: string[] = [];\n for (const el of arrayNode.elements) {\n const text = source.slice(el.getStart(sf), el.getEnd()).trim();\n // Check if this element should be removed (consolidation)\n if (ts.isIdentifier(el) && toRemove.includes(el.text)) continue;\n existingElements.push(text);\n }\n\n // Add new entries\n for (const imp of toAdd) {\n const entry = imp.isSpread ? `...${imp.symbol}` : imp.symbol;\n if (!existingElements.includes(entry)) {\n existingElements.push(entry);\n }\n }\n\n // Determine formatting\n const arrayStart = arrayNode.getStart(sf);\n const arrayEnd = arrayNode.getEnd();\n const originalText = source.slice(arrayStart, arrayEnd);\n const isMultiline = originalText.includes('\\n');\n\n let newArrayText: string;\n if (isMultiline || existingElements.length > 3) {\n const indent = detectIndent(source, arrayStart);\n const itemIndent = indent + ' ';\n newArrayText = `[\\n${existingElements.map(e => `${itemIndent}${e},`).join('\\n')}\\n${indent}]`;\n } else {\n newArrayText = `[${existingElements.join(', ')}]`;\n }\n\n return source.slice(0, arrayStart) + newArrayText + source.slice(arrayEnd);\n}\n\nfunction addEsImports(source: string, filePath: string, imports: ImportToAdd[]): string {\n let result = source;\n\n // Group by import path\n const byPath = new Map<string, string[]>();\n for (const imp of imports) {\n const existing = byPath.get(imp.importPath) || [];\n existing.push(imp.symbol);\n byPath.set(imp.importPath, existing);\n }\n\n for (const [importPath, symbols] of byPath) {\n const sf = ts.createSourceFile(filePath, result, ts.ScriptTarget.Latest, true);\n\n // Check if there's already an import from this path\n const existingImport = sf.statements.find(\n (s): s is ts.ImportDeclaration =>\n ts.isImportDeclaration(s) && ts.isStringLiteral(s.moduleSpecifier) && s.moduleSpecifier.text === importPath,\n );\n\n if (existingImport?.importClause?.namedBindings && ts.isNamedImports(existingImport.importClause.namedBindings)) {\n // Extend existing import\n const namedBindings = existingImport.importClause.namedBindings;\n const existingNames = namedBindings.elements.map(el => el.name.text);\n const newNames = symbols.filter(s => !existingNames.includes(s));\n if (newNames.length === 0) continue;\n\n const allNames = [...existingNames, ...newNames].sort();\n const newClause = `{ ${allNames.join(', ')} }`;\n result = result.slice(0, namedBindings.getStart(sf)) + newClause + result.slice(namedBindings.getEnd());\n } else {\n // Add new import statement\n const sortedSymbols = [...symbols].sort();\n const newImport = `import { ${sortedSymbols.join(', ')} } from '${importPath}';\\n`;\n\n // Insert after the last existing import\n const lastImport = [...sf.statements].reverse().find(ts.isImportDeclaration);\n if (lastImport) {\n const pos = lastImport.getEnd();\n result = result.slice(0, pos) + '\\n' + newImport.trimEnd() + result.slice(pos);\n } else {\n result = newImport + result;\n }\n }\n }\n\n return result;\n}\n\nfunction detectIndent(source: string, pos: number): string {\n const lineStart = source.lastIndexOf('\\n', pos - 1) + 1;\n const match = source.slice(lineStart, pos).match(/^(\\s*)/);\n return match ? match[1] : '';\n}\n\nfunction removeFromEsImports(source: string, filePath: string, symbolsToRemove: string[]): string {\n let result = source;\n const sf = ts.createSourceFile(filePath, result, ts.ScriptTarget.Latest, true);\n\n for (const stmt of sf.statements) {\n if (!ts.isImportDeclaration(stmt) || !stmt.importClause?.namedBindings || !ts.isNamedImports(stmt.importClause.namedBindings)) continue;\n const namedBindings = stmt.importClause.namedBindings;\n const existingNames = namedBindings.elements.map(el => el.name.text);\n const remaining = existingNames.filter(n => !symbolsToRemove.includes(n));\n\n if (remaining.length === existingNames.length) continue; // Nothing to remove from this import\n\n if (remaining.length === 0) {\n // Remove the entire import statement\n result = result.slice(0, stmt.getStart(sf)) + result.slice(stmt.getEnd()).replace(/^\\r?\\n/, '');\n } else {\n const newClause = `{ ${remaining.join(', ')} }`;\n result = result.slice(0, namedBindings.getStart(sf)) + newClause + result.slice(namedBindings.getEnd());\n }\n break; // Only process the first matching import for the consolidated symbols\n }\n\n return result;\n}\n",
2498
2439
  "displayName": "Schema",
2499
2440
  "properties": [
2500
2441
  {
@@ -2506,7 +2447,7 @@
2506
2447
  "indexKey": "",
2507
2448
  "optional": true,
2508
2449
  "description": "",
2509
- "line": 8,
2450
+ "line": 9,
2510
2451
  "rawdescription": "\n"
2511
2452
  },
2512
2453
  {
@@ -2518,81 +2459,11 @@
2518
2459
  "indexKey": "",
2519
2460
  "optional": true,
2520
2461
  "description": "",
2521
- "line": 7,
2522
- "rawdescription": "\n"
2523
- }
2524
- ],
2525
- "indexSignatures": [],
2526
- "kind": 172,
2527
- "methods": [],
2528
- "extends": [],
2529
- "isDuplicate": true,
2530
- "duplicateId": 7,
2531
- "duplicateName": "Schema-7",
2532
- "relationships": {
2533
- "incoming": [],
2534
- "outgoing": []
2535
- }
2536
- },
2537
- {
2538
- "name": "Schema",
2539
- "id": "interface-Schema-39d3d52e788050bebc4e0b99ed0756f7e9727556cb090ae8796bd4261a56463fe1f479c322b23028cd4134d71e4d1dc44cd248ac62a9a148b5fc46a665466f7c-8",
2540
- "file": "packages/core/schematics/migrate-eui-chip/index.ts",
2541
- "deprecated": false,
2542
- "deprecationMessage": "",
2543
- "type": "interface",
2544
- "sourceCode": "import { parseTemplate, TmplAstBoundAttribute, TmplAstElement, TmplAstNode, TmplAstTextAttribute } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst REMOVED_INPUTS = new Set(['isSquared']);\n\nexport function migrateEuiChip(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes('eui-chip') && !original.includes('euiChip')) return;\n\n const result = path.endsWith('.html')\n ? migrateTemplate(original)\n : migrateInlineTemplates(original);\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would remove 'isSquared' input in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n\n // Warn about TS usages inline\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts') && original.includes('isSquared')) {\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === 'isSquared') {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(`${path}:${line + 1} - \"isSquared\" is no longer a valid input on eui-chip. Remove this assignment.`);\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n });\n\n context.logger.info(`Removed deprecated eui-chip 'isSquared' input from ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const removals: { start: number; end: number }[] = [];\n\n visitNodes(parsed.nodes, removals);\n\n let result = source;\n for (const { start, end } of removals.sort((a, b) => b.start - a.start)) {\n let adjustedStart = start;\n while (adjustedStart > 0 && (result[adjustedStart - 1] === ' ' || result[adjustedStart - 1] === '\\t')) {\n adjustedStart--;\n }\n result = result.slice(0, adjustedStart) + result.slice(end);\n }\n\n return result;\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const templateRegex = /template\\s*:\\s*`([^`]*)`/gs;\n return source.replace(templateRegex, (match, templateContent: string) => {\n if (!templateContent.includes('eui-chip') && !templateContent.includes('euiChip')) return match;\n const migrated = migrateTemplate(templateContent);\n if (migrated === templateContent) return match;\n return match.replace(templateContent, migrated);\n });\n}\n\nfunction isChipElement(element: TmplAstElement): boolean {\n if (element.name === 'eui-chip') return true;\n return element.attributes.some((a) => a.name === 'euiChip');\n}\n\nfunction visitNodes(nodes: TmplAstNode[], removals: { start: number; end: number }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (isChipElement(node)) collectRemovals(node, removals);\n visitNodes(node.children, removals);\n }\n }\n}\n\nfunction collectRemovals(element: TmplAstElement, removals: { start: number; end: number }[]): void {\n for (const attr of element.attributes) {\n if (REMOVED_INPUTS.has(attr.name)) {\n removals.push(getAttributeSpan(attr));\n }\n }\n for (const input of element.inputs) {\n if (REMOVED_INPUTS.has(input.name)) {\n removals.push(getAttributeSpan(input));\n }\n }\n}\n\nfunction getAttributeSpan(attr: TmplAstTextAttribute | TmplAstBoundAttribute): { start: number; end: number } {\n return { start: attr.sourceSpan.start.offset, end: attr.sourceSpan.end.offset };\n}\n",
2545
- "displayName": "Schema",
2546
- "properties": [
2547
- {
2548
- "name": "dryRun",
2549
- "coverageIgnore": false,
2550
- "deprecated": false,
2551
- "deprecationMessage": "",
2552
- "type": "boolean",
2553
- "indexKey": "",
2554
- "optional": true,
2555
- "description": "",
2556
2462
  "line": 8,
2557
2463
  "rawdescription": "\n"
2558
2464
  },
2559
2465
  {
2560
- "name": "path",
2561
- "coverageIgnore": false,
2562
- "deprecated": false,
2563
- "deprecationMessage": "",
2564
- "type": "string",
2565
- "indexKey": "",
2566
- "optional": true,
2567
- "description": "",
2568
- "line": 7,
2569
- "rawdescription": "\n"
2570
- }
2571
- ],
2572
- "indexSignatures": [],
2573
- "kind": 172,
2574
- "methods": [],
2575
- "extends": [],
2576
- "isDuplicate": true,
2577
- "duplicateId": 8,
2578
- "duplicateName": "Schema-8",
2579
- "relationships": {
2580
- "incoming": [],
2581
- "outgoing": []
2582
- }
2583
- },
2584
- {
2585
- "name": "Schema",
2586
- "id": "interface-Schema-311bef7d5e4b40b356b0fdb5e35dd89e48a5c5beec29eeb865c22c2ca1c4c4fb1fd0bb4391ed0b33825a6d1d744bc3a2e9822b347fbc686a537e15b170c279dd-9",
2587
- "file": "packages/core/schematics/migrate-eui-chip-list/index.ts",
2588
- "deprecated": false,
2589
- "deprecationMessage": "",
2590
- "type": "interface",
2591
- "sourceCode": "import { parseTemplate, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstElement, TmplAstNode, TmplAstTextAttribute } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\nconst CHIP_LIST_TAG = 'eui-chip-list';\nconst CHIP_LIST_ATTR = 'euiChipList';\nconst CHIP_TAG = 'eui-chip';\nconst CHIP_ATTR = 'euiChip';\n\nconst PROPAGATED_INPUTS = new Set([\n 'euiPrimary', 'euiSecondary', 'euiSuccess', 'euiInfo', 'euiWarning',\n 'euiDanger', 'euiAccent', 'euiVariant', 'euiSizeS', 'euiSizeVariant',\n 'euiOutline', 'euiDisabled',\n]);\n\nconst WARN_PROPERTIES = new Set([...PROPAGATED_INPUTS, 'chipRemove', 'isChipsRemovable', 'chipsLabelTruncateCount',\n 'maxVisibleChipsCount', 'isMaxVisibleChipsOpened', 'toggleLinkMoreLabel', 'toggleLinkLessLabel',\n 'isChipsSorted', 'chipsSortOrder']);\n\nconst REMOVED_INPUTS = new Set(['maxVisibleChipsCount', 'isMaxVisibleChipsOpened', 'toggleLinkMoreLabel', 'toggleLinkLessLabel', 'isChipsSorted', 'chipsSortOrder']);\n\nconst TRUNCATE_PIPE_IMPORT = 'EuiTruncatePipe';\nconst TRUNCATE_PIPE_PATH = '@eui/components/pipes';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\ninterface Edit {\n start: number;\n end: number;\n replacement: string;\n}\n\nexport function migrateEuiChipList(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n const filesNeedingTruncateImport = new Set<string>();\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(CHIP_LIST_TAG) && !original.includes(CHIP_LIST_ATTR)) return;\n\n let result: string;\n let addedTruncate = false;\n\n if (path.endsWith('.html')) {\n result = migrateTemplate(original);\n if (result !== original && result.includes('euiTruncate') && !original.includes('euiTruncate')) {\n // Find the associated .ts file\n const tsPath = path.replace(/\\.html$/, '.ts');\n if (tree.exists(tsPath)) {\n filesNeedingTruncateImport.add(tsPath);\n } else {\n // Try component naming convention\n const componentTsPath = path.replace(/\\.html$/, '.component.ts');\n if (tree.exists(componentTsPath)) {\n filesNeedingTruncateImport.add(componentTsPath);\n }\n }\n }\n } else {\n result = migrateInlineTemplates(original);\n if (result !== original && result.includes('euiTruncate') && !original.includes('euiTruncate')) {\n addedTruncate = true;\n }\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would move variant/size/outline inputs to child eui-chip in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n\n if (addedTruncate) {\n filesNeedingTruncateImport.add(path);\n }\n\n // Warn about TS usages of removed properties\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts')) {\n if ([...WARN_PROPERTIES].some((p) => original.includes(p))) {\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && WARN_PROPERTIES.has(node.name.text)) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${path}:${line + 1} - \"${node.name.text}\" has been removed from eui-chip-list. Move it to individual eui-chip elements.`,\n );\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n }\n });\n\n // Add EuiTruncatePipe import to component files that need it\n for (const tsPath of filesNeedingTruncateImport) {\n const buffer = tree.read(tsPath);\n if (!buffer) continue;\n const source = buffer.toString('utf-8');\n if (source.includes(TRUNCATE_PIPE_IMPORT)) continue;\n const result = addTruncatePipeImport(source, tsPath);\n if (result !== source) {\n if (!options.dryRun) {\n tree.overwrite(tsPath, result);\n }\n }\n }\n\n context.logger.info(`Migrated eui-chip-list inputs/outputs to child eui-chip in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction addTruncatePipeImport(source: string, filePath: string): string {\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);\n const edits: Edit[] = [];\n\n // 1. Add ES import statement for EuiTruncatePipe\n let hasEsImport = false;\n let lastImportEnd = 0;\n\n for (const stmt of sourceFile.statements) {\n if (ts.isImportDeclaration(stmt)) {\n lastImportEnd = stmt.getEnd();\n const moduleSpec = (stmt.moduleSpecifier as ts.StringLiteral).text;\n if (moduleSpec === TRUNCATE_PIPE_PATH) {\n const namedBindings = stmt.importClause?.namedBindings;\n if (namedBindings && ts.isNamedImports(namedBindings)) {\n if (namedBindings.elements.some((el) => el.name.text === TRUNCATE_PIPE_IMPORT)) {\n hasEsImport = true;\n } else {\n // Add to existing import from same path\n const lastEl = namedBindings.elements[namedBindings.elements.length - 1];\n edits.push({ start: lastEl.getEnd(), end: lastEl.getEnd(), replacement: `, ${TRUNCATE_PIPE_IMPORT}` });\n hasEsImport = true;\n }\n }\n }\n }\n }\n\n if (!hasEsImport) {\n const importStatement = `\\nimport { ${TRUNCATE_PIPE_IMPORT} } from '${TRUNCATE_PIPE_PATH}';`;\n edits.push({ start: lastImportEnd, end: lastImportEnd, replacement: importStatement });\n }\n\n // 2. Add EuiTruncatePipe to @Component imports array\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node)) {\n const decs = ts.getDecorators(node);\n if (!decs) return;\n for (const dec of decs) {\n if (!ts.isCallExpression(dec.expression) || !ts.isIdentifier(dec.expression.expression) || dec.expression.expression.text !== 'Component') continue;\n const metadata = dec.expression.arguments[0];\n if (!ts.isObjectLiteralExpression(metadata)) continue;\n for (const prop of metadata.properties) {\n if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name) || prop.name.text !== 'imports') continue;\n if (!ts.isArrayLiteralExpression(prop.initializer)) continue;\n const arr = prop.initializer;\n const arrText = source.slice(arr.getStart(sourceFile), arr.getEnd());\n if (arrText.includes(TRUNCATE_PIPE_IMPORT)) continue;\n if (arr.elements.length > 0) {\n const lastElement = arr.elements[arr.elements.length - 1];\n edits.push({ start: lastElement.getEnd(), end: lastElement.getEnd(), replacement: `,\\n ${TRUNCATE_PIPE_IMPORT}` });\n } else {\n edits.push({ start: arr.getStart(sourceFile) + 1, end: arr.getEnd() - 1, replacement: TRUNCATE_PIPE_IMPORT });\n }\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: Edit[] = [];\n\n visitNodes(parsed.nodes, source, edits);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: Edit[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(CHIP_LIST_TAG) && !rawTemplate.includes(CHIP_LIST_ATTR)) {\n ts.forEachChild(node, visit);\n return;\n }\n const migrated = migrateTemplate(rawTemplate);\n if (migrated !== rawTemplate) changes.push({ start, end, replacement: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n return applyEdits(source, changes);\n}\n\nfunction visitNodes(nodes: TmplAstNode[], source: string, edits: Edit[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (isChipListElement(node)) {\n migrateChipListElement(node, source, edits);\n }\n visitNodes(node.children, source, edits);\n }\n }\n}\n\nfunction isChipListElement(element: TmplAstElement): boolean {\n if (element.name === CHIP_LIST_TAG) return true;\n return element.attributes.some((a) => a.name === CHIP_LIST_ATTR);\n}\n\nfunction isChipElement(element: TmplAstElement): boolean {\n if (element.name === CHIP_TAG) return true;\n return element.attributes.some((a) => a.name === CHIP_ATTR);\n}\n\nfunction findChildChips(nodes: TmplAstNode[]): TmplAstElement[] {\n const chips: TmplAstElement[] = [];\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (isChipElement(node)) {\n chips.push(node);\n } else {\n chips.push(...findChildChips(node.children));\n }\n }\n }\n return chips;\n}\n\nfunction migrateChipListElement(element: TmplAstElement, source: string, edits: Edit[]): void {\n const childChips = findChildChips(element.children);\n const insertions: string[] = [];\n let truncateValue: string | null = null;\n\n // Collect and remove propagated static attributes\n for (const attr of element.attributes) {\n if (PROPAGATED_INPUTS.has(attr.name)) {\n edits.push(removalEdit(attr, source));\n insertions.push(attr.value ? `${attr.name}=\"${attr.value}\"` : attr.name);\n }\n if (attr.name === 'isChipsRemovable') {\n edits.push(removalEdit(attr, source));\n insertions.push('isChipRemovable');\n }\n if (attr.name === 'chipsLabelTruncateCount') {\n edits.push(removalEdit(attr, source));\n truncateValue = attr.value || null;\n }\n if (REMOVED_INPUTS.has(attr.name)) {\n edits.push(removalEdit(attr, source));\n }\n }\n\n // Collect and remove propagated bound inputs\n for (const input of element.inputs) {\n if (PROPAGATED_INPUTS.has(input.name)) {\n edits.push(removalEdit(input, source));\n const raw = source.slice(input.sourceSpan.start.offset, input.sourceSpan.end.offset);\n insertions.push(raw);\n }\n if (input.name === 'isChipsRemovable') {\n edits.push(removalEdit(input, source));\n const valueText = extractBindingValue(input, source);\n insertions.push(`[isChipRemovable]=\"${valueText}\"`);\n }\n if (input.name === 'chipsLabelTruncateCount') {\n edits.push(removalEdit(input, source));\n truncateValue = extractBindingValue(input, source);\n }\n if (REMOVED_INPUTS.has(input.name)) {\n edits.push(removalEdit(input, source));\n }\n }\n\n // Collect and remove (chipRemove) output\n let chipRemoveHandler: string | null = null;\n for (const output of element.outputs) {\n if (output.name === 'chipRemove') {\n edits.push(removalEdit(output, source));\n chipRemoveHandler = extractHandlerExpression(output, source);\n }\n }\n\n if (chipRemoveHandler) {\n insertions.push(`(remove)=\"${chipRemoveHandler}\"`);\n }\n\n // Add collected attributes to each child eui-chip\n if (insertions.length > 0) {\n for (const chip of childChips) {\n const existingNames = getExistingAttrNames(chip);\n const toInsert = insertions.filter((ins) => {\n const name = extractAttrName(ins);\n return !existingNames.has(name);\n });\n if (toInsert.length > 0) {\n const insertPos = chip.startSourceSpan.end.offset - 1;\n edits.push({ start: insertPos, end: insertPos, replacement: ' ' + toInsert.join(' ') });\n }\n }\n }\n\n // Add euiTruncate pipe to chip label content\n if (truncateValue) {\n for (const chip of childChips) {\n const labelEdit = buildTruncatePipeEdit(chip, source, truncateValue);\n if (labelEdit) edits.push(labelEdit);\n }\n }\n}\n\nfunction buildTruncatePipeEdit(chip: TmplAstElement, source: string, truncateValue: string): Edit | null {\n // Find <span euiLabel>...</span> inside the chip\n const labelEl = findLabelElement(chip.children);\n if (labelEl && labelEl.endSourceSpan) {\n const contentStart = labelEl.startSourceSpan.end.offset;\n const contentEnd = labelEl.endSourceSpan.start.offset;\n const content = source.slice(contentStart, contentEnd);\n if (content && !content.includes('euiTruncate')) {\n const trimmed = content.trim();\n const interpMatch = trimmed.match(/^\\{\\{\\s*(.+?)\\s*\\}\\}$/);\n if (interpMatch) {\n return { start: contentStart, end: contentEnd, replacement: `{{ ${interpMatch[1]} | euiTruncate: ${truncateValue} }}` };\n }\n return { start: contentStart, end: contentEnd, replacement: `{{ '${trimmed}' | euiTruncate: ${truncateValue} }}` };\n }\n }\n\n // Check direct text content inside chip (no label element)\n if (chip.endSourceSpan) {\n const chipContentStart = chip.startSourceSpan.end.offset;\n const chipContentEnd = chip.endSourceSpan.start.offset;\n const chipContent = source.slice(chipContentStart, chipContentEnd);\n const trimmed = chipContent.trim();\n const interpMatch = trimmed.match(/^\\{\\{\\s*(.+?)\\s*\\}\\}$/);\n if (interpMatch && !chipContent.includes('euiTruncate')) {\n return { start: chipContentStart, end: chipContentEnd, replacement: `{{ ${interpMatch[1]} | euiTruncate: ${truncateValue} }}` };\n }\n }\n return null;\n}\n\nfunction findLabelElement(nodes: TmplAstNode[]): TmplAstElement | null {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.attributes.some((a) => a.name === 'euiLabel')) return node;\n const nested = findLabelElement(node.children);\n if (nested) return nested;\n }\n }\n return null;\n}\n\nfunction getExistingAttrNames(element: TmplAstElement): Set<string> {\n const names = new Set<string>();\n for (const attr of element.attributes) names.add(attr.name);\n for (const input of element.inputs) names.add(input.name);\n for (const output of element.outputs) names.add(output.name);\n return names;\n}\n\nfunction extractAttrName(insertion: string): string {\n const outputMatch = insertion.match(/^\\(([^)]+)\\)/);\n if (outputMatch) return outputMatch[1];\n const inputMatch = insertion.match(/^\\[([^\\]]+)\\]/);\n if (inputMatch) return inputMatch[1];\n return insertion.split('=')[0];\n}\n\nfunction removalEdit(node: TmplAstTextAttribute | TmplAstBoundAttribute | TmplAstBoundEvent, source: string): Edit {\n let start = node.sourceSpan.start.offset;\n while (start > 0 && (source[start - 1] === ' ' || source[start - 1] === '\\t')) {\n start--;\n }\n return { start, end: node.sourceSpan.end.offset, replacement: '' };\n}\n\nfunction extractBindingValue(input: TmplAstBoundAttribute, source: string): string {\n const raw = source.slice(input.sourceSpan.start.offset, input.sourceSpan.end.offset);\n const match = raw.match(/=[\"']([^\"']*)[\"']/);\n return match ? match[1] : 'true';\n}\n\nfunction extractHandlerExpression(output: TmplAstBoundEvent, source: string): string {\n const raw = source.slice(output.sourceSpan.start.offset, output.sourceSpan.end.offset);\n const match = raw.match(/=[\"']([^\"']*)[\"']/);\n return match ? match[1] : '';\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction applyEdits(source: string, edits: Edit[]): string {\n const unique = deduplicateEdits(edits);\n let result = source;\n for (const edit of unique.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n\nfunction deduplicateEdits(edits: Edit[]): Edit[] {\n const seen = new Map<string, Edit>();\n for (const edit of edits) {\n seen.set(`${edit.start}:${edit.end}`, edit);\n }\n return Array.from(seen.values());\n}\n",
2592
- "displayName": "Schema",
2593
- "properties": [
2594
- {
2595
- "name": "dryRun",
2466
+ "name": "useClassArray",
2596
2467
  "coverageIgnore": false,
2597
2468
  "deprecated": false,
2598
2469
  "deprecationMessage": "",
@@ -2600,19 +2471,7 @@
2600
2471
  "indexKey": "",
2601
2472
  "optional": true,
2602
2473
  "description": "",
2603
- "line": 28,
2604
- "rawdescription": "\n"
2605
- },
2606
- {
2607
- "name": "path",
2608
- "coverageIgnore": false,
2609
- "deprecated": false,
2610
- "deprecationMessage": "",
2611
- "type": "string",
2612
- "indexKey": "",
2613
- "optional": true,
2614
- "description": "",
2615
- "line": 27,
2474
+ "line": 10,
2616
2475
  "rawdescription": "\n"
2617
2476
  }
2618
2477
  ],
@@ -2621,8 +2480,8 @@
2621
2480
  "methods": [],
2622
2481
  "extends": [],
2623
2482
  "isDuplicate": true,
2624
- "duplicateId": 9,
2625
- "duplicateName": "Schema-9",
2483
+ "duplicateId": 6,
2484
+ "duplicateName": "Schema-6",
2626
2485
  "relationships": {
2627
2486
  "incoming": [],
2628
2487
  "outgoing": []
@@ -2630,12 +2489,12 @@
2630
2489
  },
2631
2490
  {
2632
2491
  "name": "Schema",
2633
- "id": "interface-Schema-869dfc324e9111966817cbebb3553eabfe200acfe33bb77efa71a6c46e1cba0ff5852de7b9ade3060f87264035b99591361331a9e0622daaac22b8d59146c761-10",
2634
- "file": "packages/core/schematics/migrate-eui-discussion-thread/index.ts",
2492
+ "id": "interface-Schema-c0c08f5e83da9afa13ce8375f982fb280b9bde854e00c914f41b04d44deecd531496c2cee264b50314e92a6260c26cebe35741a2cf5ec068d8b6da5c5267539e-7",
2493
+ "file": "packages/core/schematics/migrate-eui-button/index.ts",
2635
2494
  "deprecated": false,
2636
2495
  "deprecationMessage": "",
2637
2496
  "type": "interface",
2638
- "sourceCode": "import { parseTemplate, TmplAstBoundAttribute, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst COMPONENT_TAG = 'eui-discussion-thread';\n\nexport function migrateEuiDiscussionThread(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(COMPONENT_TAG)) return;\n\n const result = path.endsWith('.html')\n ? migrateTemplate(original)\n : migrateInlineTemplates(original);\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would remove [trackBy] binding in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n\n // Warn about TS usages inline\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts') && original.includes('trackByFn')) {\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === 'trackByFn') {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(`${path}:${line + 1} - \"trackByFn\" has been removed from ${COMPONENT_TAG}. Remove this reference manually.`);\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n });\n\n context.logger.info(`Removed trackByFn-based [trackBy] bindings from ${COMPONENT_TAG} in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const removals: { start: number; end: number }[] = [];\n\n visitNodes(parsed.nodes, source, removals);\n\n let result = source;\n for (const { start, end } of removals.sort((a, b) => b.start - a.start)) {\n let adjustedStart = start;\n while (adjustedStart > 0 && (result[adjustedStart - 1] === ' ' || result[adjustedStart - 1] === '\\t')) {\n adjustedStart--;\n }\n result = result.slice(0, adjustedStart) + result.slice(end);\n }\n\n return result;\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const templateRegex = /template\\s*:\\s*`([^`]*)`/gs;\n return source.replace(templateRegex, (match, templateContent: string) => {\n if (!templateContent.includes(COMPONENT_TAG)) return match;\n const migrated = migrateTemplate(templateContent);\n if (migrated === templateContent) return match;\n return match.replace(templateContent, migrated);\n });\n}\n\nfunction visitNodes(nodes: TmplAstNode[], source: string, removals: { start: number; end: number }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === COMPONENT_TAG) collectRemovals(node, source, removals);\n visitNodes(node.children, source, removals);\n }\n }\n}\n\nfunction collectRemovals(element: TmplAstElement, source: string, removals: { start: number; end: number }[]): void {\n for (const input of element.inputs) {\n if (input.name === 'trackBy') {\n const valueSource = source.slice(input.sourceSpan.start.offset, input.sourceSpan.end.offset);\n if (valueSource.includes('trackByFn')) {\n removals.push({ start: input.sourceSpan.start.offset, end: input.sourceSpan.end.offset });\n }\n }\n }\n}\n",
2497
+ "sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst OLD_NAME = 'euiButtonCall';\nconst NEW_NAME = 'euiCTAButton';\n\nexport function migrateEuiButton(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(OLD_NAME)) return;\n\n let result: string;\n\n if (path.endsWith('.html')) {\n result = migrateTemplate(original);\n } else {\n result = migrateInlineTemplates(original);\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would rename '${OLD_NAME}' → '${NEW_NAME}' in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n\n // Warn about TS property access usages\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts')) {\n if (!original.includes(OLD_NAME)) return;\n\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === OLD_NAME) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(`${path}:${line + 1} - \"${OLD_NAME}\" has been renamed to \"${NEW_NAME}\". Update this reference manually.`);\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n });\n\n context.logger.info(`Renamed '${OLD_NAME}' → '${NEW_NAME}' on elements with euiButton in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: { start: number; end: number; replacement: string }[] = [];\n\n visitNodes(parsed.nodes, edits);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: { start: number; end: number; text: string }[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(OLD_NAME)) {\n ts.forEachChild(node, visit); return; \n}\n const migrated = migrateTemplate(rawTemplate);\n if (migrated !== rawTemplate) changes.push({ start, end, text: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n let result = source;\n for (const change of changes.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, change.start) + change.text + result.slice(change.end);\n }\n return result;\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction hasEuiButtonAttribute(element: TmplAstElement): boolean {\n return element.attributes.some((a) => a.name === 'euiButton') ||\n element.inputs.some((i) => i.name === 'euiButton');\n}\n\nfunction visitNodes(nodes: TmplAstNode[], edits: { start: number; end: number; replacement: string }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (hasEuiButtonAttribute(node)) collectRenames(node, edits);\n visitNodes(node.children, edits);\n }\n }\n}\n\nfunction collectRenames(element: TmplAstElement, edits: { start: number; end: number; replacement: string }[]): void {\n for (const attr of element.attributes) {\n if (attr.name === OLD_NAME) {\n edits.push({ start: attr.keySpan!.start.offset, end: attr.keySpan!.end.offset, replacement: NEW_NAME });\n }\n }\n for (const input of element.inputs) {\n if (input.name === OLD_NAME) {\n edits.push({ start: input.keySpan!.start.offset, end: input.keySpan!.end.offset, replacement: NEW_NAME });\n }\n }\n}\n\nfunction applyEdits(source: string, edits: { start: number; end: number; replacement: string }[]): string {\n let result = source;\n for (const edit of edits.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n",
2639
2498
  "displayName": "Schema",
2640
2499
  "properties": [
2641
2500
  {
@@ -2668,55 +2527,8 @@
2668
2527
  "methods": [],
2669
2528
  "extends": [],
2670
2529
  "isDuplicate": true,
2671
- "duplicateId": 10,
2672
- "duplicateName": "Schema-10",
2673
- "relationships": {
2674
- "incoming": [],
2675
- "outgoing": []
2676
- }
2677
- },
2678
- {
2679
- "name": "Schema",
2680
- "id": "interface-Schema-375dc0924084a2acbafe4a6a32577d59f631c9a386d151180d8fb1c89e7e7cd23da9fd459e597592ed823692adb6ad2633c50baf16621f003246e8c9bb1c6ce0-11",
2681
- "file": "packages/core/schematics/migrate-eui-editor/index.ts",
2682
- "deprecated": false,
2683
- "deprecationMessage": "",
2684
- "type": "interface",
2685
- "sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\nconst COMPONENT_TAG = 'eui-editor';\nconst OLD_NAME = 'onEditorChanged';\nconst NEW_NAME = 'contentChange';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nexport function migrateEuiEditor(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n\n if (path.endsWith('.html')) {\n if (!original.includes(COMPONENT_TAG)) return;\n const result = migrateTemplate(original);\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would rename '${OLD_NAME}' → '${NEW_NAME}' in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n return;\n }\n\n // .ts file — handle both migration and warnings in one pass\n const hasTag = original.includes(COMPONENT_TAG);\n const hasOldName = original.includes(OLD_NAME);\n if (!hasTag && !hasOldName) return;\n\n if (hasTag) {\n const result = migrateInlineTemplates(original);\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would rename '${OLD_NAME}' → '${NEW_NAME}' in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n }\n\n // Warn about TS property access usages (skip spec files)\n if (hasOldName && !path.endsWith('.spec.ts')) {\n warnPropertyAccesses(path, original, context);\n }\n });\n\n context.logger.info(`Renamed '(${OLD_NAME})' → '(${NEW_NAME})' on ${COMPONENT_TAG} in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: { start: number; end: number; replacement: string }[] = [];\n\n visitNodes(parsed.nodes, edits);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: { start: number; end: number; text: string }[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(COMPONENT_TAG)) {\n ts.forEachChild(node, visit); return;\n}\n const migrated = migrateTemplate(rawTemplate);\n if (migrated !== rawTemplate) changes.push({ start, end, text: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n let result = source;\n for (const change of changes.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, change.start) + change.text + result.slice(change.end);\n }\n return result;\n}\n\nfunction warnPropertyAccesses(path: string, source: string, context: SchematicContext): void {\n const sourceFile = ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === OLD_NAME) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(`${path}:${line + 1} - \"${OLD_NAME}\" has been renamed to \"${NEW_NAME}\" on ${COMPONENT_TAG}. Update this reference manually.`);\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction visitNodes(nodes: TmplAstNode[], edits: { start: number; end: number; replacement: string }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === COMPONENT_TAG) collectRenames(node, edits);\n visitNodes(node.children, edits);\n }\n }\n}\n\nfunction collectRenames(element: TmplAstElement, edits: { start: number; end: number; replacement: string }[]): void {\n for (const output of element.outputs) {\n if (output.name === OLD_NAME) {\n edits.push({ start: output.keySpan!.start.offset, end: output.keySpan!.end.offset, replacement: NEW_NAME });\n }\n }\n}\n\nfunction applyEdits(source: string, edits: { start: number; end: number; replacement: string }[]): string {\n let result = source;\n for (const edit of edits.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n",
2686
- "displayName": "Schema",
2687
- "properties": [
2688
- {
2689
- "name": "dryRun",
2690
- "coverageIgnore": false,
2691
- "deprecated": false,
2692
- "deprecationMessage": "",
2693
- "type": "boolean",
2694
- "indexKey": "",
2695
- "optional": true,
2696
- "description": "",
2697
- "line": 12,
2698
- "rawdescription": "\n"
2699
- },
2700
- {
2701
- "name": "path",
2702
- "coverageIgnore": false,
2703
- "deprecated": false,
2704
- "deprecationMessage": "",
2705
- "type": "string",
2706
- "indexKey": "",
2707
- "optional": true,
2708
- "description": "",
2709
- "line": 11,
2710
- "rawdescription": "\n"
2711
- }
2712
- ],
2713
- "indexSignatures": [],
2714
- "kind": 172,
2715
- "methods": [],
2716
- "extends": [],
2717
- "isDuplicate": true,
2718
- "duplicateId": 11,
2719
- "duplicateName": "Schema-11",
2530
+ "duplicateId": 7,
2531
+ "duplicateName": "Schema-7",
2720
2532
  "relationships": {
2721
2533
  "incoming": [],
2722
2534
  "outgoing": []
@@ -2724,12 +2536,200 @@
2724
2536
  },
2725
2537
  {
2726
2538
  "name": "Schema",
2727
- "id": "interface-Schema-0ee574e11dd651dd971057cb66220845e5b87b2949c69c623c8ddf7355af92a396c58c237a52a05f18c22a5332a2695b717bcfeb8ea840c4e7df8d0084c2b49c-12",
2728
- "file": "packages/core/schematics/migrate-eui-fieldset/index.ts",
2539
+ "id": "interface-Schema-39d3d52e788050bebc4e0b99ed0756f7e9727556cb090ae8796bd4261a56463fe1f479c322b23028cd4134d71e4d1dc44cd248ac62a9a148b5fc46a665466f7c-8",
2540
+ "file": "packages/core/schematics/migrate-eui-chip/index.ts",
2729
2541
  "deprecated": false,
2730
2542
  "deprecationMessage": "",
2731
2543
  "type": "interface",
2732
- "sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst COMPONENT_TAG = 'eui-fieldset';\nconst OLD_NAME = 'iconSvgType';\nconst NEW_NAME = 'iconSvgName';\n\nexport function migrateEuiFieldset(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(COMPONENT_TAG)) return;\n\n let result: string;\n\n if (path.endsWith('.html')) {\n result = migrateTemplate(original);\n } else {\n result = migrateInlineTemplates(original);\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would rename '${OLD_NAME}' → '${NEW_NAME}' in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n\n // Warn about TS property access usages (merged from warnTsUsages)\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts') && original.includes(OLD_NAME)) {\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === OLD_NAME) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(`${path}:${line + 1} - \"${OLD_NAME}\" has been renamed to \"${NEW_NAME}\" on ${COMPONENT_TAG}. Update this reference manually.`);\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n });\n\n context.logger.info(`Renamed '${OLD_NAME}' → '${NEW_NAME}' on ${COMPONENT_TAG} in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: { start: number; end: number; replacement: string }[] = [];\n\n visitNodes(parsed.nodes, edits);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: { start: number; end: number; text: string }[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(COMPONENT_TAG)) {\n ts.forEachChild(node, visit); return; \n}\n const migrated = migrateTemplate(rawTemplate);\n if (migrated !== rawTemplate) changes.push({ start, end, text: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n let result = source;\n for (const change of changes.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, change.start) + change.text + result.slice(change.end);\n }\n return result;\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction visitNodes(nodes: TmplAstNode[], edits: { start: number; end: number; replacement: string }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === COMPONENT_TAG) collectRenames(node, edits);\n visitNodes(node.children, edits);\n }\n }\n}\n\nfunction collectRenames(element: TmplAstElement, edits: { start: number; end: number; replacement: string }[]): void {\n for (const attr of element.attributes) {\n if (attr.name === OLD_NAME) {\n edits.push({ start: attr.keySpan!.start.offset, end: attr.keySpan!.end.offset, replacement: NEW_NAME });\n }\n }\n for (const input of element.inputs) {\n if (input.name === OLD_NAME) {\n edits.push({ start: input.keySpan!.start.offset, end: input.keySpan!.end.offset, replacement: NEW_NAME });\n }\n }\n}\n\nfunction applyEdits(source: string, edits: { start: number; end: number; replacement: string }[]): string {\n let result = source;\n for (const edit of edits.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n",
2544
+ "sourceCode": "import { parseTemplate, TmplAstBoundAttribute, TmplAstElement, TmplAstNode, TmplAstTextAttribute } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst REMOVED_INPUTS = new Set(['isSquared']);\n\nexport function migrateEuiChip(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes('eui-chip') && !original.includes('euiChip')) return;\n\n const result = path.endsWith('.html')\n ? migrateTemplate(original)\n : migrateInlineTemplates(original);\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would remove 'isSquared' input in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n\n // Warn about TS usages inline\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts') && original.includes('isSquared')) {\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === 'isSquared') {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(`${path}:${line + 1} - \"isSquared\" is no longer a valid input on eui-chip. Remove this assignment.`);\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n });\n\n context.logger.info(`Removed deprecated eui-chip 'isSquared' input from ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const removals: { start: number; end: number }[] = [];\n\n visitNodes(parsed.nodes, removals);\n\n let result = source;\n for (const { start, end } of removals.sort((a, b) => b.start - a.start)) {\n let adjustedStart = start;\n while (adjustedStart > 0 && (result[adjustedStart - 1] === ' ' || result[adjustedStart - 1] === '\\t')) {\n adjustedStart--;\n }\n result = result.slice(0, adjustedStart) + result.slice(end);\n }\n\n return result;\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const templateRegex = /template\\s*:\\s*`([^`]*)`/gs;\n return source.replace(templateRegex, (match, templateContent: string) => {\n if (!templateContent.includes('eui-chip') && !templateContent.includes('euiChip')) return match;\n const migrated = migrateTemplate(templateContent);\n if (migrated === templateContent) return match;\n return match.replace(templateContent, migrated);\n });\n}\n\nfunction isChipElement(element: TmplAstElement): boolean {\n if (element.name === 'eui-chip') return true;\n return element.attributes.some((a) => a.name === 'euiChip');\n}\n\nfunction visitNodes(nodes: TmplAstNode[], removals: { start: number; end: number }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (isChipElement(node)) collectRemovals(node, removals);\n visitNodes(node.children, removals);\n }\n }\n}\n\nfunction collectRemovals(element: TmplAstElement, removals: { start: number; end: number }[]): void {\n for (const attr of element.attributes) {\n if (REMOVED_INPUTS.has(attr.name)) {\n removals.push(getAttributeSpan(attr));\n }\n }\n for (const input of element.inputs) {\n if (REMOVED_INPUTS.has(input.name)) {\n removals.push(getAttributeSpan(input));\n }\n }\n}\n\nfunction getAttributeSpan(attr: TmplAstTextAttribute | TmplAstBoundAttribute): { start: number; end: number } {\n return { start: attr.sourceSpan.start.offset, end: attr.sourceSpan.end.offset };\n}\n",
2545
+ "displayName": "Schema",
2546
+ "properties": [
2547
+ {
2548
+ "name": "dryRun",
2549
+ "coverageIgnore": false,
2550
+ "deprecated": false,
2551
+ "deprecationMessage": "",
2552
+ "type": "boolean",
2553
+ "indexKey": "",
2554
+ "optional": true,
2555
+ "description": "",
2556
+ "line": 8,
2557
+ "rawdescription": "\n"
2558
+ },
2559
+ {
2560
+ "name": "path",
2561
+ "coverageIgnore": false,
2562
+ "deprecated": false,
2563
+ "deprecationMessage": "",
2564
+ "type": "string",
2565
+ "indexKey": "",
2566
+ "optional": true,
2567
+ "description": "",
2568
+ "line": 7,
2569
+ "rawdescription": "\n"
2570
+ }
2571
+ ],
2572
+ "indexSignatures": [],
2573
+ "kind": 172,
2574
+ "methods": [],
2575
+ "extends": [],
2576
+ "isDuplicate": true,
2577
+ "duplicateId": 8,
2578
+ "duplicateName": "Schema-8",
2579
+ "relationships": {
2580
+ "incoming": [],
2581
+ "outgoing": []
2582
+ }
2583
+ },
2584
+ {
2585
+ "name": "Schema",
2586
+ "id": "interface-Schema-311bef7d5e4b40b356b0fdb5e35dd89e48a5c5beec29eeb865c22c2ca1c4c4fb1fd0bb4391ed0b33825a6d1d744bc3a2e9822b347fbc686a537e15b170c279dd-9",
2587
+ "file": "packages/core/schematics/migrate-eui-chip-list/index.ts",
2588
+ "deprecated": false,
2589
+ "deprecationMessage": "",
2590
+ "type": "interface",
2591
+ "sourceCode": "import { parseTemplate, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstElement, TmplAstNode, TmplAstTextAttribute } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\nconst CHIP_LIST_TAG = 'eui-chip-list';\nconst CHIP_LIST_ATTR = 'euiChipList';\nconst CHIP_TAG = 'eui-chip';\nconst CHIP_ATTR = 'euiChip';\n\nconst PROPAGATED_INPUTS = new Set([\n 'euiPrimary', 'euiSecondary', 'euiSuccess', 'euiInfo', 'euiWarning',\n 'euiDanger', 'euiAccent', 'euiVariant', 'euiSizeS', 'euiSizeVariant',\n 'euiOutline', 'euiDisabled',\n]);\n\nconst WARN_PROPERTIES = new Set([...PROPAGATED_INPUTS, 'chipRemove', 'isChipsRemovable', 'chipsLabelTruncateCount',\n 'maxVisibleChipsCount', 'isMaxVisibleChipsOpened', 'toggleLinkMoreLabel', 'toggleLinkLessLabel',\n 'isChipsSorted', 'chipsSortOrder']);\n\nconst REMOVED_INPUTS = new Set(['maxVisibleChipsCount', 'isMaxVisibleChipsOpened', 'toggleLinkMoreLabel', 'toggleLinkLessLabel', 'isChipsSorted', 'chipsSortOrder']);\n\nconst TRUNCATE_PIPE_IMPORT = 'EuiTruncatePipe';\nconst TRUNCATE_PIPE_PATH = '@eui/components/pipes';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\ninterface Edit {\n start: number;\n end: number;\n replacement: string;\n}\n\nexport function migrateEuiChipList(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n const filesNeedingTruncateImport = new Set<string>();\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(CHIP_LIST_TAG) && !original.includes(CHIP_LIST_ATTR)) return;\n\n let result: string;\n let addedTruncate = false;\n\n if (path.endsWith('.html')) {\n result = migrateTemplate(original);\n if (result !== original && result.includes('euiTruncate') && !original.includes('euiTruncate')) {\n // Find the associated .ts file\n const tsPath = path.replace(/\\.html$/, '.ts');\n if (tree.exists(tsPath)) {\n filesNeedingTruncateImport.add(tsPath);\n } else {\n // Try component naming convention\n const componentTsPath = path.replace(/\\.html$/, '.component.ts');\n if (tree.exists(componentTsPath)) {\n filesNeedingTruncateImport.add(componentTsPath);\n }\n }\n }\n } else {\n result = migrateInlineTemplates(original);\n if (result !== original && result.includes('euiTruncate') && !original.includes('euiTruncate')) {\n addedTruncate = true;\n }\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would move variant/size/outline inputs to child eui-chip in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n\n if (addedTruncate) {\n filesNeedingTruncateImport.add(path);\n }\n\n // Warn about TS usages of removed properties\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts')) {\n if ([...WARN_PROPERTIES].some((p) => original.includes(p))) {\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && WARN_PROPERTIES.has(node.name.text)) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${path}:${line + 1} - \"${node.name.text}\" has been removed from eui-chip-list. Move it to individual eui-chip elements.`,\n );\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n }\n });\n\n // Add EuiTruncatePipe import to component files that need it\n for (const tsPath of filesNeedingTruncateImport) {\n const buffer = tree.read(tsPath);\n if (!buffer) continue;\n const source = buffer.toString('utf-8');\n if (source.includes(TRUNCATE_PIPE_IMPORT)) continue;\n const result = addTruncatePipeImport(source, tsPath);\n if (result !== source) {\n if (!options.dryRun) {\n tree.overwrite(tsPath, result);\n }\n }\n }\n\n context.logger.info(`Migrated eui-chip-list inputs/outputs to child eui-chip in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction addTruncatePipeImport(source: string, filePath: string): string {\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);\n const edits: Edit[] = [];\n\n // 1. Add ES import statement for EuiTruncatePipe\n let hasEsImport = false;\n let lastImportEnd = 0;\n\n for (const stmt of sourceFile.statements) {\n if (ts.isImportDeclaration(stmt)) {\n lastImportEnd = stmt.getEnd();\n const moduleSpec = (stmt.moduleSpecifier as ts.StringLiteral).text;\n if (moduleSpec === TRUNCATE_PIPE_PATH) {\n const namedBindings = stmt.importClause?.namedBindings;\n if (namedBindings && ts.isNamedImports(namedBindings)) {\n if (namedBindings.elements.some((el) => el.name.text === TRUNCATE_PIPE_IMPORT)) {\n hasEsImport = true;\n } else {\n // Add to existing import from same path\n const lastEl = namedBindings.elements[namedBindings.elements.length - 1];\n edits.push({ start: lastEl.getEnd(), end: lastEl.getEnd(), replacement: `, ${TRUNCATE_PIPE_IMPORT}` });\n hasEsImport = true;\n }\n }\n }\n }\n }\n\n if (!hasEsImport) {\n const importStatement = `\\nimport { ${TRUNCATE_PIPE_IMPORT} } from '${TRUNCATE_PIPE_PATH}';`;\n edits.push({ start: lastImportEnd, end: lastImportEnd, replacement: importStatement });\n }\n\n // 2. Add EuiTruncatePipe to @Component imports array\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node)) {\n const decs = ts.getDecorators(node);\n if (!decs) return;\n for (const dec of decs) {\n if (!ts.isCallExpression(dec.expression) || !ts.isIdentifier(dec.expression.expression) || dec.expression.expression.text !== 'Component') continue;\n const metadata = dec.expression.arguments[0];\n if (!ts.isObjectLiteralExpression(metadata)) continue;\n for (const prop of metadata.properties) {\n if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name) || prop.name.text !== 'imports') continue;\n if (!ts.isArrayLiteralExpression(prop.initializer)) continue;\n const arr = prop.initializer;\n const arrText = source.slice(arr.getStart(sourceFile), arr.getEnd());\n if (arrText.includes(TRUNCATE_PIPE_IMPORT)) continue;\n if (arr.elements.length > 0) {\n const lastElement = arr.elements[arr.elements.length - 1];\n edits.push({ start: lastElement.getEnd(), end: lastElement.getEnd(), replacement: `,\\n ${TRUNCATE_PIPE_IMPORT}` });\n } else {\n edits.push({ start: arr.getStart(sourceFile) + 1, end: arr.getEnd() - 1, replacement: TRUNCATE_PIPE_IMPORT });\n }\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: Edit[] = [];\n\n visitNodes(parsed.nodes, source, edits);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: Edit[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(CHIP_LIST_TAG) && !rawTemplate.includes(CHIP_LIST_ATTR)) {\n ts.forEachChild(node, visit);\n return;\n }\n const migrated = migrateTemplate(rawTemplate);\n if (migrated !== rawTemplate) changes.push({ start, end, replacement: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n return applyEdits(source, changes);\n}\n\nfunction visitNodes(nodes: TmplAstNode[], source: string, edits: Edit[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (isChipListElement(node)) {\n migrateChipListElement(node, source, edits);\n }\n visitNodes(node.children, source, edits);\n }\n }\n}\n\nfunction isChipListElement(element: TmplAstElement): boolean {\n if (element.name === CHIP_LIST_TAG) return true;\n return element.attributes.some((a) => a.name === CHIP_LIST_ATTR);\n}\n\nfunction isChipElement(element: TmplAstElement): boolean {\n if (element.name === CHIP_TAG) return true;\n return element.attributes.some((a) => a.name === CHIP_ATTR);\n}\n\nfunction findChildChips(nodes: TmplAstNode[]): TmplAstElement[] {\n const chips: TmplAstElement[] = [];\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (isChipElement(node)) {\n chips.push(node);\n } else {\n chips.push(...findChildChips(node.children));\n }\n }\n }\n return chips;\n}\n\nfunction migrateChipListElement(element: TmplAstElement, source: string, edits: Edit[]): void {\n const childChips = findChildChips(element.children);\n const insertions: string[] = [];\n let truncateValue: string | null = null;\n\n // Collect and remove propagated static attributes\n for (const attr of element.attributes) {\n if (PROPAGATED_INPUTS.has(attr.name)) {\n edits.push(removalEdit(attr, source));\n insertions.push(attr.value ? `${attr.name}=\"${attr.value}\"` : attr.name);\n }\n if (attr.name === 'isChipsRemovable') {\n edits.push(removalEdit(attr, source));\n insertions.push('isChipRemovable');\n }\n if (attr.name === 'chipsLabelTruncateCount') {\n edits.push(removalEdit(attr, source));\n truncateValue = attr.value || null;\n }\n if (REMOVED_INPUTS.has(attr.name)) {\n edits.push(removalEdit(attr, source));\n }\n }\n\n // Collect and remove propagated bound inputs\n for (const input of element.inputs) {\n if (PROPAGATED_INPUTS.has(input.name)) {\n edits.push(removalEdit(input, source));\n const raw = source.slice(input.sourceSpan.start.offset, input.sourceSpan.end.offset);\n insertions.push(raw);\n }\n if (input.name === 'isChipsRemovable') {\n edits.push(removalEdit(input, source));\n const valueText = extractBindingValue(input, source);\n insertions.push(`[isChipRemovable]=\"${valueText}\"`);\n }\n if (input.name === 'chipsLabelTruncateCount') {\n edits.push(removalEdit(input, source));\n truncateValue = extractBindingValue(input, source);\n }\n if (REMOVED_INPUTS.has(input.name)) {\n edits.push(removalEdit(input, source));\n }\n }\n\n // Collect and remove (chipRemove) output\n let chipRemoveHandler: string | null = null;\n for (const output of element.outputs) {\n if (output.name === 'chipRemove') {\n edits.push(removalEdit(output, source));\n chipRemoveHandler = extractHandlerExpression(output, source);\n }\n }\n\n if (chipRemoveHandler) {\n insertions.push(`(remove)=\"${chipRemoveHandler}\"`);\n }\n\n // Add collected attributes to each child eui-chip\n if (insertions.length > 0) {\n for (const chip of childChips) {\n const existingNames = getExistingAttrNames(chip);\n const toInsert = insertions.filter((ins) => {\n const name = extractAttrName(ins);\n return !existingNames.has(name);\n });\n if (toInsert.length > 0) {\n const insertPos = chip.startSourceSpan.end.offset - 1;\n edits.push({ start: insertPos, end: insertPos, replacement: ' ' + toInsert.join(' ') });\n }\n }\n }\n\n // Add euiTruncate pipe to chip label content\n if (truncateValue) {\n for (const chip of childChips) {\n const labelEdit = buildTruncatePipeEdit(chip, source, truncateValue);\n if (labelEdit) edits.push(labelEdit);\n }\n }\n}\n\nfunction buildTruncatePipeEdit(chip: TmplAstElement, source: string, truncateValue: string): Edit | null {\n // Find <span euiLabel>...</span> inside the chip\n const labelEl = findLabelElement(chip.children);\n if (labelEl && labelEl.endSourceSpan) {\n const contentStart = labelEl.startSourceSpan.end.offset;\n const contentEnd = labelEl.endSourceSpan.start.offset;\n const content = source.slice(contentStart, contentEnd);\n if (content && !content.includes('euiTruncate')) {\n const trimmed = content.trim();\n const interpMatch = trimmed.match(/^\\{\\{\\s*(.+?)\\s*\\}\\}$/);\n if (interpMatch) {\n return { start: contentStart, end: contentEnd, replacement: `{{ ${interpMatch[1]} | euiTruncate: ${truncateValue} }}` };\n }\n return { start: contentStart, end: contentEnd, replacement: `{{ '${trimmed}' | euiTruncate: ${truncateValue} }}` };\n }\n }\n\n // Check direct text content inside chip (no label element)\n if (chip.endSourceSpan) {\n const chipContentStart = chip.startSourceSpan.end.offset;\n const chipContentEnd = chip.endSourceSpan.start.offset;\n const chipContent = source.slice(chipContentStart, chipContentEnd);\n const trimmed = chipContent.trim();\n const interpMatch = trimmed.match(/^\\{\\{\\s*(.+?)\\s*\\}\\}$/);\n if (interpMatch && !chipContent.includes('euiTruncate')) {\n return { start: chipContentStart, end: chipContentEnd, replacement: `{{ ${interpMatch[1]} | euiTruncate: ${truncateValue} }}` };\n }\n }\n return null;\n}\n\nfunction findLabelElement(nodes: TmplAstNode[]): TmplAstElement | null {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.attributes.some((a) => a.name === 'euiLabel')) return node;\n const nested = findLabelElement(node.children);\n if (nested) return nested;\n }\n }\n return null;\n}\n\nfunction getExistingAttrNames(element: TmplAstElement): Set<string> {\n const names = new Set<string>();\n for (const attr of element.attributes) names.add(attr.name);\n for (const input of element.inputs) names.add(input.name);\n for (const output of element.outputs) names.add(output.name);\n return names;\n}\n\nfunction extractAttrName(insertion: string): string {\n const outputMatch = insertion.match(/^\\(([^)]+)\\)/);\n if (outputMatch) return outputMatch[1];\n const inputMatch = insertion.match(/^\\[([^\\]]+)\\]/);\n if (inputMatch) return inputMatch[1];\n return insertion.split('=')[0];\n}\n\nfunction removalEdit(node: TmplAstTextAttribute | TmplAstBoundAttribute | TmplAstBoundEvent, source: string): Edit {\n let start = node.sourceSpan.start.offset;\n while (start > 0 && (source[start - 1] === ' ' || source[start - 1] === '\\t')) {\n start--;\n }\n return { start, end: node.sourceSpan.end.offset, replacement: '' };\n}\n\nfunction extractBindingValue(input: TmplAstBoundAttribute, source: string): string {\n const raw = source.slice(input.sourceSpan.start.offset, input.sourceSpan.end.offset);\n const match = raw.match(/=[\"']([^\"']*)[\"']/);\n return match ? match[1] : 'true';\n}\n\nfunction extractHandlerExpression(output: TmplAstBoundEvent, source: string): string {\n const raw = source.slice(output.sourceSpan.start.offset, output.sourceSpan.end.offset);\n const match = raw.match(/=[\"']([^\"']*)[\"']/);\n return match ? match[1] : '';\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction applyEdits(source: string, edits: Edit[]): string {\n const unique = deduplicateEdits(edits);\n let result = source;\n for (const edit of unique.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n\nfunction deduplicateEdits(edits: Edit[]): Edit[] {\n const seen = new Map<string, Edit>();\n for (const edit of edits) {\n seen.set(`${edit.start}:${edit.end}`, edit);\n }\n return Array.from(seen.values());\n}\n",
2592
+ "displayName": "Schema",
2593
+ "properties": [
2594
+ {
2595
+ "name": "dryRun",
2596
+ "coverageIgnore": false,
2597
+ "deprecated": false,
2598
+ "deprecationMessage": "",
2599
+ "type": "boolean",
2600
+ "indexKey": "",
2601
+ "optional": true,
2602
+ "description": "",
2603
+ "line": 28,
2604
+ "rawdescription": "\n"
2605
+ },
2606
+ {
2607
+ "name": "path",
2608
+ "coverageIgnore": false,
2609
+ "deprecated": false,
2610
+ "deprecationMessage": "",
2611
+ "type": "string",
2612
+ "indexKey": "",
2613
+ "optional": true,
2614
+ "description": "",
2615
+ "line": 27,
2616
+ "rawdescription": "\n"
2617
+ }
2618
+ ],
2619
+ "indexSignatures": [],
2620
+ "kind": 172,
2621
+ "methods": [],
2622
+ "extends": [],
2623
+ "isDuplicate": true,
2624
+ "duplicateId": 9,
2625
+ "duplicateName": "Schema-9",
2626
+ "relationships": {
2627
+ "incoming": [],
2628
+ "outgoing": []
2629
+ }
2630
+ },
2631
+ {
2632
+ "name": "Schema",
2633
+ "id": "interface-Schema-869dfc324e9111966817cbebb3553eabfe200acfe33bb77efa71a6c46e1cba0ff5852de7b9ade3060f87264035b99591361331a9e0622daaac22b8d59146c761-10",
2634
+ "file": "packages/core/schematics/migrate-eui-discussion-thread/index.ts",
2635
+ "deprecated": false,
2636
+ "deprecationMessage": "",
2637
+ "type": "interface",
2638
+ "sourceCode": "import { parseTemplate, TmplAstBoundAttribute, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst COMPONENT_TAG = 'eui-discussion-thread';\n\nexport function migrateEuiDiscussionThread(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(COMPONENT_TAG)) return;\n\n const result = path.endsWith('.html')\n ? migrateTemplate(original)\n : migrateInlineTemplates(original);\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would remove [trackBy] binding in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n\n // Warn about TS usages inline\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts') && original.includes('trackByFn')) {\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === 'trackByFn') {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(`${path}:${line + 1} - \"trackByFn\" has been removed from ${COMPONENT_TAG}. Remove this reference manually.`);\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n });\n\n context.logger.info(`Removed trackByFn-based [trackBy] bindings from ${COMPONENT_TAG} in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const removals: { start: number; end: number }[] = [];\n\n visitNodes(parsed.nodes, source, removals);\n\n let result = source;\n for (const { start, end } of removals.sort((a, b) => b.start - a.start)) {\n let adjustedStart = start;\n while (adjustedStart > 0 && (result[adjustedStart - 1] === ' ' || result[adjustedStart - 1] === '\\t')) {\n adjustedStart--;\n }\n result = result.slice(0, adjustedStart) + result.slice(end);\n }\n\n return result;\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const templateRegex = /template\\s*:\\s*`([^`]*)`/gs;\n return source.replace(templateRegex, (match, templateContent: string) => {\n if (!templateContent.includes(COMPONENT_TAG)) return match;\n const migrated = migrateTemplate(templateContent);\n if (migrated === templateContent) return match;\n return match.replace(templateContent, migrated);\n });\n}\n\nfunction visitNodes(nodes: TmplAstNode[], source: string, removals: { start: number; end: number }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === COMPONENT_TAG) collectRemovals(node, source, removals);\n visitNodes(node.children, source, removals);\n }\n }\n}\n\nfunction collectRemovals(element: TmplAstElement, source: string, removals: { start: number; end: number }[]): void {\n for (const input of element.inputs) {\n if (input.name === 'trackBy') {\n const valueSource = source.slice(input.sourceSpan.start.offset, input.sourceSpan.end.offset);\n if (valueSource.includes('trackByFn')) {\n removals.push({ start: input.sourceSpan.start.offset, end: input.sourceSpan.end.offset });\n }\n }\n }\n}\n",
2639
+ "displayName": "Schema",
2640
+ "properties": [
2641
+ {
2642
+ "name": "dryRun",
2643
+ "coverageIgnore": false,
2644
+ "deprecated": false,
2645
+ "deprecationMessage": "",
2646
+ "type": "boolean",
2647
+ "indexKey": "",
2648
+ "optional": true,
2649
+ "description": "",
2650
+ "line": 8,
2651
+ "rawdescription": "\n"
2652
+ },
2653
+ {
2654
+ "name": "path",
2655
+ "coverageIgnore": false,
2656
+ "deprecated": false,
2657
+ "deprecationMessage": "",
2658
+ "type": "string",
2659
+ "indexKey": "",
2660
+ "optional": true,
2661
+ "description": "",
2662
+ "line": 7,
2663
+ "rawdescription": "\n"
2664
+ }
2665
+ ],
2666
+ "indexSignatures": [],
2667
+ "kind": 172,
2668
+ "methods": [],
2669
+ "extends": [],
2670
+ "isDuplicate": true,
2671
+ "duplicateId": 10,
2672
+ "duplicateName": "Schema-10",
2673
+ "relationships": {
2674
+ "incoming": [],
2675
+ "outgoing": []
2676
+ }
2677
+ },
2678
+ {
2679
+ "name": "Schema",
2680
+ "id": "interface-Schema-375dc0924084a2acbafe4a6a32577d59f631c9a386d151180d8fb1c89e7e7cd23da9fd459e597592ed823692adb6ad2633c50baf16621f003246e8c9bb1c6ce0-11",
2681
+ "file": "packages/core/schematics/migrate-eui-editor/index.ts",
2682
+ "deprecated": false,
2683
+ "deprecationMessage": "",
2684
+ "type": "interface",
2685
+ "sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\nconst COMPONENT_TAG = 'eui-editor';\nconst OLD_NAME = 'onEditorChanged';\nconst NEW_NAME = 'contentChange';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nexport function migrateEuiEditor(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n\n if (path.endsWith('.html')) {\n if (!original.includes(COMPONENT_TAG)) return;\n const result = migrateTemplate(original);\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would rename '${OLD_NAME}' → '${NEW_NAME}' in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n return;\n }\n\n // .ts file — handle both migration and warnings in one pass\n const hasTag = original.includes(COMPONENT_TAG);\n const hasOldName = original.includes(OLD_NAME);\n if (!hasTag && !hasOldName) return;\n\n if (hasTag) {\n const result = migrateInlineTemplates(original);\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would rename '${OLD_NAME}' → '${NEW_NAME}' in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n }\n\n // Warn about TS property access usages (skip spec files)\n if (hasOldName && !path.endsWith('.spec.ts')) {\n warnPropertyAccesses(path, original, context);\n }\n });\n\n context.logger.info(`Renamed '(${OLD_NAME})' → '(${NEW_NAME})' on ${COMPONENT_TAG} in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: { start: number; end: number; replacement: string }[] = [];\n\n visitNodes(parsed.nodes, edits);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: { start: number; end: number; text: string }[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(COMPONENT_TAG)) {\n ts.forEachChild(node, visit); return;\n}\n const migrated = migrateTemplate(rawTemplate);\n if (migrated !== rawTemplate) changes.push({ start, end, text: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n let result = source;\n for (const change of changes.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, change.start) + change.text + result.slice(change.end);\n }\n return result;\n}\n\nfunction warnPropertyAccesses(path: string, source: string, context: SchematicContext): void {\n const sourceFile = ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === OLD_NAME) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(`${path}:${line + 1} - \"${OLD_NAME}\" has been renamed to \"${NEW_NAME}\" on ${COMPONENT_TAG}. Update this reference manually.`);\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction visitNodes(nodes: TmplAstNode[], edits: { start: number; end: number; replacement: string }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === COMPONENT_TAG) collectRenames(node, edits);\n visitNodes(node.children, edits);\n }\n }\n}\n\nfunction collectRenames(element: TmplAstElement, edits: { start: number; end: number; replacement: string }[]): void {\n for (const output of element.outputs) {\n if (output.name === OLD_NAME) {\n edits.push({ start: output.keySpan!.start.offset, end: output.keySpan!.end.offset, replacement: NEW_NAME });\n }\n }\n}\n\nfunction applyEdits(source: string, edits: { start: number; end: number; replacement: string }[]): string {\n let result = source;\n for (const edit of edits.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n",
2686
+ "displayName": "Schema",
2687
+ "properties": [
2688
+ {
2689
+ "name": "dryRun",
2690
+ "coverageIgnore": false,
2691
+ "deprecated": false,
2692
+ "deprecationMessage": "",
2693
+ "type": "boolean",
2694
+ "indexKey": "",
2695
+ "optional": true,
2696
+ "description": "",
2697
+ "line": 12,
2698
+ "rawdescription": "\n"
2699
+ },
2700
+ {
2701
+ "name": "path",
2702
+ "coverageIgnore": false,
2703
+ "deprecated": false,
2704
+ "deprecationMessage": "",
2705
+ "type": "string",
2706
+ "indexKey": "",
2707
+ "optional": true,
2708
+ "description": "",
2709
+ "line": 11,
2710
+ "rawdescription": "\n"
2711
+ }
2712
+ ],
2713
+ "indexSignatures": [],
2714
+ "kind": 172,
2715
+ "methods": [],
2716
+ "extends": [],
2717
+ "isDuplicate": true,
2718
+ "duplicateId": 11,
2719
+ "duplicateName": "Schema-11",
2720
+ "relationships": {
2721
+ "incoming": [],
2722
+ "outgoing": []
2723
+ }
2724
+ },
2725
+ {
2726
+ "name": "Schema",
2727
+ "id": "interface-Schema-0ee574e11dd651dd971057cb66220845e5b87b2949c69c623c8ddf7355af92a396c58c237a52a05f18c22a5332a2695b717bcfeb8ea840c4e7df8d0084c2b49c-12",
2728
+ "file": "packages/core/schematics/migrate-eui-fieldset/index.ts",
2729
+ "deprecated": false,
2730
+ "deprecationMessage": "",
2731
+ "type": "interface",
2732
+ "sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst COMPONENT_TAG = 'eui-fieldset';\nconst OLD_NAME = 'iconSvgType';\nconst NEW_NAME = 'iconSvgName';\n\nexport function migrateEuiFieldset(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(COMPONENT_TAG)) return;\n\n let result: string;\n\n if (path.endsWith('.html')) {\n result = migrateTemplate(original);\n } else {\n result = migrateInlineTemplates(original);\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would rename '${OLD_NAME}' → '${NEW_NAME}' in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n\n // Warn about TS property access usages (merged from warnTsUsages)\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts') && original.includes(OLD_NAME)) {\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === OLD_NAME) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(`${path}:${line + 1} - \"${OLD_NAME}\" has been renamed to \"${NEW_NAME}\" on ${COMPONENT_TAG}. Update this reference manually.`);\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n });\n\n context.logger.info(`Renamed '${OLD_NAME}' → '${NEW_NAME}' on ${COMPONENT_TAG} in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: { start: number; end: number; replacement: string }[] = [];\n\n visitNodes(parsed.nodes, edits);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: { start: number; end: number; text: string }[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(COMPONENT_TAG)) {\n ts.forEachChild(node, visit); return; \n}\n const migrated = migrateTemplate(rawTemplate);\n if (migrated !== rawTemplate) changes.push({ start, end, text: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n let result = source;\n for (const change of changes.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, change.start) + change.text + result.slice(change.end);\n }\n return result;\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction visitNodes(nodes: TmplAstNode[], edits: { start: number; end: number; replacement: string }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === COMPONENT_TAG) collectRenames(node, edits);\n visitNodes(node.children, edits);\n }\n }\n}\n\nfunction collectRenames(element: TmplAstElement, edits: { start: number; end: number; replacement: string }[]): void {\n for (const attr of element.attributes) {\n if (attr.name === OLD_NAME) {\n edits.push({ start: attr.keySpan!.start.offset, end: attr.keySpan!.end.offset, replacement: NEW_NAME });\n }\n }\n for (const input of element.inputs) {\n if (input.name === OLD_NAME) {\n edits.push({ start: input.keySpan!.start.offset, end: input.keySpan!.end.offset, replacement: NEW_NAME });\n }\n }\n}\n\nfunction applyEdits(source: string, edits: { start: number; end: number; replacement: string }[]): string {\n let result = source;\n for (const edit of edits.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n",
2733
2733
  "displayName": "Schema",
2734
2734
  "properties": [
2735
2735
  {
@@ -2865,12 +2865,12 @@
2865
2865
  },
2866
2866
  {
2867
2867
  "name": "Schema",
2868
- "id": "interface-Schema-a806c1769fb526271565003a6a3ef9ab4c67e421d93ee0cb54e1f6de223807afc1f16ad294656ef614a77a91a38cc914ce41ac97ed3a8a4195a1e74a729c0c08-15",
2869
- "file": "packages/core/schematics/migrate-eui-popover/index.ts",
2868
+ "id": "interface-Schema-90e62c16ce9ada881e8d434336bb633dae9745236106ddf60964afbd11de6aa579255a13d6036b384281860eb6ed2ee329194b519c51fdaf20f79bc511d0f64f-15",
2869
+ "file": "packages/core/schematics/migrate-eui-progress-circle/index.ts",
2870
2870
  "deprecated": false,
2871
2871
  "deprecationMessage": "",
2872
2872
  "type": "interface",
2873
- "sourceCode": "import { parseTemplate, TmplAstBoundAttribute, TmplAstElement, TmplAstNode, TmplAstTextAttribute } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst REMOVED_INPUTS = new Set(['type']);\n\nexport function migrateEuiPopover(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes('eui-popover')) return;\n\n const result = path.endsWith('.html')\n ? migrateTemplate(original)\n : migrateInlineTemplates(original);\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would remove 'type' input in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n\n // Warn about TS usages inline\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts') && (original.includes('eui-popover') || original.includes('euiPopover') || original.includes('EuiPopover'))) {\n if (original.includes('type')) {\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === 'type') {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${path}:${line + 1} - Manual action needed: \"type\" is no longer a valid input on eui-popover. Remove this assignment.`,\n );\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n }\n });\n\n context.logger.info(`Removed deprecated eui-popover 'type' input from ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const removals: { start: number; end: number }[] = [];\n\n visitNodes(parsed.nodes, removals);\n\n let result = source;\n for (const { start, end } of removals.sort((a, b) => b.start - a.start)) {\n // Extend start backwards to consume leading whitespace\n let adjustedStart = start;\n while (adjustedStart > 0 && (result[adjustedStart - 1] === ' ' || result[adjustedStart - 1] === '\\t')) {\n adjustedStart--;\n }\n result = result.slice(0, adjustedStart) + result.slice(end);\n }\n\n return result;\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const templateRegex = /template\\s*:\\s*`([^`]*)`/gs;\n return source.replace(templateRegex, (match, templateContent: string) => {\n if (!templateContent.includes('eui-popover')) return match;\n const migrated = migrateTemplate(templateContent);\n if (migrated === templateContent) return match;\n return match.replace(templateContent, migrated);\n });\n}\n\nfunction visitNodes(nodes: TmplAstNode[], removals: { start: number; end: number }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === 'eui-popover') {\n collectRemovals(node, removals);\n }\n visitNodes(node.children, removals);\n }\n }\n}\n\nfunction collectRemovals(element: TmplAstElement, removals: { start: number; end: number }[]): void {\n for (const attr of element.attributes) {\n if (REMOVED_INPUTS.has(attr.name)) {\n removals.push(getAttributeSpan(attr));\n }\n }\n for (const input of element.inputs) {\n if (REMOVED_INPUTS.has(input.name)) {\n removals.push(getAttributeSpan(input));\n }\n }\n}\n\nfunction getAttributeSpan(attr: TmplAstTextAttribute | TmplAstBoundAttribute): { start: number; end: number } {\n return { start: attr.sourceSpan.start.offset, end: attr.sourceSpan.end.offset };\n}\n",
2873
+ "sourceCode": "import { parseTemplate, TmplAstBoundAttribute, TmplAstElement, TmplAstNode, TmplAstTextAttribute } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst INPUT_RENAMES = new Map([\n ['iconLabelClass', 'icon'],\n ['iconLabelStyleClass', 'fillColor'],\n]);\n\nexport function migrateEuiProgressCircle(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n const oldNames = [...INPUT_RENAMES.keys()];\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n\n if (original.includes('eui-progress-circle')) {\n const result = path.endsWith('.html')\n ? migrateTemplate(original)\n : migrateInlineTemplates(original);\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would rename deprecated inputs in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n }\n\n // Warn about TS property access usages (merged from warnTsUsages)\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts')) {\n if (oldNames.some((name) => original.includes(name))) {\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && INPUT_RENAMES.has(node.name.text)) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n const newName = INPUT_RENAMES.get(node.name.text);\n context.logger.warn(\n `${path}:${line + 1} - \"${node.name.text}\" has been renamed to \"${newName}\" on eui-progress-circle. Update this reference manually.`,\n );\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n }\n });\n\n context.logger.info(`Renamed deprecated eui-progress-circle inputs in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: { start: number; end: number; replacement: string }[] = [];\n\n visitNodes(parsed.nodes, edits);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: { start: number; end: number; text: string }[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes('eui-progress-circle')) {\n ts.forEachChild(node, visit);\n return;\n }\n const migrated = migrateTemplate(rawTemplate);\n if (migrated !== rawTemplate) {\n changes.push({ start, end, text: migrated });\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n let result = source;\n for (const change of changes.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, change.start) + change.text + result.slice(change.end);\n }\n return result;\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) {\n current = current.expression;\n }\n return current;\n}\n\nfunction visitNodes(nodes: TmplAstNode[], edits: { start: number; end: number; replacement: string }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === 'eui-progress-circle') {\n collectRenames(node, edits);\n }\n visitNodes(node.children, edits);\n }\n }\n}\n\nfunction collectRenames(element: TmplAstElement, edits: { start: number; end: number; replacement: string }[]): void {\n for (const attr of element.attributes) {\n const newName = INPUT_RENAMES.get(attr.name);\n if (newName) {\n edits.push({ start: attr.keySpan!.start.offset, end: attr.keySpan!.end.offset, replacement: newName });\n }\n }\n for (const input of element.inputs) {\n const newName = INPUT_RENAMES.get(input.name);\n if (newName) {\n edits.push({ start: input.keySpan!.start.offset, end: input.keySpan!.end.offset, replacement: newName });\n }\n }\n}\n\nfunction applyEdits(source: string, edits: { start: number; end: number; replacement: string }[]): string {\n let result = source;\n for (const edit of edits.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n",
2874
2874
  "displayName": "Schema",
2875
2875
  "properties": [
2876
2876
  {
@@ -2912,12 +2912,12 @@
2912
2912
  },
2913
2913
  {
2914
2914
  "name": "Schema",
2915
- "id": "interface-Schema-90e62c16ce9ada881e8d434336bb633dae9745236106ddf60964afbd11de6aa579255a13d6036b384281860eb6ed2ee329194b519c51fdaf20f79bc511d0f64f-16",
2916
- "file": "packages/core/schematics/migrate-eui-progress-circle/index.ts",
2915
+ "id": "interface-Schema-91c9f900bf3ebb82488fb65644938ef486ceaa447cff8bfd9d710df9595d82acb267b26f3c252173bc684353f362473120226426ed7ee5d98b33e30011dd9383-16",
2916
+ "file": "packages/core/schematics/migrate-eui-table/index.ts",
2917
2917
  "deprecated": false,
2918
2918
  "deprecationMessage": "",
2919
2919
  "type": "interface",
2920
- "sourceCode": "import { parseTemplate, TmplAstBoundAttribute, TmplAstElement, TmplAstNode, TmplAstTextAttribute } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst INPUT_RENAMES = new Map([\n ['iconLabelClass', 'icon'],\n ['iconLabelStyleClass', 'fillColor'],\n]);\n\nexport function migrateEuiProgressCircle(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n const oldNames = [...INPUT_RENAMES.keys()];\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n\n if (original.includes('eui-progress-circle')) {\n const result = path.endsWith('.html')\n ? migrateTemplate(original)\n : migrateInlineTemplates(original);\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would rename deprecated inputs in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n }\n\n // Warn about TS property access usages (merged from warnTsUsages)\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts')) {\n if (oldNames.some((name) => original.includes(name))) {\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && INPUT_RENAMES.has(node.name.text)) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n const newName = INPUT_RENAMES.get(node.name.text);\n context.logger.warn(\n `${path}:${line + 1} - \"${node.name.text}\" has been renamed to \"${newName}\" on eui-progress-circle. Update this reference manually.`,\n );\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n }\n });\n\n context.logger.info(`Renamed deprecated eui-progress-circle inputs in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: { start: number; end: number; replacement: string }[] = [];\n\n visitNodes(parsed.nodes, edits);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: { start: number; end: number; text: string }[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes('eui-progress-circle')) {\n ts.forEachChild(node, visit);\n return;\n }\n const migrated = migrateTemplate(rawTemplate);\n if (migrated !== rawTemplate) {\n changes.push({ start, end, text: migrated });\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n let result = source;\n for (const change of changes.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, change.start) + change.text + result.slice(change.end);\n }\n return result;\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) {\n current = current.expression;\n }\n return current;\n}\n\nfunction visitNodes(nodes: TmplAstNode[], edits: { start: number; end: number; replacement: string }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === 'eui-progress-circle') {\n collectRenames(node, edits);\n }\n visitNodes(node.children, edits);\n }\n }\n}\n\nfunction collectRenames(element: TmplAstElement, edits: { start: number; end: number; replacement: string }[]): void {\n for (const attr of element.attributes) {\n const newName = INPUT_RENAMES.get(attr.name);\n if (newName) {\n edits.push({ start: attr.keySpan!.start.offset, end: attr.keySpan!.end.offset, replacement: newName });\n }\n }\n for (const input of element.inputs) {\n const newName = INPUT_RENAMES.get(input.name);\n if (newName) {\n edits.push({ start: input.keySpan!.start.offset, end: input.keySpan!.end.offset, replacement: newName });\n }\n }\n}\n\nfunction applyEdits(source: string, edits: { start: number; end: number; replacement: string }[]): string {\n let result = source;\n for (const edit of edits.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n",
2920
+ "sourceCode": "import { BindingPipe, parseTemplate, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstBoundText, TmplAstElement, TmplAstNode, TmplAstTemplate, TmplAstTextAttribute, AST, ASTWithSource, Interpolation } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Edit { start: number; end: number; replacement: string; }\n\n// --- Table-level input renames (on elements with euiTable attribute) ---\nconst TABLE_INPUT_RENAMES = new Map([\n ['rows', 'data'],\n ['loading', 'isLoading'],\n ['asyncTable', 'isAsync'],\n ['euiTableResponsive', 'isTableResponsive'],\n ['euiTableFixedLayout', 'isTableFixedLayout'],\n ['euiTableCompact', 'isTableCompact'],\n ['hasStickyColumns', 'hasStickyCols'],\n]);\n\n// --- Child element input renames (scoped to euiTable context) ---\nconst TH_TD_INPUT_RENAMES = new Map([\n ['isStickyColumn', 'isStickyCol'],\n ['sortable', 'isSortable'],\n]);\n\nconst TR_INPUT_RENAMES = new Map([\n ['isSelectableHeader', 'isHeaderSelectable'],\n ['isSelectable', 'isDataSelectable'],\n]);\n\n// --- Removed inputs ---\nconst REMOVED_INPUTS = new Set(['euiTableBordered', 'isHoverable', 'defaultMultiOrder', 'paginable']);\n\n// --- Output renames/removals ---\nconst OUTPUT_RENAMES = new Map([['selectedRows', 'rowsSelect']]);\nconst REMOVED_OUTPUTS = new Set(['multiSortChange']);\n\n// --- Pipe rename ---\nconst OLD_PIPE = 'euiTableHighlightFilter';\nconst NEW_PIPE = 'euiTableHighlight';\n\n// --- TS property renames ---\nconst TS_PROPERTY_RENAMES = new Map([['filteredRows', 'getFilteredData']]);\n\nconst PAGINATOR_IMPORT_PATH = '@eui/components/eui-paginator';\nconst PAGINATOR_COMPONENT = 'EuiPaginatorComponent';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nexport function migrateEuiTable(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n const paginatorHtmlFiles = new Set<string>();\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes('euiTable') && !original.includes(OLD_PIPE) && !original.includes('filteredRows') && !original.includes('setSort')) return;\n\n let result: string;\n\n if (path.endsWith('.html')) {\n const { output, hasPaginator } = migrateTemplateWithPaginator(original, path, context);\n result = output;\n if (hasPaginator) paginatorHtmlFiles.add(path);\n } else {\n const { output, hasPaginator } = migrateTypeScript(original, path, context);\n result = output;\n if (hasPaginator) {\n result = addPaginatorImport(result, path);\n }\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would migrate eui-table breaking changes in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n });\n\n // Handle paginator imports for external templates\n if (paginatorHtmlFiles.size > 0) {\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n if (!path.endsWith('.ts')) return;\n\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const source = buffer.toString('utf-8');\n if (!source.includes('templateUrl')) return;\n\n for (const htmlFile of paginatorHtmlFiles) {\n const htmlFileName = htmlFile.split('/').pop()!;\n if (source.includes(htmlFileName)) {\n const updated = addPaginatorImport(source, path);\n if (updated !== source) {\n if (options.dryRun) {\n logDryRun(context, `Would add paginator import in ${path}`);\n } else {\n tree.overwrite(path, updated);\n }\n count++;\n }\n break;\n }\n }\n });\n }\n\n context.logger.info(`Migrated eui-table in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction migrateTemplateWithPaginator(source: string, filePath: string, context: SchematicContext): { output: string; hasPaginator: boolean } {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: Edit[] = [];\n let hasPaginator = false;\n\n const paginatorResult = visitNodesForTable(parsed.nodes, source, edits, false, filePath, context);\n if (paginatorResult) hasPaginator = true;\n\n collectPipeRenames(parsed.nodes, source, edits);\n\n return { output: applyEdits(source, edits), hasPaginator };\n}\n\nfunction migrateTemplate(source: string, filePath: string, context: SchematicContext): string {\n return migrateTemplateWithPaginator(source, filePath, context).output;\n}\n\nfunction migrateTypeScript(source: string, filePath: string, context: SchematicContext): { output: string; hasPaginator: boolean } {\n let result = source;\n let hasPaginator = false;\n\n // Migrate inline templates\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: Edit[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = result.slice(start, end);\n if (!rawTemplate.includes('euiTable') && !rawTemplate.includes(OLD_PIPE)) {\n ts.forEachChild(node, visit); return;\n}\n const { output: migrated, hasPaginator: pag } = migrateTemplateWithPaginator(rawTemplate, filePath, context);\n if (pag) hasPaginator = true;\n if (migrated !== rawTemplate) changes.push({ start, end, replacement: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n result = applyEdits(result, changes);\n\n // Rename TS property accesses\n result = renameTsProperties(result);\n\n // Warn about setSort\n warnSetSort(result, filePath, context);\n\n return { output: result, hasPaginator };\n}\n\nfunction renameTsProperties(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: Edit[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && TS_PROPERTY_RENAMES.has(node.name.text)) {\n edits.push({ start: node.name.getStart(sourceFile), end: node.name.getEnd(), replacement: TS_PROPERTY_RENAMES.get(node.name.text)! });\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n return applyEdits(source, edits);\n}\n\nfunction warnSetSort(source: string, filePath: string, context: SchematicContext): void {\n if (!source.includes('setSort')) return;\n\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) &&\n ts.isIdentifier(node.expression.name) && node.expression.name.text === 'setSort') {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${filePath}:${line + 1} - \"setSort\" signature changed from setSort(sort: string, order: \"asc\" | \"desc\") to setSort(Sort[]). Update manually.`,\n );\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n}\n\n// --- Template AST visitors ---\n\nfunction visitNodesForTable(\n nodes: TmplAstNode[], source: string, edits: Edit[], insideEuiTable: boolean, filePath: string, context: SchematicContext,\n): boolean {\n let hasPaginator = false;\n\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n const isEuiTable = hasEuiTableAttribute(node);\n\n if (isEuiTable) {\n collectTableInputRenames(node, edits);\n collectInputRemovals(node, source, edits, filePath, context);\n collectOutputChanges(node, source, edits, filePath, context);\n if (collectPaginatorMigration(node, source, edits)) hasPaginator = true;\n }\n\n if (isEuiTable || insideEuiTable) {\n collectChildElementRenames(node, edits);\n collectChildInputRemovals(node, source, edits, filePath, context);\n collectEmptyMessageRename(node, edits);\n }\n\n const childResult = visitNodesForTable(node.children, source, edits, isEuiTable || insideEuiTable, filePath, context);\n if (childResult) hasPaginator = true;\n }\n\n if (node instanceof TmplAstTemplate) {\n if (insideEuiTable) {\n collectTemplateEmptyMessageRename(node, edits);\n }\n const childResult = visitNodesForTable(node.children, source, edits, insideEuiTable, filePath, context);\n if (childResult) hasPaginator = true;\n }\n }\n\n return hasPaginator;\n}\n\nfunction hasEuiTableAttribute(element: TmplAstElement): boolean {\n return element.attributes.some((a) => a.name === 'euiTable') ||\n element.inputs.some((i) => i.name === 'euiTable');\n}\n\nfunction collectTableInputRenames(element: TmplAstElement, edits: Edit[]): void {\n for (const attr of element.attributes) {\n const newName = TABLE_INPUT_RENAMES.get(attr.name);\n if (newName) edits.push({ start: attr.keySpan!.start.offset, end: attr.keySpan!.end.offset, replacement: newName });\n }\n for (const input of element.inputs) {\n const newName = TABLE_INPUT_RENAMES.get(input.name);\n if (newName) edits.push({ start: input.keySpan!.start.offset, end: input.keySpan!.end.offset, replacement: newName });\n }\n}\n\nfunction collectChildElementRenames(element: TmplAstElement, edits: Edit[]): void {\n const renames = (element.name === 'th' || element.name === 'td') ? TH_TD_INPUT_RENAMES\n : element.name === 'tr' ? TR_INPUT_RENAMES : null;\n if (!renames) return;\n\n for (const attr of element.attributes) {\n const newName = renames.get(attr.name);\n if (newName) edits.push({ start: attr.keySpan!.start.offset, end: attr.keySpan!.end.offset, replacement: newName });\n }\n for (const input of element.inputs) {\n const newName = renames.get(input.name);\n if (newName) edits.push({ start: input.keySpan!.start.offset, end: input.keySpan!.end.offset, replacement: newName });\n }\n}\n\nfunction collectChildInputRemovals(element: TmplAstElement, source: string, edits: Edit[], filePath: string, context: SchematicContext): void {\n const childRemovedInputs = new Set(['defaultMultiOrder']);\n\n for (const attr of element.attributes) {\n if (childRemovedInputs.has(attr.name)) {\n removeAttribute(attr.sourceSpan.start.offset, attr.sourceSpan.end.offset, source, edits);\n logRemovalWarning(attr.name, filePath, element, context);\n }\n }\n for (const input of element.inputs) {\n if (childRemovedInputs.has(input.name)) {\n removeAttribute(input.sourceSpan.start.offset, input.sourceSpan.end.offset, source, edits);\n logRemovalWarning(input.name, filePath, element, context);\n }\n }\n}\n\nfunction collectInputRemovals(element: TmplAstElement, source: string, edits: Edit[], filePath: string, context: SchematicContext): void {\n for (const attr of element.attributes) {\n if (REMOVED_INPUTS.has(attr.name) && attr.name !== 'paginable') {\n removeAttribute(attr.sourceSpan.start.offset, attr.sourceSpan.end.offset, source, edits);\n logRemovalWarning(attr.name, filePath, element, context);\n }\n }\n for (const input of element.inputs) {\n if (REMOVED_INPUTS.has(input.name) && input.name !== 'paginable') {\n removeAttribute(input.sourceSpan.start.offset, input.sourceSpan.end.offset, source, edits);\n logRemovalWarning(input.name, filePath, element, context);\n }\n }\n}\n\nfunction collectOutputChanges(element: TmplAstElement, source: string, edits: Edit[], filePath: string, context: SchematicContext): void {\n for (const output of element.outputs) {\n const newName = OUTPUT_RENAMES.get(output.name);\n if (newName) {\n edits.push({ start: output.keySpan!.start.offset, end: output.keySpan!.end.offset, replacement: newName });\n }\n if (REMOVED_OUTPUTS.has(output.name)) {\n removeAttribute(output.sourceSpan.start.offset, output.sourceSpan.end.offset, source, edits);\n const { line } = element.startSourceSpan.start;\n context.logger.warn(\n `${filePath}:${line + 1} - \"(${output.name})\" removed. Multi-sort is now supported by (sortChange) output.`,\n );\n }\n }\n}\n\nfunction collectPaginatorMigration(element: TmplAstElement, source: string, edits: Edit[]): boolean {\n let found = false;\n\n for (const attr of element.attributes) {\n if (attr.name === 'paginable') {\n removeAttribute(attr.sourceSpan.start.offset, attr.sourceSpan.end.offset, source, edits);\n found = true;\n }\n }\n for (const input of element.inputs) {\n if (input.name === 'paginable') {\n removeAttribute(input.sourceSpan.start.offset, input.sourceSpan.end.offset, source, edits);\n found = true;\n }\n }\n\n if (found) {\n // Add [paginator]=\"paginator\" before closing > of opening tag\n const insertPos = element.startSourceSpan.end.offset - 1;\n edits.push({ start: insertPos, end: insertPos, replacement: ' [paginator]=\"paginator\"' });\n\n // Add eui-paginator after </table>\n if (element.endSourceSpan) {\n const afterTable = element.endSourceSpan.end.offset;\n edits.push({\n start: afterTable,\n end: afterTable,\n replacement: '\\n<!-- TODO: Configure paginator and implement onPageChange handler -->\\n<eui-paginator #paginator [pageSize]=\"10\" [pageSizeOptions]=\"[5, 10, 25, 50]\" />',\n });\n }\n }\n\n return found;\n}\n\nfunction collectEmptyMessageRename(element: TmplAstElement, edits: Edit[]): void {\n // Handle direct attribute on elements (unlikely but handle)\n for (const attr of element.attributes) {\n if (attr.name === 'euiTemplate' && attr.value === 'emptyMessage' && attr.valueSpan) {\n edits.push({ start: attr.valueSpan.start.offset, end: attr.valueSpan.end.offset, replacement: 'footer' });\n }\n }\n}\n\nfunction collectTemplateEmptyMessageRename(template: TmplAstTemplate, edits: Edit[]): void {\n for (const attr of template.templateAttrs) {\n if (attr instanceof TmplAstTextAttribute && attr.name === 'euiTemplate' && attr.value === 'emptyMessage' && attr.valueSpan) {\n edits.push({ start: attr.valueSpan.start.offset, end: attr.valueSpan.end.offset, replacement: 'footer' });\n }\n }\n for (const attr of template.attributes) {\n if (attr.name === 'euiTemplate' && attr.value === 'emptyMessage' && attr.valueSpan) {\n edits.push({ start: attr.valueSpan.start.offset, end: attr.valueSpan.end.offset, replacement: 'footer' });\n }\n }\n}\n\n// --- Pipe rename via AST ---\n\nfunction collectPipeRenames(nodes: TmplAstNode[], source: string, edits: Edit[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n for (const input of node.inputs) {\n visitExpressionForPipes(input.value, edits);\n }\n for (const output of node.outputs) {\n if (output.handler) visitExpressionForPipes(output.handler, edits);\n }\n collectPipeRenames(node.children, source, edits);\n }\n if (node instanceof TmplAstTemplate) {\n for (const input of node.inputs) {\n visitExpressionForPipes(input.value, edits);\n }\n collectPipeRenames(node.children, source, edits);\n }\n if (node instanceof TmplAstBoundText) {\n visitExpressionForPipes(node.value, edits);\n }\n }\n}\n\nfunction visitExpressionForPipes(expr: AST, edits: Edit[]): void {\n if (expr instanceof ASTWithSource && expr.ast) {\n visitAstForPipes(expr.ast, edits);\n } else {\n visitAstForPipes(expr, edits);\n }\n}\n\nfunction visitAstForPipes(ast: AST, edits: Edit[]): void {\n if (ast instanceof BindingPipe) {\n if (ast.name === OLD_PIPE && ast.nameSpan) {\n edits.push({ start: ast.nameSpan.start, end: ast.nameSpan.end, replacement: NEW_PIPE });\n }\n visitAstForPipes(ast.exp, edits);\n for (const arg of ast.args) {\n visitAstForPipes(arg, edits);\n }\n return;\n }\n\n if (ast instanceof Interpolation) {\n for (const expr of ast.expressions) {\n visitAstForPipes(expr, edits);\n }\n return;\n }\n\n // Recursively visit all properties that could contain AST nodes\n for (const key of Object.keys(ast)) {\n // eslint-disable-next-line\n const val = (ast as any)[key];\n if (val instanceof AST) {\n visitAstForPipes(val, edits);\n } else if (Array.isArray(val)) {\n for (const item of val) {\n if (item instanceof AST) visitAstForPipes(item, edits);\n }\n }\n }\n}\n\n// --- Import handling for paginator ---\n\nfunction addPaginatorImport(source: string, filePath: string): string {\n if (source.includes(PAGINATOR_COMPONENT)) return source;\n\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: Edit[] = [];\n\n // Add import statement after last import\n let lastImportEnd = 0;\n for (const stmt of sourceFile.statements) {\n if (ts.isImportDeclaration(stmt)) {\n lastImportEnd = stmt.getEnd();\n }\n }\n\n if (lastImportEnd > 0) {\n edits.push({\n start: lastImportEnd,\n end: lastImportEnd,\n replacement: `\\nimport { ${PAGINATOR_COMPONENT} } from '${PAGINATOR_IMPORT_PATH}';`,\n });\n }\n\n // Add to component imports array\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && node.name.text === 'imports' && isComponentMetadataProperty(node)) {\n if (ts.isArrayLiteralExpression(node.initializer)) {\n const arr = node.initializer;\n const elements = arr.elements;\n if (elements.length > 0) {\n const lastElement = elements[elements.length - 1];\n edits.push({\n start: lastElement.getEnd(),\n end: lastElement.getEnd(),\n replacement: `, ${PAGINATOR_COMPONENT}`,\n });\n } else {\n const insertPos = arr.getStart(sourceFile) + 1;\n edits.push({ start: insertPos, end: insertPos, replacement: PAGINATOR_COMPONENT });\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n return applyEdits(source, edits);\n}\n\n// --- Helpers ---\n\nfunction removeAttribute(start: number, end: number, source: string, edits: Edit[]): void {\n let adjustedStart = start;\n while (adjustedStart > 0 && (source[adjustedStart - 1] === ' ' || source[adjustedStart - 1] === '\\t')) {\n adjustedStart--;\n }\n edits.push({ start: adjustedStart, end, replacement: '' });\n}\n\nfunction logRemovalWarning(name: string, filePath: string, element: TmplAstElement, context: SchematicContext): void {\n const { line } = element.startSourceSpan.start;\n if (name === 'defaultMultiOrder') {\n context.logger.warn(`${filePath}:${line + 1} - \"[${name}]\" removed. Use setSort(Sort[]) to initialize sorting.`);\n } else {\n context.logger.warn(`${filePath}:${line + 1} - \"[${name}]\" removed to align to Design System.`);\n }\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction applyEdits(source: string, edits: Edit[]): string {\n const unique = new Map<string, Edit>();\n for (const edit of edits) {\n const key = `${edit.start}:${edit.end}`;\n unique.set(key, edit);\n }\n let result = source;\n for (const edit of [...unique.values()].sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n",
2921
2921
  "displayName": "Schema",
2922
2922
  "properties": [
2923
2923
  {
@@ -2929,7 +2929,7 @@
2929
2929
  "indexKey": "",
2930
2930
  "optional": true,
2931
2931
  "description": "",
2932
- "line": 8,
2932
+ "line": 49,
2933
2933
  "rawdescription": "\n"
2934
2934
  },
2935
2935
  {
@@ -2941,7 +2941,7 @@
2941
2941
  "indexKey": "",
2942
2942
  "optional": true,
2943
2943
  "description": "",
2944
- "line": 7,
2944
+ "line": 48,
2945
2945
  "rawdescription": "\n"
2946
2946
  }
2947
2947
  ],
@@ -2959,12 +2959,12 @@
2959
2959
  },
2960
2960
  {
2961
2961
  "name": "Schema",
2962
- "id": "interface-Schema-91c9f900bf3ebb82488fb65644938ef486ceaa447cff8bfd9d710df9595d82acb267b26f3c252173bc684353f362473120226426ed7ee5d98b33e30011dd9383-17",
2963
- "file": "packages/core/schematics/migrate-eui-table/index.ts",
2962
+ "id": "interface-Schema-a806c1769fb526271565003a6a3ef9ab4c67e421d93ee0cb54e1f6de223807afc1f16ad294656ef614a77a91a38cc914ce41ac97ed3a8a4195a1e74a729c0c08-17",
2963
+ "file": "packages/core/schematics/migrate-eui-popover/index.ts",
2964
2964
  "deprecated": false,
2965
2965
  "deprecationMessage": "",
2966
2966
  "type": "interface",
2967
- "sourceCode": "import { BindingPipe, parseTemplate, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstBoundText, TmplAstElement, TmplAstNode, TmplAstTemplate, TmplAstTextAttribute, AST, ASTWithSource, Interpolation } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Edit { start: number; end: number; replacement: string; }\n\n// --- Table-level input renames (on elements with euiTable attribute) ---\nconst TABLE_INPUT_RENAMES = new Map([\n ['rows', 'data'],\n ['loading', 'isLoading'],\n ['asyncTable', 'isAsync'],\n ['euiTableResponsive', 'isTableResponsive'],\n ['euiTableFixedLayout', 'isTableFixedLayout'],\n ['euiTableCompact', 'isTableCompact'],\n ['hasStickyColumns', 'hasStickyCols'],\n]);\n\n// --- Child element input renames (scoped to euiTable context) ---\nconst TH_TD_INPUT_RENAMES = new Map([\n ['isStickyColumn', 'isStickyCol'],\n ['sortable', 'isSortable'],\n]);\n\nconst TR_INPUT_RENAMES = new Map([\n ['isSelectableHeader', 'isHeaderSelectable'],\n ['isSelectable', 'isDataSelectable'],\n]);\n\n// --- Removed inputs ---\nconst REMOVED_INPUTS = new Set(['euiTableBordered', 'isHoverable', 'defaultMultiOrder', 'paginable']);\n\n// --- Output renames/removals ---\nconst OUTPUT_RENAMES = new Map([['selectedRows', 'rowsSelect']]);\nconst REMOVED_OUTPUTS = new Set(['multiSortChange']);\n\n// --- Pipe rename ---\nconst OLD_PIPE = 'euiTableHighlightFilter';\nconst NEW_PIPE = 'euiTableHighlight';\n\n// --- TS property renames ---\nconst TS_PROPERTY_RENAMES = new Map([['filteredRows', 'getFilteredData']]);\n\nconst PAGINATOR_IMPORT_PATH = '@eui/components/eui-paginator';\nconst PAGINATOR_COMPONENT = 'EuiPaginatorComponent';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nexport function migrateEuiTable(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n const paginatorHtmlFiles = new Set<string>();\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes('euiTable') && !original.includes(OLD_PIPE) && !original.includes('filteredRows') && !original.includes('setSort')) return;\n\n let result: string;\n\n if (path.endsWith('.html')) {\n const { output, hasPaginator } = migrateTemplateWithPaginator(original, path, context);\n result = output;\n if (hasPaginator) paginatorHtmlFiles.add(path);\n } else {\n const { output, hasPaginator } = migrateTypeScript(original, path, context);\n result = output;\n if (hasPaginator) {\n result = addPaginatorImport(result, path);\n }\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would migrate eui-table breaking changes in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n });\n\n // Handle paginator imports for external templates\n if (paginatorHtmlFiles.size > 0) {\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n if (!path.endsWith('.ts')) return;\n\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const source = buffer.toString('utf-8');\n if (!source.includes('templateUrl')) return;\n\n for (const htmlFile of paginatorHtmlFiles) {\n const htmlFileName = htmlFile.split('/').pop()!;\n if (source.includes(htmlFileName)) {\n const updated = addPaginatorImport(source, path);\n if (updated !== source) {\n if (options.dryRun) {\n logDryRun(context, `Would add paginator import in ${path}`);\n } else {\n tree.overwrite(path, updated);\n }\n count++;\n }\n break;\n }\n }\n });\n }\n\n context.logger.info(`Migrated eui-table in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction migrateTemplateWithPaginator(source: string, filePath: string, context: SchematicContext): { output: string; hasPaginator: boolean } {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: Edit[] = [];\n let hasPaginator = false;\n\n const paginatorResult = visitNodesForTable(parsed.nodes, source, edits, false, filePath, context);\n if (paginatorResult) hasPaginator = true;\n\n collectPipeRenames(parsed.nodes, source, edits);\n\n return { output: applyEdits(source, edits), hasPaginator };\n}\n\nfunction migrateTemplate(source: string, filePath: string, context: SchematicContext): string {\n return migrateTemplateWithPaginator(source, filePath, context).output;\n}\n\nfunction migrateTypeScript(source: string, filePath: string, context: SchematicContext): { output: string; hasPaginator: boolean } {\n let result = source;\n let hasPaginator = false;\n\n // Migrate inline templates\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: Edit[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = result.slice(start, end);\n if (!rawTemplate.includes('euiTable') && !rawTemplate.includes(OLD_PIPE)) {\n ts.forEachChild(node, visit); return;\n}\n const { output: migrated, hasPaginator: pag } = migrateTemplateWithPaginator(rawTemplate, filePath, context);\n if (pag) hasPaginator = true;\n if (migrated !== rawTemplate) changes.push({ start, end, replacement: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n result = applyEdits(result, changes);\n\n // Rename TS property accesses\n result = renameTsProperties(result);\n\n // Warn about setSort\n warnSetSort(result, filePath, context);\n\n return { output: result, hasPaginator };\n}\n\nfunction renameTsProperties(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: Edit[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && TS_PROPERTY_RENAMES.has(node.name.text)) {\n edits.push({ start: node.name.getStart(sourceFile), end: node.name.getEnd(), replacement: TS_PROPERTY_RENAMES.get(node.name.text)! });\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n return applyEdits(source, edits);\n}\n\nfunction warnSetSort(source: string, filePath: string, context: SchematicContext): void {\n if (!source.includes('setSort')) return;\n\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) &&\n ts.isIdentifier(node.expression.name) && node.expression.name.text === 'setSort') {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${filePath}:${line + 1} - \"setSort\" signature changed from setSort(sort: string, order: \"asc\" | \"desc\") to setSort(Sort[]). Update manually.`,\n );\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n}\n\n// --- Template AST visitors ---\n\nfunction visitNodesForTable(\n nodes: TmplAstNode[], source: string, edits: Edit[], insideEuiTable: boolean, filePath: string, context: SchematicContext,\n): boolean {\n let hasPaginator = false;\n\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n const isEuiTable = hasEuiTableAttribute(node);\n\n if (isEuiTable) {\n collectTableInputRenames(node, edits);\n collectInputRemovals(node, source, edits, filePath, context);\n collectOutputChanges(node, source, edits, filePath, context);\n if (collectPaginatorMigration(node, source, edits)) hasPaginator = true;\n }\n\n if (isEuiTable || insideEuiTable) {\n collectChildElementRenames(node, edits);\n collectChildInputRemovals(node, source, edits, filePath, context);\n collectEmptyMessageRename(node, edits);\n }\n\n const childResult = visitNodesForTable(node.children, source, edits, isEuiTable || insideEuiTable, filePath, context);\n if (childResult) hasPaginator = true;\n }\n\n if (node instanceof TmplAstTemplate) {\n if (insideEuiTable) {\n collectTemplateEmptyMessageRename(node, edits);\n }\n const childResult = visitNodesForTable(node.children, source, edits, insideEuiTable, filePath, context);\n if (childResult) hasPaginator = true;\n }\n }\n\n return hasPaginator;\n}\n\nfunction hasEuiTableAttribute(element: TmplAstElement): boolean {\n return element.attributes.some((a) => a.name === 'euiTable') ||\n element.inputs.some((i) => i.name === 'euiTable');\n}\n\nfunction collectTableInputRenames(element: TmplAstElement, edits: Edit[]): void {\n for (const attr of element.attributes) {\n const newName = TABLE_INPUT_RENAMES.get(attr.name);\n if (newName) edits.push({ start: attr.keySpan!.start.offset, end: attr.keySpan!.end.offset, replacement: newName });\n }\n for (const input of element.inputs) {\n const newName = TABLE_INPUT_RENAMES.get(input.name);\n if (newName) edits.push({ start: input.keySpan!.start.offset, end: input.keySpan!.end.offset, replacement: newName });\n }\n}\n\nfunction collectChildElementRenames(element: TmplAstElement, edits: Edit[]): void {\n const renames = (element.name === 'th' || element.name === 'td') ? TH_TD_INPUT_RENAMES\n : element.name === 'tr' ? TR_INPUT_RENAMES : null;\n if (!renames) return;\n\n for (const attr of element.attributes) {\n const newName = renames.get(attr.name);\n if (newName) edits.push({ start: attr.keySpan!.start.offset, end: attr.keySpan!.end.offset, replacement: newName });\n }\n for (const input of element.inputs) {\n const newName = renames.get(input.name);\n if (newName) edits.push({ start: input.keySpan!.start.offset, end: input.keySpan!.end.offset, replacement: newName });\n }\n}\n\nfunction collectChildInputRemovals(element: TmplAstElement, source: string, edits: Edit[], filePath: string, context: SchematicContext): void {\n const childRemovedInputs = new Set(['defaultMultiOrder']);\n\n for (const attr of element.attributes) {\n if (childRemovedInputs.has(attr.name)) {\n removeAttribute(attr.sourceSpan.start.offset, attr.sourceSpan.end.offset, source, edits);\n logRemovalWarning(attr.name, filePath, element, context);\n }\n }\n for (const input of element.inputs) {\n if (childRemovedInputs.has(input.name)) {\n removeAttribute(input.sourceSpan.start.offset, input.sourceSpan.end.offset, source, edits);\n logRemovalWarning(input.name, filePath, element, context);\n }\n }\n}\n\nfunction collectInputRemovals(element: TmplAstElement, source: string, edits: Edit[], filePath: string, context: SchematicContext): void {\n for (const attr of element.attributes) {\n if (REMOVED_INPUTS.has(attr.name) && attr.name !== 'paginable') {\n removeAttribute(attr.sourceSpan.start.offset, attr.sourceSpan.end.offset, source, edits);\n logRemovalWarning(attr.name, filePath, element, context);\n }\n }\n for (const input of element.inputs) {\n if (REMOVED_INPUTS.has(input.name) && input.name !== 'paginable') {\n removeAttribute(input.sourceSpan.start.offset, input.sourceSpan.end.offset, source, edits);\n logRemovalWarning(input.name, filePath, element, context);\n }\n }\n}\n\nfunction collectOutputChanges(element: TmplAstElement, source: string, edits: Edit[], filePath: string, context: SchematicContext): void {\n for (const output of element.outputs) {\n const newName = OUTPUT_RENAMES.get(output.name);\n if (newName) {\n edits.push({ start: output.keySpan!.start.offset, end: output.keySpan!.end.offset, replacement: newName });\n }\n if (REMOVED_OUTPUTS.has(output.name)) {\n removeAttribute(output.sourceSpan.start.offset, output.sourceSpan.end.offset, source, edits);\n const { line } = element.startSourceSpan.start;\n context.logger.warn(\n `${filePath}:${line + 1} - \"(${output.name})\" removed. Multi-sort is now supported by (sortChange) output.`,\n );\n }\n }\n}\n\nfunction collectPaginatorMigration(element: TmplAstElement, source: string, edits: Edit[]): boolean {\n let found = false;\n\n for (const attr of element.attributes) {\n if (attr.name === 'paginable') {\n removeAttribute(attr.sourceSpan.start.offset, attr.sourceSpan.end.offset, source, edits);\n found = true;\n }\n }\n for (const input of element.inputs) {\n if (input.name === 'paginable') {\n removeAttribute(input.sourceSpan.start.offset, input.sourceSpan.end.offset, source, edits);\n found = true;\n }\n }\n\n if (found) {\n // Add [paginator]=\"paginator\" before closing > of opening tag\n const insertPos = element.startSourceSpan.end.offset - 1;\n edits.push({ start: insertPos, end: insertPos, replacement: ' [paginator]=\"paginator\"' });\n\n // Add eui-paginator after </table>\n if (element.endSourceSpan) {\n const afterTable = element.endSourceSpan.end.offset;\n edits.push({\n start: afterTable,\n end: afterTable,\n replacement: '\\n<!-- TODO: Configure paginator and implement onPageChange handler -->\\n<eui-paginator #paginator [pageSize]=\"10\" [pageSizeOptions]=\"[5, 10, 25, 50]\" />',\n });\n }\n }\n\n return found;\n}\n\nfunction collectEmptyMessageRename(element: TmplAstElement, edits: Edit[]): void {\n // Handle direct attribute on elements (unlikely but handle)\n for (const attr of element.attributes) {\n if (attr.name === 'euiTemplate' && attr.value === 'emptyMessage' && attr.valueSpan) {\n edits.push({ start: attr.valueSpan.start.offset, end: attr.valueSpan.end.offset, replacement: 'footer' });\n }\n }\n}\n\nfunction collectTemplateEmptyMessageRename(template: TmplAstTemplate, edits: Edit[]): void {\n for (const attr of template.templateAttrs) {\n if (attr instanceof TmplAstTextAttribute && attr.name === 'euiTemplate' && attr.value === 'emptyMessage' && attr.valueSpan) {\n edits.push({ start: attr.valueSpan.start.offset, end: attr.valueSpan.end.offset, replacement: 'footer' });\n }\n }\n for (const attr of template.attributes) {\n if (attr.name === 'euiTemplate' && attr.value === 'emptyMessage' && attr.valueSpan) {\n edits.push({ start: attr.valueSpan.start.offset, end: attr.valueSpan.end.offset, replacement: 'footer' });\n }\n }\n}\n\n// --- Pipe rename via AST ---\n\nfunction collectPipeRenames(nodes: TmplAstNode[], source: string, edits: Edit[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n for (const input of node.inputs) {\n visitExpressionForPipes(input.value, edits);\n }\n for (const output of node.outputs) {\n if (output.handler) visitExpressionForPipes(output.handler, edits);\n }\n collectPipeRenames(node.children, source, edits);\n }\n if (node instanceof TmplAstTemplate) {\n for (const input of node.inputs) {\n visitExpressionForPipes(input.value, edits);\n }\n collectPipeRenames(node.children, source, edits);\n }\n if (node instanceof TmplAstBoundText) {\n visitExpressionForPipes(node.value, edits);\n }\n }\n}\n\nfunction visitExpressionForPipes(expr: AST, edits: Edit[]): void {\n if (expr instanceof ASTWithSource && expr.ast) {\n visitAstForPipes(expr.ast, edits);\n } else {\n visitAstForPipes(expr, edits);\n }\n}\n\nfunction visitAstForPipes(ast: AST, edits: Edit[]): void {\n if (ast instanceof BindingPipe) {\n if (ast.name === OLD_PIPE && ast.nameSpan) {\n edits.push({ start: ast.nameSpan.start, end: ast.nameSpan.end, replacement: NEW_PIPE });\n }\n visitAstForPipes(ast.exp, edits);\n for (const arg of ast.args) {\n visitAstForPipes(arg, edits);\n }\n return;\n }\n\n if (ast instanceof Interpolation) {\n for (const expr of ast.expressions) {\n visitAstForPipes(expr, edits);\n }\n return;\n }\n\n // Recursively visit all properties that could contain AST nodes\n for (const key of Object.keys(ast)) {\n // eslint-disable-next-line\n const val = (ast as any)[key];\n if (val instanceof AST) {\n visitAstForPipes(val, edits);\n } else if (Array.isArray(val)) {\n for (const item of val) {\n if (item instanceof AST) visitAstForPipes(item, edits);\n }\n }\n }\n}\n\n// --- Import handling for paginator ---\n\nfunction addPaginatorImport(source: string, filePath: string): string {\n if (source.includes(PAGINATOR_COMPONENT)) return source;\n\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: Edit[] = [];\n\n // Add import statement after last import\n let lastImportEnd = 0;\n for (const stmt of sourceFile.statements) {\n if (ts.isImportDeclaration(stmt)) {\n lastImportEnd = stmt.getEnd();\n }\n }\n\n if (lastImportEnd > 0) {\n edits.push({\n start: lastImportEnd,\n end: lastImportEnd,\n replacement: `\\nimport { ${PAGINATOR_COMPONENT} } from '${PAGINATOR_IMPORT_PATH}';`,\n });\n }\n\n // Add to component imports array\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && node.name.text === 'imports' && isComponentMetadataProperty(node)) {\n if (ts.isArrayLiteralExpression(node.initializer)) {\n const arr = node.initializer;\n const elements = arr.elements;\n if (elements.length > 0) {\n const lastElement = elements[elements.length - 1];\n edits.push({\n start: lastElement.getEnd(),\n end: lastElement.getEnd(),\n replacement: `, ${PAGINATOR_COMPONENT}`,\n });\n } else {\n const insertPos = arr.getStart(sourceFile) + 1;\n edits.push({ start: insertPos, end: insertPos, replacement: PAGINATOR_COMPONENT });\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n return applyEdits(source, edits);\n}\n\n// --- Helpers ---\n\nfunction removeAttribute(start: number, end: number, source: string, edits: Edit[]): void {\n let adjustedStart = start;\n while (adjustedStart > 0 && (source[adjustedStart - 1] === ' ' || source[adjustedStart - 1] === '\\t')) {\n adjustedStart--;\n }\n edits.push({ start: adjustedStart, end, replacement: '' });\n}\n\nfunction logRemovalWarning(name: string, filePath: string, element: TmplAstElement, context: SchematicContext): void {\n const { line } = element.startSourceSpan.start;\n if (name === 'defaultMultiOrder') {\n context.logger.warn(`${filePath}:${line + 1} - \"[${name}]\" removed. Use setSort(Sort[]) to initialize sorting.`);\n } else {\n context.logger.warn(`${filePath}:${line + 1} - \"[${name}]\" removed to align to Design System.`);\n }\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction applyEdits(source: string, edits: Edit[]): string {\n const unique = new Map<string, Edit>();\n for (const edit of edits) {\n const key = `${edit.start}:${edit.end}`;\n unique.set(key, edit);\n }\n let result = source;\n for (const edit of [...unique.values()].sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n",
2967
+ "sourceCode": "import { parseTemplate, TmplAstBoundAttribute, TmplAstElement, TmplAstNode, TmplAstTextAttribute } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst REMOVED_INPUTS = new Set(['type']);\n\nexport function migrateEuiPopover(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes('eui-popover')) return;\n\n const result = path.endsWith('.html')\n ? migrateTemplate(original)\n : migrateInlineTemplates(original);\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would remove 'type' input in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n\n // Warn about TS usages inline\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts') && (original.includes('eui-popover') || original.includes('euiPopover') || original.includes('EuiPopover'))) {\n if (original.includes('type')) {\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === 'type') {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${path}:${line + 1} - Manual action needed: \"type\" is no longer a valid input on eui-popover. Remove this assignment.`,\n );\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n }\n });\n\n context.logger.info(`Removed deprecated eui-popover 'type' input from ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const removals: { start: number; end: number }[] = [];\n\n visitNodes(parsed.nodes, removals);\n\n let result = source;\n for (const { start, end } of removals.sort((a, b) => b.start - a.start)) {\n // Extend start backwards to consume leading whitespace\n let adjustedStart = start;\n while (adjustedStart > 0 && (result[adjustedStart - 1] === ' ' || result[adjustedStart - 1] === '\\t')) {\n adjustedStart--;\n }\n result = result.slice(0, adjustedStart) + result.slice(end);\n }\n\n return result;\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const templateRegex = /template\\s*:\\s*`([^`]*)`/gs;\n return source.replace(templateRegex, (match, templateContent: string) => {\n if (!templateContent.includes('eui-popover')) return match;\n const migrated = migrateTemplate(templateContent);\n if (migrated === templateContent) return match;\n return match.replace(templateContent, migrated);\n });\n}\n\nfunction visitNodes(nodes: TmplAstNode[], removals: { start: number; end: number }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === 'eui-popover') {\n collectRemovals(node, removals);\n }\n visitNodes(node.children, removals);\n }\n }\n}\n\nfunction collectRemovals(element: TmplAstElement, removals: { start: number; end: number }[]): void {\n for (const attr of element.attributes) {\n if (REMOVED_INPUTS.has(attr.name)) {\n removals.push(getAttributeSpan(attr));\n }\n }\n for (const input of element.inputs) {\n if (REMOVED_INPUTS.has(input.name)) {\n removals.push(getAttributeSpan(input));\n }\n }\n}\n\nfunction getAttributeSpan(attr: TmplAstTextAttribute | TmplAstBoundAttribute): { start: number; end: number } {\n return { start: attr.sourceSpan.start.offset, end: attr.sourceSpan.end.offset };\n}\n",
2968
2968
  "displayName": "Schema",
2969
2969
  "properties": [
2970
2970
  {
@@ -2976,7 +2976,7 @@
2976
2976
  "indexKey": "",
2977
2977
  "optional": true,
2978
2978
  "description": "",
2979
- "line": 49,
2979
+ "line": 8,
2980
2980
  "rawdescription": "\n"
2981
2981
  },
2982
2982
  {
@@ -2988,7 +2988,7 @@
2988
2988
  "indexKey": "",
2989
2989
  "optional": true,
2990
2990
  "description": "",
2991
- "line": 48,
2991
+ "line": 7,
2992
2992
  "rawdescription": "\n"
2993
2993
  }
2994
2994
  ],
@@ -3053,12 +3053,12 @@
3053
3053
  },
3054
3054
  {
3055
3055
  "name": "Schema",
3056
- "id": "interface-Schema-e1cd02924eb82a618c71a0b26c081bdd020519d2699aa0e2dc98640c4e0f347c3649b967c5fc87543e41bbbfabd1304835850ccc38f40684d6521c242b2030ed-19",
3057
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
3056
+ "id": "interface-Schema-cac2ac952d8245647e8a48b450e8f127a36bcffb394cf2cd81fe9c0810ed02c19e5e1629dbf29a68a5c6f675aed99570e9ac04ae470e13315e20c92cc0c6c453-19",
3057
+ "file": "packages/core/schematics/migrate-to-signals/index.ts",
3058
3058
  "deprecated": false,
3059
3059
  "deprecationMessage": "",
3060
3060
  "type": "interface",
3061
- "sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\nconst OLD_TAG = 'eui-toolbar-menu';\nconst NEW_TAG = 'eui-toolbar-mega-menu';\nconst OLD_COMPONENT = 'EuiToolbarMenuComponent';\nconst NEW_COMPONENT = 'EuiToolbarMegaMenuComponent';\nconst OLD_INTERFACE = 'ToolbarItem';\nconst NEW_INTERFACE = 'EuiMenuItem';\nconst NEW_COMPONENT_PATH = '@eui/components/layout';\nconst NEW_INTERFACE_PATH = '@eui/core';\nconst REMOVED_OUTPUT = 'menuItemClick';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\ninterface Edit {\n start: number;\n end: number;\n replacement: string;\n}\n\nexport function migrateEuiToolbarMenu(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let fileCount = 0;\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(OLD_TAG) && !original.includes(OLD_COMPONENT) && !original.includes(OLD_INTERFACE)) return;\n\n let result: string;\n\n if (path.endsWith('.html')) {\n result = migrateTemplate(original, path, context);\n } else {\n result = migrateTypeScript(original, path, context);\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would migrate eui-toolbar-menu → eui-toolbar-mega-menu in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n fileCount++;\n }\n });\n\n context.logger.info(`Migrated eui-toolbar-menu → eui-toolbar-mega-menu in ${fileCount} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction migrateTemplate(source: string, filePath: string, context: SchematicContext): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: Edit[] = [];\n\n visitNodes(parsed.nodes, source, edits, filePath, context);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateTypeScript(source: string, filePath: string, context: SchematicContext): string {\n let result = migrateInlineTemplates(source, filePath, context);\n result = migrateImportsAndTypes(result, filePath, context);\n return result;\n}\n\nfunction migrateInlineTemplates(source: string, filePath: string, context: SchematicContext): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: Edit[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(OLD_TAG)) {\n ts.forEachChild(node, visit); return; \n}\n const migrated = migrateTemplate(rawTemplate, filePath, context);\n if (migrated !== rawTemplate) changes.push({ start, end, replacement: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n return applyEdits(source, changes);\n}\n\nfunction migrateImportsAndTypes(source: string, filePath: string, context: SchematicContext): string {\n if (!source.includes(OLD_COMPONENT) && !source.includes(OLD_INTERFACE)) return source;\n\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: Edit[] = [];\n\n // Track if EuiMenuItem is already imported from @eui/core\n let hasEuiMenuItemImport = false;\n\n // First pass: analyze imports\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const moduleSpec = (stmt.moduleSpecifier as ts.StringLiteral).text;\n const namedBindings = stmt.importClause?.namedBindings;\n if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;\n\n for (const specifier of namedBindings.elements) {\n if (specifier.name.text === NEW_INTERFACE && moduleSpec === NEW_INTERFACE_PATH) {\n hasEuiMenuItemImport = true;\n }\n }\n }\n\n // Second pass: collect edits for import declarations\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const namedBindings = stmt.importClause?.namedBindings;\n if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;\n\n const moduleSpec = stmt.moduleSpecifier as ts.StringLiteral;\n const specifiers = namedBindings.elements;\n const hasComponent = specifiers.some((s) => s.name.text === OLD_COMPONENT);\n const hasInterface = specifiers.some((s) => s.name.text === OLD_INTERFACE);\n\n if (hasComponent && hasInterface) {\n // Both are in the same import → must split into two different paths\n const others = specifiers.filter((s) => s.name.text !== OLD_COMPONENT && s.name.text !== OLD_INTERFACE);\n const lines: string[] = [];\n lines.push(`import { ${NEW_COMPONENT} } from '${NEW_COMPONENT_PATH}';`);\n if (!hasEuiMenuItemImport) {\n lines.push(`import { ${NEW_INTERFACE} } from '${NEW_INTERFACE_PATH}';`);\n }\n if (others.length > 0) {\n const otherNames = others.map((s) => s.name.text).join(', ');\n lines.push(`import { ${otherNames} } from '${moduleSpec.text}';`);\n }\n edits.push({\n start: stmt.getStart(sourceFile),\n end: stmt.getEnd(),\n replacement: lines.join('\\n'),\n });\n } else if (hasComponent) {\n edits.push({\n start: moduleSpec.getStart(sourceFile) + 1,\n end: moduleSpec.getEnd() - 1,\n replacement: NEW_COMPONENT_PATH,\n });\n for (const specifier of specifiers) {\n if (specifier.name.text === OLD_COMPONENT) {\n edits.push({\n start: specifier.name.getStart(sourceFile),\n end: specifier.name.getEnd(),\n replacement: NEW_COMPONENT,\n });\n }\n }\n } else if (hasInterface) {\n if (hasEuiMenuItemImport) {\n removeImportSpecifier(namedBindings, specifiers.find((s) => s.name.text === OLD_INTERFACE)!, sourceFile, edits);\n } else {\n edits.push({\n start: moduleSpec.getStart(sourceFile) + 1,\n end: moduleSpec.getEnd() - 1,\n replacement: NEW_INTERFACE_PATH,\n });\n for (const specifier of specifiers) {\n if (specifier.name.text === OLD_INTERFACE) {\n edits.push({\n start: specifier.name.getStart(sourceFile),\n end: specifier.name.getEnd(),\n replacement: NEW_INTERFACE,\n });\n }\n }\n }\n }\n }\n\n // Third pass: rename identifier references in non-import positions\n const visitRefs = (node: ts.Node): void => {\n if (ts.isImportDeclaration(node)) return; // skip imports (already handled)\n if (ts.isIdentifier(node)) {\n if (node.text === OLD_COMPONENT) {\n edits.push({ start: node.getStart(sourceFile), end: node.getEnd(), replacement: NEW_COMPONENT });\n }\n if (node.text === OLD_INTERFACE) {\n edits.push({ start: node.getStart(sourceFile), end: node.getEnd(), replacement: NEW_INTERFACE });\n }\n }\n ts.forEachChild(node, visitRefs);\n };\n\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) {\n visitRefs(stmt);\n }\n }\n\n // Warn about ToolbarItem-specific properties\n warnRemovedProperties(sourceFile, filePath, context);\n\n return applyEdits(source, edits);\n}\n\nfunction removeImportSpecifier(\n namedImports: ts.NamedImports,\n specifier: ts.ImportSpecifier,\n sourceFile: ts.SourceFile,\n edits: Edit[],\n): void {\n const elements = namedImports.elements;\n if (elements.length === 1) {\n // Remove the entire import declaration\n const importDecl = namedImports.parent.parent;\n edits.push({\n start: importDecl.getStart(sourceFile),\n end: importDecl.getEnd(),\n replacement: '',\n });\n } else {\n // Remove just this specifier with surrounding comma/whitespace\n const idx = elements.indexOf(specifier);\n let start: number;\n let end: number;\n if (idx < elements.length - 1) {\n start = specifier.getStart(sourceFile);\n end = elements[idx + 1].getStart(sourceFile);\n } else {\n start = elements[idx - 1].getEnd();\n end = specifier.getEnd();\n }\n edits.push({ start, end, replacement: '' });\n }\n}\n\nfunction warnRemovedProperties(sourceFile: ts.SourceFile, filePath: string, context: SchematicContext): void {\n const deprecated = ['isHome', 'isSeparator'];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && deprecated.includes(node.name.text)) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${filePath}:${line + 1} - \"${node.name.text}\" was part of ToolbarItem but does not exist on EuiMenuItem. Review manually.`,\n );\n }\n if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && deprecated.includes(node.name.text)) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${filePath}:${line + 1} - \"${node.name.text}\" was part of ToolbarItem but does not exist on EuiMenuItem. Review manually.`,\n );\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n}\n\nfunction visitNodes(nodes: TmplAstNode[], source: string, edits: Edit[], filePath: string, context: SchematicContext): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === OLD_TAG) {\n collectTagRenames(node, source, edits);\n collectOutputRemovals(node, source, edits, filePath, context);\n }\n visitNodes(node.children, source, edits, filePath, context);\n }\n }\n}\n\nfunction collectTagRenames(element: TmplAstElement, source: string, edits: Edit[]): void {\n // Rename opening tag\n const openStart = element.startSourceSpan.start.offset + 1; // skip '<'\n edits.push({ start: openStart, end: openStart + OLD_TAG.length, replacement: NEW_TAG });\n\n // Rename closing tag\n if (element.endSourceSpan) {\n const closeStart = element.endSourceSpan.start.offset + 2; // skip '</'\n edits.push({ start: closeStart, end: closeStart + OLD_TAG.length, replacement: NEW_TAG });\n }\n}\n\nfunction collectOutputRemovals(\n element: TmplAstElement,\n source: string,\n edits: Edit[],\n filePath: string,\n context: SchematicContext,\n): void {\n for (const output of element.outputs) {\n if (output.name === REMOVED_OUTPUT) {\n let start = output.sourceSpan.start.offset;\n // Remove leading whitespace\n while (start > 0 && (source[start - 1] === ' ' || source[start - 1] === '\\t')) {\n start--;\n }\n edits.push({ start, end: output.sourceSpan.end.offset, replacement: '' });\n\n const { line } = element.startSourceSpan.start;\n context.logger.warn(\n `${filePath}:${line + 1} - \"(menuItemClick)\" has been removed. There is no equivalent on eui-toolbar-mega-menu.`,\n );\n }\n }\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction applyEdits(source: string, edits: Edit[]): string {\n // Deduplicate edits at same position (e.g. module path edits when both Component and ToolbarItem are from same source)\n const unique = deduplicateEdits(edits);\n let result = source;\n for (const edit of unique.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n\nfunction deduplicateEdits(edits: Edit[]): Edit[] {\n const seen = new Map<string, Edit>();\n for (const edit of edits) {\n const key = `${edit.start}:${edit.end}`;\n // Last wins for same range\n seen.set(key, edit);\n }\n return Array.from(seen.values());\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n",
3061
+ "sourceCode": "import { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\nimport { EUI_SIGNAL_MAP, EUI_SELECTOR_TO_CLASS } from './signal-map';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\ninterface Edit {\n start: number;\n end: number;\n replacement: string;\n}\n\n/**\n * Migrates consumer code that accesses EUI component instances programmatically.\n *\n * When EUI migrated @Input() → input(), @ViewChild() → viewChild(), and\n * @ContentChild() → contentChild(), properties that were plain class fields\n * became signal getters. Consumers who access them via @ViewChild need to\n * change `comp.prop` to `comp.prop()`.\n *\n * This schematic:\n * 1. Finds @ViewChild / @ContentChild fields typed as EUI component classes\n * 2. Resolves untyped @ViewChild('refName') by reading the component template\n * and matching #refName to an EUI element tag/selector\n * 3. Tracks which local variable names hold EUI component references (incl. aliases)\n * 4. Rewrites property accesses on those references: `ref.prop` → `ref.prop()`\n * (only for properties listed in the signal map)\n * 5. Does NOT touch template bindings — those are unaffected by signal migrations\n */\nexport function migrateToSignals(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let fileCount = 0;\n let propCount = 0;\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n if (!path.endsWith('.ts')) return;\n // Skip spec files — test code may use different patterns\n if (path.endsWith('.spec.ts')) return;\n\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n\n // Fast bail: file must mention at least one EUI class name or have\n // ViewChild/ContentChild with string refs (which might reference EUI elements)\n if (!hasEuiReference(original) && !hasStringRefQuery(original)) return;\n\n const result = migrateTypeScript(original, path, tree, context); if (result.source !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would migrate ${result.editCount} signal property access(es) in ${path}`);\n } else {\n tree.overwrite(path, result.source);\n }\n fileCount++;\n propCount += result.editCount;\n }\n });\n\n context.logger.info(`Migrated ${propCount} property access(es) to signal getters in ${fileCount} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\n/** Quick check: does the file reference any EUI class name from the signal map? */\nfunction hasEuiReference(source: string): boolean {\n for (const className of Object.keys(EUI_SIGNAL_MAP)) {\n if (source.includes(className)) return true;\n }\n return false;\n}\n\n/** Quick check: does the file have @ViewChild('...') or @ContentChild('...') with string refs? */\nfunction hasStringRefQuery(source: string): boolean {\n return /(@ViewChild|@ContentChild|@ViewChildren|@ContentChildren)\\s*\\(\\s*['\"]/.test(source);\n}\n\ninterface MigrateResult {\n source: string;\n editCount: number;\n}\n\nfunction migrateTypeScript(source: string, filePath: string, tree: Tree, context: SchematicContext): MigrateResult {\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: Edit[] = [];\n\n // Phase 0: Build template ref map (#refName → EUI class name) from the component's template\n const templateRefMap = buildTemplateRefMap(sourceFile, source, filePath, tree);\n\n // Phase 1: Collect all variables/properties typed as EUI classes\n const euiRefs = collectEuiReferences(sourceFile, source, templateRefMap);\n\n if (euiRefs.size === 0) {\n return { source, editCount: 0 };\n }\n\n // Phase 2: Find property accesses on those references and rewrite signal props\n const visitNode = (node: ts.Node): void => {\n // Match: ref.prop (PropertyAccessExpression)\n if (ts.isPropertyAccessExpression(node)) {\n const rewrite = tryRewritePropertyAccess(node, euiRefs, sourceFile, source);\n if (rewrite) {\n edits.push(rewrite);\n }\n }\n ts.forEachChild(node, visitNode);\n };\n\n ts.forEachChild(sourceFile, visitNode);\n\n if (edits.length === 0) {\n return { source, editCount: 0 };\n }\n\n return {\n source: applyEdits(source, edits),\n editCount: edits.length,\n };\n}\n\n/**\n * Scans the source file for class properties or variables typed as EUI component classes.\n * Returns a map: localName → Set of signal property names.\n *\n * Handles:\n * @ViewChild(EuiXxxComponent) name: EuiXxxComponent;\n * @ViewChild(EuiXxxComponent) name!: EuiXxxComponent;\n * @ViewChild('ref') name: EuiXxxComponent;\n * @ContentChild(EuiXxxComponent) name: EuiXxxComponent;\n * @ViewChild('ref') name: ElementRef; (resolved via template #ref → EUI selector)\n * someVar: EuiXxxComponent; (typed class field, even without decorator)\n * const alias = this.viewChildRef; (alias tracking)\n */\nfunction collectEuiReferences(\n sourceFile: ts.SourceFile,\n source: string,\n templateRefMap: Map<string, string>,\n): Map<string, ReadonlySet<string>> {\n const refs = new Map<string, ReadonlySet<string>>();\n\n // Pass 1: collect directly-typed references\n const visitDirect = (node: ts.Node): void => {\n // Class property declarations\n if (ts.isPropertyDeclaration(node) && node.name && ts.isIdentifier(node.name)) {\n const euiClass = resolveEuiClassName(node, sourceFile, templateRefMap);\n if (euiClass && EUI_SIGNAL_MAP[euiClass]) {\n refs.set(node.name.text, EUI_SIGNAL_MAP[euiClass]);\n }\n }\n\n // Variable declarations (const x: EuiXxxComponent = ...)\n if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) {\n const typeName = getTypeAnnotationName(node.type);\n if (typeName && EUI_SIGNAL_MAP[typeName]) {\n refs.set(node.name.text, EUI_SIGNAL_MAP[typeName]);\n }\n }\n\n // Parameter declarations (method(ref: EuiXxxComponent))\n if (ts.isParameter(node) && ts.isIdentifier(node.name)) {\n const typeName = getTypeAnnotationName(node.type);\n if (typeName && EUI_SIGNAL_MAP[typeName]) {\n refs.set(node.name.text, EUI_SIGNAL_MAP[typeName]);\n }\n }\n\n ts.forEachChild(node, visitDirect);\n };\n\n ts.forEachChild(sourceFile, visitDirect);\n\n // Pass 2: track aliases — variables assigned from already-tracked references\n // e.g. const alias = this.viewChildRef; or const alias = someParam;\n const visitAlias = (node: ts.Node): void => {\n if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) {\n // Skip if already tracked (has explicit type annotation)\n if (refs.has(node.name.text)) {\n ts.forEachChild(node, visitAlias);\n return;\n }\n\n const aliasedRef = resolveRefName(node.initializer);\n if (aliasedRef && refs.has(aliasedRef)) {\n refs.set(node.name.text, refs.get(aliasedRef)!);\n }\n }\n\n ts.forEachChild(node, visitAlias);\n };\n\n ts.forEachChild(sourceFile, visitAlias);\n return refs;\n}\n\n/**\n * Resolves the EUI class name from a property declaration.\n * Checks: type annotation → decorator class argument → template ref map.\n */\nfunction resolveEuiClassName(\n node: ts.PropertyDeclaration,\n sourceFile: ts.SourceFile,\n templateRefMap: Map<string, string>,\n): string | null {\n // Check type annotation first\n const typeName = getTypeAnnotationName(node.type);\n if (typeName && EUI_SIGNAL_MAP[typeName]) {\n return typeName;\n }\n\n // Check generic type argument: ElementRef<EuiXxxComponent>\n if (node.type && ts.isTypeReferenceNode(node.type) && node.type.typeArguments?.length) {\n const genericArg = getTypeAnnotationName(node.type.typeArguments[0]);\n if (genericArg && EUI_SIGNAL_MAP[genericArg]) {\n return genericArg;\n }\n }\n\n // Check decorator arguments: @ViewChild(EuiXxxComponent) or @ViewChild('refName')\n if (node.modifiers) {\n for (const modifier of node.modifiers) {\n if (!ts.isDecorator(modifier)) continue;\n if (!ts.isCallExpression(modifier.expression)) continue;\n\n const decoratorName = getDecoratorName(modifier.expression);\n if (!decoratorName) continue;\n if (!['ViewChild', 'ViewChildren', 'ContentChild', 'ContentChildren'].includes(decoratorName)) continue;\n\n const args = modifier.expression.arguments;\n if (args.length > 0) {\n const firstArg = args[0];\n\n // @ViewChild(EuiXxxComponent) — class reference\n if (ts.isIdentifier(firstArg) && EUI_SIGNAL_MAP[firstArg.text]) {\n return firstArg.text;\n }\n\n // @ViewChild('refName') — string template ref, resolve via template\n if (ts.isStringLiteralLike(firstArg)) {\n const refName = firstArg.text;\n const euiClass = templateRefMap.get(refName);\n if (euiClass && EUI_SIGNAL_MAP[euiClass]) {\n return euiClass;\n }\n }\n }\n }\n }\n\n return null;\n}\n\nfunction getDecoratorName(callExpr: ts.CallExpression): string | null {\n if (ts.isIdentifier(callExpr.expression)) {\n return callExpr.expression.text;\n }\n return null;\n}\n\nfunction getTypeAnnotationName(typeNode: ts.TypeNode | undefined): string | null {\n if (!typeNode) return null;\n if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {\n return typeNode.typeName.text;\n }\n return null;\n}\n\n/**\n * Checks if a property access `ref.prop` should be rewritten to `ref.prop()`.\n *\n * We rewrite if:\n * - `ref` is tracked in euiRefs\n * - `prop` is in the signal set for that EUI class\n * - The access is NOT already a call expression (not `ref.prop()` or `ref.prop(args)`)\n * - The access is NOT a write target (not `ref.prop = value`)\n * - The access is NOT a template binding argument (those are unaffected)\n */\nfunction tryRewritePropertyAccess(\n node: ts.PropertyAccessExpression,\n euiRefs: Map<string, ReadonlySet<string>>,\n sourceFile: ts.SourceFile,\n source: string,\n): Edit | null {\n const propName = node.name.text;\n\n // Resolve the reference name — handle `this.ref.prop` and `ref.prop`\n const refName = resolveRefName(node.expression);\n if (!refName) return null;\n\n const signalProps = euiRefs.get(refName);\n if (!signalProps || !signalProps.has(propName)) return null;\n\n // Skip if parent is already a call expression on this property: ref.prop(...)\n if (ts.isCallExpression(node.parent) && node.parent.expression === node) {\n return null;\n }\n\n // Skip if this is the left side of an assignment: ref.prop = value\n if (ts.isBinaryExpression(node.parent) &&\n node.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken &&\n node.parent.left === node) {\n return null;\n }\n\n // Skip if this is a compound assignment: ref.prop += value, ref.prop &&= value, etc.\n if (ts.isBinaryExpression(node.parent) && isAssignmentOperator(node.parent.operatorToken.kind) && node.parent.left === node) {\n return null;\n }\n\n // Skip if this is used with delete: delete ref.prop\n if (ts.isDeleteExpression(node.parent) && node.parent.expression === node) {\n return null;\n }\n\n // Skip if inside a typeof: typeof ref.prop\n if (ts.isTypeOfExpression(node.parent) && node.parent.expression === node) {\n return null;\n }\n\n // Skip property access chains that go deeper: ref.prop.something → ref.prop().something\n // This IS correct — we still want to add ()\n\n // Rewrite: insert () after the property name\n const end = node.name.getEnd();\n return {\n start: end,\n end: end,\n replacement: '()',\n };\n}\n\nfunction isAssignmentOperator(kind: ts.SyntaxKind): boolean {\n return kind >= ts.SyntaxKind.FirstAssignment && kind <= ts.SyntaxKind.LastAssignment;\n}\n\n/**\n * Resolves a reference name from an expression.\n * Handles: `ref`, `this.ref`, `this._ref`\n */\nfunction resolveRefName(expr: ts.Expression): string | null {\n // Direct identifier: ref.prop\n if (ts.isIdentifier(expr)) {\n return expr.text;\n }\n\n // this.ref.prop\n if (ts.isPropertyAccessExpression(expr) && expr.expression.kind === ts.SyntaxKind.ThisKeyword) {\n return expr.name.text;\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Template ref resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Builds a map of template reference variable names (#ref) to EUI class names.\n *\n * Parses the component's template (inline or external) and finds elements like:\n * <eui-alert #myAlert> → myAlert → EuiAlertComponent\n * <input euiAutocomplete #ac> → ac → EuiAutocompleteComponent\n *\n * Returns Map<refName, euiClassName>.\n */\nfunction buildTemplateRefMap(\n sourceFile: ts.SourceFile,\n source: string,\n filePath: string,\n tree: Tree,\n): Map<string, string> {\n const refMap = new Map<string, string>();\n\n // Find @Component decorator\n const componentMeta = findComponentMetadata(sourceFile, source);\n if (!componentMeta) return refMap;\n\n let templateSource: string | null = null;\n\n if (componentMeta.inlineTemplate !== null) {\n templateSource = componentMeta.inlineTemplate;\n } else if (componentMeta.templateUrl) {\n // Resolve templateUrl relative to the TS file\n const dir = filePath.substring(0, filePath.lastIndexOf('/'));\n const templatePath = `${dir}/${componentMeta.templateUrl}`;\n const buffer = tree.read(templatePath);\n if (buffer) {\n templateSource = buffer.toString('utf-8');\n }\n }\n\n if (!templateSource) return refMap;\n\n // Parse the template and collect #refs\n try {\n const parsed = parseTemplate(templateSource, '', { preserveWhitespaces: true });\n collectTemplateRefs(parsed.nodes, refMap);\n } catch {\n // Template parse failure — silently skip (might be malformed or use third-party syntax)\n }\n\n return refMap;\n}\n\n/**\n * Extracts @Component metadata: templateUrl or inline template content.\n */\nfunction findComponentMetadata(\n sourceFile: ts.SourceFile,\n source: string,\n): { templateUrl: string | null; inlineTemplate: string | null } | null {\n let result: { templateUrl: string | null; inlineTemplate: string | null } | null = null;\n\n const visit = (node: ts.Node): void => {\n if (result) return;\n\n if (ts.isDecorator(node) && ts.isCallExpression(node.expression)) {\n const name = getDecoratorName(node.expression);\n if (name !== 'Component') {\n ts.forEachChild(node, visit);\n return;\n }\n\n const args = node.expression.arguments;\n if (args.length === 0 || !ts.isObjectLiteralExpression(args[0])) {\n ts.forEachChild(node, visit);\n return;\n }\n\n const meta: { templateUrl: string | null; inlineTemplate: string | null } = {\n templateUrl: null,\n inlineTemplate: null,\n };\n\n for (const prop of args[0].properties) {\n if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) continue;\n\n if (prop.name.text === 'templateUrl') {\n const init = unwrapExpression(prop.initializer);\n if (ts.isStringLiteralLike(init)) {\n meta.templateUrl = init.text;\n }\n }\n\n if (prop.name.text === 'template') {\n const init = unwrapExpression(prop.initializer);\n if (ts.isStringLiteralLike(init)) {\n meta.inlineTemplate = init.text;\n } else if (ts.isNoSubstitutionTemplateLiteral(init)) {\n meta.inlineTemplate = init.text;\n }\n }\n }\n\n result = meta;\n return;\n }\n\n ts.forEachChild(node, visit);\n };\n\n ts.forEachChild(sourceFile, visit);\n return result;\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\n/**\n * Recursively walks template AST nodes, collecting #refName → EUI class name mappings.\n *\n * For each element with a template reference (#ref), we try to resolve its EUI class:\n * 1. Match element tag name against EUI_SELECTOR_TO_CLASS (e.g. 'eui-alert' → EuiAlertComponent)\n * 2. Match element attributes against attribute selectors (e.g. [euiAutocomplete] → EuiAutocompleteComponent)\n */\nfunction collectTemplateRefs(nodes: TmplAstNode[], refMap: Map<string, string>): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.references && node.references.length > 0) {\n const euiClass = resolveElementToEuiClass(node);\n if (euiClass) {\n for (const ref of node.references) {\n refMap.set(ref.name, euiClass);\n }\n }\n }\n collectTemplateRefs(node.children, refMap);\n }\n }\n}\n\n/**\n * Given a template element, resolves it to an EUI class name by checking:\n * 1. Tag name: <eui-alert> → look up 'eui-alert' in EUI_SELECTOR_TO_CLASS\n * 2. Attribute selectors: <input euiAutocomplete> → look up 'input[euiAutocomplete]' or '[euiAutocomplete]'\n */\nfunction resolveElementToEuiClass(element: TmplAstElement): string | null {\n const tag = element.name;\n\n // Direct tag match: eui-alert, eui-dialog, etc.\n if (EUI_SELECTOR_TO_CLASS[tag]) {\n return EUI_SELECTOR_TO_CLASS[tag];\n }\n\n // Attribute selector match: check all attributes against known patterns\n const allAttrNames = [\n ...element.attributes.map((a) => a.name),\n ...element.inputs.map((i) => i.name),\n ];\n\n for (const attrName of allAttrNames) {\n // Try tag[attr] pattern first (e.g. 'button[euiButton]', 'input[euiAutocomplete]')\n const tagAttrSelector = `${tag}[${attrName}]`;\n if (EUI_SELECTOR_TO_CLASS[tagAttrSelector]) {\n return EUI_SELECTOR_TO_CLASS[tagAttrSelector];\n }\n\n // Try [attr] pattern (e.g. '[euiTooltip]', '[euiLoading]')\n const attrSelector = `[${attrName}]`;\n if (EUI_SELECTOR_TO_CLASS[attrSelector]) {\n return EUI_SELECTOR_TO_CLASS[attrSelector];\n }\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Edit helpers\n// ---------------------------------------------------------------------------\n\nfunction applyEdits(source: string, edits: Edit[]): string {\n const unique = deduplicateEdits(edits);\n let result = source;\n for (const edit of unique.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n\nfunction deduplicateEdits(edits: Edit[]): Edit[] {\n const seen = new Map<string, Edit>();\n for (const edit of edits) {\n const key = `${edit.start}:${edit.end}`;\n seen.set(key, edit);\n }\n return Array.from(seen.values());\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n",
3062
3062
  "displayName": "Schema",
3063
3063
  "properties": [
3064
3064
  {
@@ -3070,7 +3070,7 @@
3070
3070
  "indexKey": "",
3071
3071
  "optional": true,
3072
3072
  "description": "",
3073
- "line": 18,
3073
+ "line": 9,
3074
3074
  "rawdescription": "\n"
3075
3075
  },
3076
3076
  {
@@ -3082,7 +3082,7 @@
3082
3082
  "indexKey": "",
3083
3083
  "optional": true,
3084
3084
  "description": "",
3085
- "line": 17,
3085
+ "line": 8,
3086
3086
  "rawdescription": "\n"
3087
3087
  }
3088
3088
  ],
@@ -3100,12 +3100,12 @@
3100
3100
  },
3101
3101
  {
3102
3102
  "name": "Schema",
3103
- "id": "interface-Schema-d36032102ed30a7ada1e3d36bb9ca41b7234b855760cac9783a25818f7ffe2097e1ebd8e808b574ddec0760f827272f6cd561f45f3eb1578f87f39ee2633730a-20",
3104
- "file": "packages/core/schematics/migrate-eui-tooltip/index.ts",
3103
+ "id": "interface-Schema-817c4b549cc3eab9fcf4936acab2e71182d90a4480369f5c003cbbda10792efa587ad99d65acf049f64e7030e32589e4fabc4a6d7a5621e4fe54725ef91dc9e8-20",
3104
+ "file": "packages/core/schematics/migrate-to-standalone/index.ts",
3105
3105
  "deprecated": false,
3106
3106
  "deprecationMessage": "",
3107
3107
  "type": "interface",
3108
- "sourceCode": "import { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\ninterface Edit {\n start: number;\n end: number;\n replacement: string;\n}\n\nconst OLD_CLASS = 'EuiTooltipConfig';\nconst NEW_INTERFACE = 'EuiTooltipInterface';\n\nexport function migrateEuiTooltip(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let fileCount = 0;\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n if (!path.endsWith('.ts')) return;\n\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(OLD_CLASS)) return;\n\n const result = migrateTypeScript(original, path, context);\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would migrate EuiTooltipConfig → EuiTooltipInterface in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n fileCount++;\n }\n });\n\n context.logger.info(`Migrated EuiTooltipConfig → EuiTooltipInterface in ${fileCount} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction migrateTypeScript(source: string, filePath: string, context: SchematicContext): string {\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: Edit[] = [];\n\n // Track if EuiTooltipInterface is already imported\n let hasInterfaceImport = false;\n let classImportDecl: ts.ImportDeclaration | null = null;\n let classImportModuleSpecifier: string | null = null;\n\n // First pass: analyze imports\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const namedBindings = stmt.importClause?.namedBindings;\n if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;\n\n for (const specifier of namedBindings.elements) {\n if (specifier.name.text === NEW_INTERFACE) {\n hasInterfaceImport = true;\n }\n if (specifier.name.text === OLD_CLASS) {\n classImportDecl = stmt;\n classImportModuleSpecifier = (stmt.moduleSpecifier as ts.StringLiteral).text;\n }\n }\n }\n\n // Second pass: handle import declarations\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const namedBindings = stmt.importClause?.namedBindings;\n if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;\n\n const specifiers = namedBindings.elements;\n const classSpecifier = specifiers.find((s) => s.name.text === OLD_CLASS);\n if (!classSpecifier) continue;\n\n if (hasInterfaceImport) {\n // EuiTooltipInterface is already imported elsewhere → remove EuiTooltipConfig from this import\n removeImportSpecifier(namedBindings, classSpecifier, sourceFile, edits);\n } else {\n // Rename EuiTooltipConfig → EuiTooltipInterface in the import\n edits.push({\n start: classSpecifier.name.getStart(sourceFile),\n end: classSpecifier.name.getEnd(),\n replacement: NEW_INTERFACE,\n });\n hasInterfaceImport = true;\n }\n }\n\n // Third pass: replace `new EuiTooltipConfig(...)` → spread/cast to interface\n const visitNewExpressions = (node: ts.Node): void => {\n if (ts.isNewExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === OLD_CLASS) {\n const args = node.arguments;\n if (args && args.length === 1) {\n const arg = args[0];\n // `new EuiTooltipConfig({ ... })` → `{ ... } as EuiTooltipInterface`\n // But if the argument is just a variable, we keep it: `varName as EuiTooltipInterface`\n const argText = source.slice(arg.getStart(sourceFile), arg.getEnd());\n\n if (ts.isObjectLiteralExpression(arg)) {\n // Inline object: `new EuiTooltipConfig({ x: 1 })` → `{ x: 1 }`\n edits.push({\n start: node.getStart(sourceFile),\n end: node.getEnd(),\n replacement: argText,\n });\n } else {\n // Variable or expression: `new EuiTooltipConfig(opts)` → `opts`\n edits.push({\n start: node.getStart(sourceFile),\n end: node.getEnd(),\n replacement: argText,\n });\n }\n } else if (!args || args.length === 0) {\n // `new EuiTooltipConfig()` → `{} as EuiTooltipInterface`\n edits.push({\n start: node.getStart(sourceFile),\n end: node.getEnd(),\n replacement: `{} as ${NEW_INTERFACE}`,\n });\n }\n return; // don't recurse into children we've already replaced\n }\n ts.forEachChild(node, visitNewExpressions);\n };\n\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) {\n visitNewExpressions(stmt);\n }\n }\n\n // Fourth pass: rename all remaining identifier references (type annotations, etc.)\n const visitRefs = (node: ts.Node): void => {\n if (ts.isImportDeclaration(node)) return;\n // Skip nodes we already covered in new expressions\n if (ts.isNewExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === OLD_CLASS) return;\n\n if (ts.isIdentifier(node) && node.text === OLD_CLASS) {\n // Ensure this is not part of an import declaration\n if (!isPartOfImport(node)) {\n edits.push({\n start: node.getStart(sourceFile),\n end: node.getEnd(),\n replacement: NEW_INTERFACE,\n });\n }\n }\n ts.forEachChild(node, visitRefs);\n };\n\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) {\n visitRefs(stmt);\n }\n }\n\n return applyEdits(source, edits);\n}\n\nfunction isPartOfImport(node: ts.Node): boolean {\n let current: ts.Node | undefined = node.parent;\n while (current) {\n if (ts.isImportDeclaration(current)) return true;\n current = current.parent;\n }\n return false;\n}\n\nfunction removeImportSpecifier(\n namedImports: ts.NamedImports,\n specifier: ts.ImportSpecifier,\n sourceFile: ts.SourceFile,\n edits: Edit[],\n): void {\n const elements = namedImports.elements;\n if (elements.length === 1) {\n // Remove the entire import declaration\n const importDecl = namedImports.parent.parent;\n let end = importDecl.getEnd();\n // Also remove trailing newline if present\n const fullText = sourceFile.getFullText();\n if (fullText[end] === '\\n') end++;\n edits.push({\n start: importDecl.getStart(sourceFile),\n end,\n replacement: '',\n });\n } else {\n // Remove just this specifier with surrounding comma/whitespace\n const idx = elements.indexOf(specifier);\n let start: number;\n let end: number;\n if (idx < elements.length - 1) {\n // Not the last → remove from this specifier start to next specifier start\n start = specifier.getStart(sourceFile);\n end = elements[idx + 1].getStart(sourceFile);\n } else {\n // Last element → remove from previous element end to this end\n start = elements[idx - 1].getEnd();\n end = specifier.getEnd();\n }\n edits.push({ start, end, replacement: '' });\n }\n}\n\nfunction applyEdits(source: string, edits: Edit[]): string {\n const unique = deduplicateEdits(edits);\n let result = source;\n for (const edit of unique.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n\nfunction deduplicateEdits(edits: Edit[]): Edit[] {\n const seen = new Map<string, Edit>();\n for (const edit of edits) {\n const key = `${edit.start}:${edit.end}`;\n seen.set(key, edit);\n }\n return Array.from(seen.values());\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n",
3108
+ "sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface ModuleMapping {\n importPath: string;\n selectors: Record<string, string>;\n}\n\nconst MODULE_MAPPINGS: Record<string, ModuleMapping> = {\n EuiAccordionModule: {\n importPath: '@eui/components/eui-accordion',\n selectors: {\n 'eui-accordion': 'EuiAccordionComponent',\n 'eui-accordion-item': 'EuiAccordionItemComponent',\n euiAccordionItemHeader: 'EuiAccordionItemHeaderDirective',\n },\n },\n EuiAlertModule: {\n importPath: '@eui/components/eui-alert',\n selectors: {\n 'eui-alert': 'EuiAlertComponent',\n euiAlert: 'EuiAlertComponent',\n 'eui-alert-title': 'EuiAlertTitleComponent',\n },\n },\n EuiAutocompleteModule: {\n importPath: '@eui/components/eui-autocomplete',\n selectors: {\n 'eui-autocomplete': 'EuiAutocompleteComponent',\n euiAutocomplete: 'EuiAutocompleteComponent',\n 'eui-autocomplete-option': 'EuiAutocompleteOptionComponent',\n 'eui-autocomplete-option-group': 'EuiAutocompleteOptionGroupComponent',\n 'eui-autocomplete-panel': 'EuiAutocompletePanelComponent',\n },\n },\n EuiAvatarModule: {\n importPath: '@eui/components/eui-avatar',\n selectors: {\n 'eui-avatar': 'EuiAvatarComponent',\n euiAvatar: 'EuiAvatarComponent',\n },\n },\n EuiBadgeModule: {\n importPath: '@eui/components/eui-badge',\n selectors: {\n 'eui-badge': 'EuiBadgeComponent',\n euiBadge: 'EuiBadgeComponent',\n },\n },\n EuiBlockContentModule: {\n importPath: '@eui/components/eui-block-content',\n selectors: {\n 'eui-block-content': 'EuiBlockContentComponent',\n },\n },\n EuiBreadcrumbModule: {\n importPath: '@eui/components/eui-breadcrumb',\n selectors: {\n 'eui-breadcrumb': 'EuiBreadcrumbComponent',\n },\n },\n EuiButtonModule: {\n importPath: '@eui/components/eui-button',\n selectors: {\n euiButton: 'EuiButtonComponent',\n },\n },\n EuiButtonGroupModule: {\n importPath: '@eui/components/eui-button-group',\n selectors: {\n 'eui-button-group': 'EuiButtonGroupComponent',\n },\n },\n EuiCardModule: {\n importPath: '@eui/components/eui-card',\n selectors: {\n 'eui-card': 'EuiCardComponent',\n 'eui-card-header': 'EuiCardHeaderComponent',\n 'eui-card-header-title': 'EuiCardHeaderTitleComponent',\n 'eui-card-content': 'EuiCardContentComponent',\n 'eui-card-footer': 'EuiCardFooterComponent',\n 'eui-card-media': 'EuiCardMediaComponent',\n },\n },\n EuiChipModule: {\n importPath: '@eui/components/eui-chip',\n selectors: {\n 'eui-chip': 'EuiChipComponent',\n euiChip: 'EuiChipComponent',\n },\n },\n EuiChipListModule: {\n importPath: '@eui/components/eui-chip-list',\n selectors: {\n 'eui-chip-list': 'EuiChipListComponent',\n },\n },\n EuiChipGroupModule: {\n importPath: '@eui/components/eui-chip-group',\n selectors: {\n 'eui-chip-group': 'EuiChipGroupComponent',\n },\n },\n EuiDashboardCardModule: {\n importPath: '@eui/components/eui-dashboard-card',\n selectors: {\n 'eui-dashboard-card': 'EuiDashboardCardComponent',\n 'eui-dashboard-card-content': 'EuiDashboardCardContentComponent',\n 'eui-dashboard-card-content-header': 'EuiDashboardCardContentHeaderComponent',\n 'eui-dashboard-card-content-body': 'EuiDashboardCardContentBodyComponent',\n 'eui-dashboard-card-content-footer': 'EuiDashboardCardContentFooterComponent',\n },\n },\n EuiDashboardButtonModule: {\n importPath: '@eui/components/eui-dashboard-card',\n selectors: {\n 'eui-dashboard-card': 'EuiDashboardCardComponent',\n 'eui-dashboard-card-content': 'EuiDashboardCardContentComponent',\n 'eui-dashboard-card-content-header': 'EuiDashboardCardContentHeaderComponent',\n 'eui-dashboard-card-content-body': 'EuiDashboardCardContentBodyComponent',\n 'eui-dashboard-card-content-footer': 'EuiDashboardCardContentFooterComponent',\n },\n },\n EuiDatepickerModule: {\n importPath: '@eui/components/eui-datepicker',\n selectors: {\n 'eui-datepicker': 'EuiDatepickerComponent',\n },\n },\n EuiDateRangeSelectorModule: {\n importPath: '@eui/components/eui-date-range-selector',\n selectors: {\n 'eui-date-range-selector': 'EuiDateRangeSelectorComponent',\n },\n },\n EuiDialogModule: {\n importPath: '@eui/components/eui-dialog',\n selectors: {\n 'eui-dialog': 'EuiDialogComponent',\n 'eui-dialog-header': 'EuiDialogHeaderDirective',\n 'eui-dialog-footer': 'EuiDialogFooterDirective',\n 'eui-dialog-container': 'EuiDialogContainerComponent',\n },\n },\n EuiDisableContentModule: {\n importPath: '@eui/components/eui-disable-content',\n selectors: {\n 'eui-disable-content': 'EuiDisableContentComponent',\n },\n },\n EuiDiscussionThreadModule: {\n importPath: '@eui/components/eui-discussion-thread',\n selectors: {\n 'eui-discussion-thread': 'EuiDiscussionThreadComponent',\n 'eui-discussion-thread-item': 'EuiDiscussionThreadItemComponent',\n },\n },\n EuiDropdownModule: {\n importPath: '@eui/components/eui-dropdown',\n selectors: {\n 'eui-dropdown': 'EuiDropdownComponent',\n },\n },\n EuiFeedbackMessageModule: {\n importPath: '@eui/components/eui-feedback-message',\n selectors: {\n 'eui-feedback-message': 'EuiFeedbackMessageComponent',\n },\n },\n EuiFieldsetModule: {\n importPath: '@eui/components/eui-fieldset',\n selectors: {\n 'eui-fieldset': 'EuiFieldsetComponent',\n euiFieldsetLabelRightContent: 'EuiFieldsetLabelRightContentTagDirective',\n euiFieldsetLabelExtraContent: 'EuiFieldsetLabelExtraContentTagDirective',\n },\n },\n EuiFileUploadModule: {\n importPath: '@eui/components/eui-file-upload',\n selectors: {\n 'eui-file-upload': 'EuiFileUploadComponent',\n },\n },\n EuiGrowlModule: {\n importPath: '@eui/components/eui-growl',\n selectors: {\n 'eui-growl': 'EuiGrowlComponent',\n },\n },\n EuiIconModule: {\n importPath: '@eui/components/eui-icon',\n selectors: {\n 'eui-icon-svg': 'EuiIconSvgComponent',\n euiIconSvg: 'EuiIconSvgComponent',\n },\n },\n EuiIconButtonModule: {\n importPath: '@eui/components/eui-icon-button',\n selectors: {\n 'eui-icon-button': 'EuiIconButtonComponent',\n },\n },\n EuiIconToggleModule: {\n importPath: '@eui/components/eui-icon-toggle',\n selectors: {\n 'eui-icon-toggle': 'EuiIconToggleComponent',\n },\n },\n EuiInputCheckboxModule: {\n importPath: '@eui/components/eui-input-checkbox',\n selectors: {\n euiInputCheckBox: 'EuiInputCheckboxComponent',\n },\n },\n EuiInputGroupModule: {\n importPath: '@eui/components/eui-input-group',\n selectors: {\n euiInputGroup: 'EuiInputGroupComponent',\n 'eui-input-group-addon': 'EuiInputGroupAddOnComponent',\n euiInputGroupAddOn: 'EuiInputGroupAddOnComponent',\n 'eui-input-group-addon-item': 'EuiInputGroupAddOnItemComponent',\n euiInputGroupAddOnItem: 'EuiInputGroupAddOnItemComponent',\n },\n },\n EuiInputNumberModule: {\n importPath: '@eui/components/eui-input-number',\n selectors: {\n euiInputNumber: 'EuiInputNumberComponent',\n },\n },\n EuiInputRadioModule: {\n importPath: '@eui/components/eui-input-radio',\n selectors: {\n euiInputRadio: 'EuiInputRadioComponent',\n },\n },\n EuiInputTextModule: {\n importPath: '@eui/components/eui-input-text',\n selectors: {\n euiInputText: 'EuiInputTextComponent',\n },\n },\n EuiLabelModule: {\n importPath: '@eui/components/eui-label',\n selectors: {\n 'eui-label': 'EuiLabelComponent',\n euiLabel: 'EuiLabelComponent',\n },\n },\n EuiListModule: {\n importPath: '@eui/components/eui-list',\n selectors: {\n 'eui-list': 'EuiListComponent',\n euiList: 'EuiListComponent',\n 'eui-list-item': 'EuiListItemComponent',\n euiListItem: 'EuiListItemComponent',\n },\n },\n EuiMenuModule: {\n importPath: '@eui/components/eui-menu',\n selectors: {\n 'eui-menu': 'EuiMenuComponent',\n 'eui-menu-item': 'EuiMenuItemComponent',\n },\n },\n EuiMessageBoxModule: {\n importPath: '@eui/components/eui-message-box',\n selectors: {\n 'eui-message-box': 'EuiMessageBoxComponent',\n 'eui-message-box-footer': 'EuiMessageBoxFooterDirective',\n },\n },\n EuiOverlayModule: {\n importPath: '@eui/components/eui-overlay',\n selectors: {\n 'eui-overlay': 'EuiOverlayComponent',\n },\n },\n EuiPageModule: {\n importPath: '@eui/components/eui-page',\n selectors: {\n 'eui-page': 'EuiPageComponent',\n },\n },\n EuiPaginatorModule: {\n importPath: '@eui/components/eui-paginator',\n selectors: {\n 'eui-paginator': 'EuiPaginatorComponent',\n },\n },\n EuiPopoverModule: {\n importPath: '@eui/components/eui-popover',\n selectors: {\n 'eui-popover': 'EuiPopoverComponent',\n },\n },\n EuiProgressBarModule: {\n importPath: '@eui/components/eui-progress-bar',\n selectors: {\n 'eui-progress-bar': 'EuiProgressBarComponent',\n },\n },\n EuiProgressCircleModule: {\n importPath: '@eui/components/eui-progress-circle',\n selectors: {\n 'eui-progress-circle': 'EuiProgressCircleComponent',\n },\n },\n EuiSelectModule: {\n importPath: '@eui/components/eui-select',\n selectors: {\n euiSelect: 'EuiSelectComponent',\n },\n },\n EuiSidebarMenuModule: {\n importPath: '@eui/components/eui-sidebar-menu',\n selectors: {\n 'eui-sidebar-menu': 'EuiSidebarMenuComponent',\n },\n },\n EuiSkeletonModule: {\n importPath: '@eui/components/eui-skeleton',\n selectors: {\n 'eui-skeleton': 'EuiSkeletonComponent',\n },\n },\n EuiSlideToggleModule: {\n importPath: '@eui/components/eui-slide-toggle',\n selectors: {\n 'eui-slide-toggle': 'EuiSlideToggleComponent',\n },\n },\n EuiTableModule: {\n importPath: '@eui/components/eui-table',\n selectors: {\n 'eui-table': 'EuiTableComponent',\n euiTable: 'EuiTableComponent',\n 'eui-table-filter': 'EuiTableFilterComponent',\n isSortable: 'EuiTableSortableColComponent',\n isStickyCol: 'EuiTableStickyColDirective',\n isHeaderSelectable: 'EuiTableSelectableHeaderComponent',\n isDataSelectable: 'EuiTableSelectableRowComponent',\n isExpandableRow: 'EuiTableExpandableRowDirective',\n },\n },\n EuiTableV2Module: {\n importPath: '@eui/components/eui-table',\n selectors: {\n 'eui-table': 'EuiTableComponent',\n euiTable: 'EuiTableComponent',\n 'eui-table-filter': 'EuiTableFilterComponent',\n isSortable: 'EuiTableSortableColComponent',\n isStickyCol: 'EuiTableStickyColDirective',\n isHeaderSelectable: 'EuiTableSelectableHeaderComponent',\n isDataSelectable: 'EuiTableSelectableRowComponent',\n isExpandableRow: 'EuiTableExpandableRowDirective',\n },\n },\n EuiTabsModule: {\n importPath: '@eui/components/eui-tabs',\n selectors: {\n 'eui-tabs': 'EuiTabsComponent',\n 'eui-tab': 'EuiTabComponent',\n 'eui-tab-header': 'EuiTabHeaderComponent',\n 'eui-tab-body': 'EuiTabBodyComponent',\n },\n },\n EuiTextAreaModule: {\n importPath: '@eui/components/eui-textarea',\n selectors: {\n euiTextArea: 'EuiTextareaComponent',\n },\n },\n EuiTimelineModule: {\n importPath: '@eui/components/eui-timeline',\n selectors: {\n 'eui-timeline': 'EuiTimelineComponent',\n 'eui-timeline-item': 'EuiTimelineItemComponent',\n },\n },\n EuiTimepickerModule: {\n importPath: '@eui/components/eui-timepicker',\n selectors: {\n 'eui-timepicker': 'EuiTimepickerComponent',\n },\n },\n EuiTreeModule: {\n importPath: '@eui/components/eui-tree',\n selectors: {\n 'eui-tree': 'EuiTreeComponent',\n },\n },\n EuiTreeListModule: {\n importPath: '@eui/components/eui-tree-list',\n selectors: {\n 'eui-tree-list': 'EuiTreeListComponent',\n 'eui-tree-list-item': 'EuiTreeListItemComponent',\n },\n },\n EuiUserProfileModule: {\n importPath: '@eui/components/eui-user-profile',\n selectors: {\n 'eui-user-profile': 'EuiUserProfileComponent',\n },\n },\n EuiWizardModule: {\n importPath: '@eui/components/eui-wizard',\n selectors: {\n 'eui-wizard': 'EuiWizardComponent',\n 'eui-wizard-step': 'EuiWizardStepComponent',\n },\n },\n EuiTooltipDirectiveModule: {\n importPath: '@eui/components/directives',\n selectors: {\n euiTooltip: 'EuiTooltipDirective',\n },\n },\n EuiTemplateDirectiveModule: {\n importPath: '@eui/components/directives',\n selectors: {\n euiTemplate: 'EuiTemplateDirective',\n },\n },\n EuiResizableDirectiveModule: {\n importPath: '@eui/components/directives',\n selectors: {\n euiResizable: 'EuiResizableDirective',\n 'eui-resizable': 'EuiResizableComponent',\n },\n },\n EuiMaxLengthDirectiveModule: {\n importPath: '@eui/components/directives',\n selectors: {\n euiEditorMaxlength: 'EuiMaxLengthDirective',\n },\n },\n EuiTruncatePipeModule: {\n importPath: '@eui/components/pipes',\n selectors: {\n euiTruncate: 'EuiTruncatePipe',\n },\n },\n EuiLayoutModule: {\n importPath: '@eui/components/layout',\n selectors: {\n 'eui-app': 'EuiAppComponent',\n 'eui-header': 'EuiHeaderComponent',\n 'eui-footer': 'EuiFooterComponent',\n 'eui-toolbar': 'EuiToolbarComponent',\n 'eui-sidebar-toggle': 'EuiSidebarToggleComponent',\n },\n },\n};\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nexport function migrateToStandalone(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n const allModuleNames = Object.keys(MODULE_MAPPINGS);\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const source = buffer.toString('utf-8');\n if (!allModuleNames.some((m) => source.includes(m))) return;\n\n const sourceFile = ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true);\n const componentDecorators = findComponentDecorators(sourceFile);\n\n for (const decorator of componentDecorators) {\n const importsNode = findImportsArrayNode(decorator);\n if (!importsNode) continue;\n\n const currentSource = tree.read(path)!.toString('utf-8');\n const modulesInArray = allModuleNames.filter((m) => hasModuleInArray(importsNode, m, source));\n if (modulesInArray.length === 0) continue;\n\n const templateSelectors = getTemplateSelectors(tree, path, decorator, source);\n const replacements = buildReplacements(modulesInArray, templateSelectors);\n if (replacements.size === 0) continue;\n\n const result = applyReplacements(currentSource, path, replacements);\n if (options.dryRun) {\n logDryRun(context, `Would replace module imports with standalone imports in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n }\n });\n\n context.logger.info('Migration to standalone imports complete.');\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction buildReplacements(moduleNames: string[], templateSelectors: Set<string>): Map<string, { components: string[]; importPath: string }> {\n const map = new Map<string, { components: string[]; importPath: string }>();\n\n for (const moduleName of moduleNames) {\n const mapping = MODULE_MAPPINGS[moduleName];\n const components: string[] = [];\n\n for (const [selector, component] of Object.entries(mapping.selectors)) {\n if (templateSelectors.has(selector)) {\n components.push(component);\n }\n }\n\n if (components.length > 0) {\n map.set(moduleName, { components: [...new Set(components)].sort(), importPath: mapping.importPath });\n }\n }\n\n return map;\n}\n\nfunction findComponentDecorators(sourceFile: ts.SourceFile): ts.Decorator[] {\n const decorators: ts.Decorator[] = [];\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node)) {\n const decs = ts.getDecorators(node);\n if (decs) {\n for (const dec of decs) {\n if (ts.isCallExpression(dec.expression) && ts.isIdentifier(dec.expression.expression) && dec.expression.expression.text === 'Component') {\n decorators.push(dec);\n }\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n return decorators;\n}\n\nfunction findImportsArrayNode(decorator: ts.Decorator): ts.ArrayLiteralExpression | undefined {\n const call = decorator.expression as ts.CallExpression;\n const metadata = call.arguments[0];\n if (!ts.isObjectLiteralExpression(metadata)) return undefined;\n\n for (const prop of metadata.properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'imports') {\n if (ts.isArrayLiteralExpression(prop.initializer)) {\n return prop.initializer;\n }\n }\n }\n return undefined;\n}\n\nfunction hasModuleInArray(array: ts.ArrayLiteralExpression, moduleName: string, source: string): boolean {\n return array.elements.some((el) => source.slice(el.getStart(), el.getEnd()).trim() === moduleName);\n}\n\nfunction getTemplateSelectors(tree: Tree, tsPath: string, decorator: ts.Decorator, source: string): Set<string> {\n const call = decorator.expression as ts.CallExpression;\n const metadata = call.arguments[0] as ts.ObjectLiteralExpression;\n const allSelectors = Object.values(MODULE_MAPPINGS).flatMap((m) => Object.keys(m.selectors));\n\n for (const prop of metadata.properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'template') {\n const init = prop.initializer;\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n return findSelectorsInTemplate(init.text, allSelectors);\n }\n }\n }\n\n for (const prop of metadata.properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'templateUrl') {\n if (ts.isStringLiteral(prop.initializer)) {\n const dir = tsPath.substring(0, tsPath.lastIndexOf('/'));\n const templateBuffer = tree.read(`${dir}/${prop.initializer.text}`);\n if (templateBuffer) {\n return findSelectorsInTemplate(templateBuffer.toString('utf-8'), allSelectors);\n }\n }\n }\n }\n\n return new Set();\n}\n\nfunction findSelectorsInTemplate(html: string, knownSelectors: string[]): Set<string> {\n const selectors = new Set<string>();\n const parsed = parseTemplate(html, '', { preserveWhitespaces: true });\n\n const visit = (nodes: TmplAstNode[]): void => {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (knownSelectors.includes(node.name)) selectors.add(node.name);\n for (const attr of node.attributes) {\n if (knownSelectors.includes(attr.name)) selectors.add(attr.name);\n }\n visit(node.children);\n }\n }\n };\n visit(parsed.nodes);\n return selectors;\n}\n\nfunction applyReplacements(source: string, filePath: string, replacements: Map<string, { components: string[]; importPath: string }>): string {\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);\n let result = replaceInImportsArray(source, sourceFile, replacements);\n result = updateEsImports(result, filePath, replacements);\n return result;\n}\n\nfunction replaceInImportsArray(source: string, sourceFile: ts.SourceFile, replacements: Map<string, { components: string[]; importPath: string }>): string {\n let result = source;\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node)) {\n const decs = ts.getDecorators(node);\n if (!decs) return;\n for (const dec of decs) {\n if (!ts.isCallExpression(dec.expression) || !ts.isIdentifier(dec.expression.expression) || dec.expression.expression.text !== 'Component') continue;\n const metadata = dec.expression.arguments[0];\n if (!ts.isObjectLiteralExpression(metadata)) continue;\n for (const prop of metadata.properties) {\n if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name) || prop.name.text !== 'imports') continue;\n if (!ts.isArrayLiteralExpression(prop.initializer)) continue;\n const newElements = prop.initializer.elements\n .map((el) => {\n const text = result.slice(el.getStart(sourceFile), el.getEnd()).trim();\n const replacement = replacements.get(text);\n return replacement ? replacement.components.join(', ') : text;\n })\n .join(', ');\n result = result.slice(0, prop.initializer.getStart(sourceFile) + 1) + newElements + result.slice(prop.initializer.getEnd() - 1);\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n return result;\n}\n\nfunction updateEsImports(source: string, filePath: string, replacements: Map<string, { components: string[]; importPath: string }>): string {\n let result = source;\n\n for (const [moduleName, { components, importPath }] of replacements) {\n const sf = ts.createSourceFile(filePath, result, ts.ScriptTarget.Latest, true);\n\n for (const stmt of sf.statements) {\n if (!ts.isImportDeclaration(stmt) || !stmt.importClause?.namedBindings || !ts.isNamedImports(stmt.importClause.namedBindings)) continue;\n const namedBindings = stmt.importClause.namedBindings;\n const importNames = namedBindings.elements.map((el) => el.name.text);\n if (!importNames.includes(moduleName)) continue;\n\n const moduleSpecifier = (stmt.moduleSpecifier as ts.StringLiteral).text;\n const remaining = importNames.filter((n) => n !== moduleName);\n const toAdd = components.filter((c) => !importNames.includes(c));\n\n if (moduleSpecifier === importPath) {\n const newNames = [...remaining, ...toAdd].sort();\n const newClause = `{ ${newNames.join(', ')} }`;\n result = result.slice(0, namedBindings.getStart(sf)) + newClause + result.slice(namedBindings.getEnd());\n } else {\n if (remaining.length > 0) {\n const newClause = `{ ${remaining.join(', ')} }`;\n result = result.slice(0, namedBindings.getStart(sf)) + newClause + result.slice(namedBindings.getEnd());\n } else {\n result = result.slice(0, stmt.getStart(sf)) + result.slice(stmt.getEnd()).replace(/^\\r?\\n/, '');\n }\n\n if (toAdd.length > 0) {\n const updatedSf = ts.createSourceFile(filePath, result, ts.ScriptTarget.Latest, true);\n const existing = updatedSf.statements.find(\n (s) => ts.isImportDeclaration(s) && ts.isStringLiteral(s.moduleSpecifier) && s.moduleSpecifier.text === importPath,\n ) as ts.ImportDeclaration | undefined;\n\n if (existing?.importClause?.namedBindings && ts.isNamedImports(existing.importClause.namedBindings)) {\n const existingNames = existing.importClause.namedBindings.elements.map((el) => el.name.text);\n const allNames = [...new Set([...existingNames, ...toAdd])].sort();\n const newClause = `{ ${allNames.join(', ')} }`;\n result = result.slice(0, existing.importClause.namedBindings.getStart(updatedSf)) + newClause + result.slice(existing.importClause.namedBindings.getEnd());\n } else {\n const newImport = `import { ${toAdd.join(', ')} } from '${importPath}';\\n`;\n result = newImport + result;\n }\n }\n }\n break;\n }\n }\n\n return result;\n}\n",
3109
3109
  "displayName": "Schema",
3110
3110
  "properties": [
3111
3111
  {
@@ -3117,7 +3117,7 @@
3117
3117
  "indexKey": "",
3118
3118
  "optional": true,
3119
3119
  "description": "",
3120
- "line": 7,
3120
+ "line": 460,
3121
3121
  "rawdescription": "\n"
3122
3122
  },
3123
3123
  {
@@ -3129,7 +3129,7 @@
3129
3129
  "indexKey": "",
3130
3130
  "optional": true,
3131
3131
  "description": "",
3132
- "line": 6,
3132
+ "line": 459,
3133
3133
  "rawdescription": "\n"
3134
3134
  }
3135
3135
  ],
@@ -3147,12 +3147,12 @@
3147
3147
  },
3148
3148
  {
3149
3149
  "name": "Schema",
3150
- "id": "interface-Schema-cac2ac952d8245647e8a48b450e8f127a36bcffb394cf2cd81fe9c0810ed02c19e5e1629dbf29a68a5c6f675aed99570e9ac04ae470e13315e20c92cc0c6c453-21",
3151
- "file": "packages/core/schematics/migrate-to-signals/index.ts",
3150
+ "id": "interface-Schema-d36032102ed30a7ada1e3d36bb9ca41b7234b855760cac9783a25818f7ffe2097e1ebd8e808b574ddec0760f827272f6cd561f45f3eb1578f87f39ee2633730a-21",
3151
+ "file": "packages/core/schematics/migrate-eui-tooltip/index.ts",
3152
3152
  "deprecated": false,
3153
3153
  "deprecationMessage": "",
3154
3154
  "type": "interface",
3155
- "sourceCode": "import { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\nimport { EUI_SIGNAL_MAP, EUI_SELECTOR_TO_CLASS } from './signal-map';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\ninterface Edit {\n start: number;\n end: number;\n replacement: string;\n}\n\n/**\n * Migrates consumer code that accesses EUI component instances programmatically.\n *\n * When EUI migrated @Input() → input(), @ViewChild() → viewChild(), and\n * @ContentChild() → contentChild(), properties that were plain class fields\n * became signal getters. Consumers who access them via @ViewChild need to\n * change `comp.prop` to `comp.prop()`.\n *\n * This schematic:\n * 1. Finds @ViewChild / @ContentChild fields typed as EUI component classes\n * 2. Resolves untyped @ViewChild('refName') by reading the component template\n * and matching #refName to an EUI element tag/selector\n * 3. Tracks which local variable names hold EUI component references (incl. aliases)\n * 4. Rewrites property accesses on those references: `ref.prop` → `ref.prop()`\n * (only for properties listed in the signal map)\n * 5. Does NOT touch template bindings — those are unaffected by signal migrations\n */\nexport function migrateToSignals(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let fileCount = 0;\n let propCount = 0;\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n if (!path.endsWith('.ts')) return;\n // Skip spec files — test code may use different patterns\n if (path.endsWith('.spec.ts')) return;\n\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n\n // Fast bail: file must mention at least one EUI class name or have\n // ViewChild/ContentChild with string refs (which might reference EUI elements)\n if (!hasEuiReference(original) && !hasStringRefQuery(original)) return;\n\n const result = migrateTypeScript(original, path, tree, context); if (result.source !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would migrate ${result.editCount} signal property access(es) in ${path}`);\n } else {\n tree.overwrite(path, result.source);\n }\n fileCount++;\n propCount += result.editCount;\n }\n });\n\n context.logger.info(`Migrated ${propCount} property access(es) to signal getters in ${fileCount} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\n/** Quick check: does the file reference any EUI class name from the signal map? */\nfunction hasEuiReference(source: string): boolean {\n for (const className of Object.keys(EUI_SIGNAL_MAP)) {\n if (source.includes(className)) return true;\n }\n return false;\n}\n\n/** Quick check: does the file have @ViewChild('...') or @ContentChild('...') with string refs? */\nfunction hasStringRefQuery(source: string): boolean {\n return /(@ViewChild|@ContentChild|@ViewChildren|@ContentChildren)\\s*\\(\\s*['\"]/.test(source);\n}\n\ninterface MigrateResult {\n source: string;\n editCount: number;\n}\n\nfunction migrateTypeScript(source: string, filePath: string, tree: Tree, context: SchematicContext): MigrateResult {\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: Edit[] = [];\n\n // Phase 0: Build template ref map (#refName → EUI class name) from the component's template\n const templateRefMap = buildTemplateRefMap(sourceFile, source, filePath, tree);\n\n // Phase 1: Collect all variables/properties typed as EUI classes\n const euiRefs = collectEuiReferences(sourceFile, source, templateRefMap);\n\n if (euiRefs.size === 0) {\n return { source, editCount: 0 };\n }\n\n // Phase 2: Find property accesses on those references and rewrite signal props\n const visitNode = (node: ts.Node): void => {\n // Match: ref.prop (PropertyAccessExpression)\n if (ts.isPropertyAccessExpression(node)) {\n const rewrite = tryRewritePropertyAccess(node, euiRefs, sourceFile, source);\n if (rewrite) {\n edits.push(rewrite);\n }\n }\n ts.forEachChild(node, visitNode);\n };\n\n ts.forEachChild(sourceFile, visitNode);\n\n if (edits.length === 0) {\n return { source, editCount: 0 };\n }\n\n return {\n source: applyEdits(source, edits),\n editCount: edits.length,\n };\n}\n\n/**\n * Scans the source file for class properties or variables typed as EUI component classes.\n * Returns a map: localName → Set of signal property names.\n *\n * Handles:\n * @ViewChild(EuiXxxComponent) name: EuiXxxComponent;\n * @ViewChild(EuiXxxComponent) name!: EuiXxxComponent;\n * @ViewChild('ref') name: EuiXxxComponent;\n * @ContentChild(EuiXxxComponent) name: EuiXxxComponent;\n * @ViewChild('ref') name: ElementRef; (resolved via template #ref → EUI selector)\n * someVar: EuiXxxComponent; (typed class field, even without decorator)\n * const alias = this.viewChildRef; (alias tracking)\n */\nfunction collectEuiReferences(\n sourceFile: ts.SourceFile,\n source: string,\n templateRefMap: Map<string, string>,\n): Map<string, ReadonlySet<string>> {\n const refs = new Map<string, ReadonlySet<string>>();\n\n // Pass 1: collect directly-typed references\n const visitDirect = (node: ts.Node): void => {\n // Class property declarations\n if (ts.isPropertyDeclaration(node) && node.name && ts.isIdentifier(node.name)) {\n const euiClass = resolveEuiClassName(node, sourceFile, templateRefMap);\n if (euiClass && EUI_SIGNAL_MAP[euiClass]) {\n refs.set(node.name.text, EUI_SIGNAL_MAP[euiClass]);\n }\n }\n\n // Variable declarations (const x: EuiXxxComponent = ...)\n if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) {\n const typeName = getTypeAnnotationName(node.type);\n if (typeName && EUI_SIGNAL_MAP[typeName]) {\n refs.set(node.name.text, EUI_SIGNAL_MAP[typeName]);\n }\n }\n\n // Parameter declarations (method(ref: EuiXxxComponent))\n if (ts.isParameter(node) && ts.isIdentifier(node.name)) {\n const typeName = getTypeAnnotationName(node.type);\n if (typeName && EUI_SIGNAL_MAP[typeName]) {\n refs.set(node.name.text, EUI_SIGNAL_MAP[typeName]);\n }\n }\n\n ts.forEachChild(node, visitDirect);\n };\n\n ts.forEachChild(sourceFile, visitDirect);\n\n // Pass 2: track aliases — variables assigned from already-tracked references\n // e.g. const alias = this.viewChildRef; or const alias = someParam;\n const visitAlias = (node: ts.Node): void => {\n if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) {\n // Skip if already tracked (has explicit type annotation)\n if (refs.has(node.name.text)) {\n ts.forEachChild(node, visitAlias);\n return;\n }\n\n const aliasedRef = resolveRefName(node.initializer);\n if (aliasedRef && refs.has(aliasedRef)) {\n refs.set(node.name.text, refs.get(aliasedRef)!);\n }\n }\n\n ts.forEachChild(node, visitAlias);\n };\n\n ts.forEachChild(sourceFile, visitAlias);\n return refs;\n}\n\n/**\n * Resolves the EUI class name from a property declaration.\n * Checks: type annotation → decorator class argument → template ref map.\n */\nfunction resolveEuiClassName(\n node: ts.PropertyDeclaration,\n sourceFile: ts.SourceFile,\n templateRefMap: Map<string, string>,\n): string | null {\n // Check type annotation first\n const typeName = getTypeAnnotationName(node.type);\n if (typeName && EUI_SIGNAL_MAP[typeName]) {\n return typeName;\n }\n\n // Check generic type argument: ElementRef<EuiXxxComponent>\n if (node.type && ts.isTypeReferenceNode(node.type) && node.type.typeArguments?.length) {\n const genericArg = getTypeAnnotationName(node.type.typeArguments[0]);\n if (genericArg && EUI_SIGNAL_MAP[genericArg]) {\n return genericArg;\n }\n }\n\n // Check decorator arguments: @ViewChild(EuiXxxComponent) or @ViewChild('refName')\n if (node.modifiers) {\n for (const modifier of node.modifiers) {\n if (!ts.isDecorator(modifier)) continue;\n if (!ts.isCallExpression(modifier.expression)) continue;\n\n const decoratorName = getDecoratorName(modifier.expression);\n if (!decoratorName) continue;\n if (!['ViewChild', 'ViewChildren', 'ContentChild', 'ContentChildren'].includes(decoratorName)) continue;\n\n const args = modifier.expression.arguments;\n if (args.length > 0) {\n const firstArg = args[0];\n\n // @ViewChild(EuiXxxComponent) — class reference\n if (ts.isIdentifier(firstArg) && EUI_SIGNAL_MAP[firstArg.text]) {\n return firstArg.text;\n }\n\n // @ViewChild('refName') — string template ref, resolve via template\n if (ts.isStringLiteralLike(firstArg)) {\n const refName = firstArg.text;\n const euiClass = templateRefMap.get(refName);\n if (euiClass && EUI_SIGNAL_MAP[euiClass]) {\n return euiClass;\n }\n }\n }\n }\n }\n\n return null;\n}\n\nfunction getDecoratorName(callExpr: ts.CallExpression): string | null {\n if (ts.isIdentifier(callExpr.expression)) {\n return callExpr.expression.text;\n }\n return null;\n}\n\nfunction getTypeAnnotationName(typeNode: ts.TypeNode | undefined): string | null {\n if (!typeNode) return null;\n if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {\n return typeNode.typeName.text;\n }\n return null;\n}\n\n/**\n * Checks if a property access `ref.prop` should be rewritten to `ref.prop()`.\n *\n * We rewrite if:\n * - `ref` is tracked in euiRefs\n * - `prop` is in the signal set for that EUI class\n * - The access is NOT already a call expression (not `ref.prop()` or `ref.prop(args)`)\n * - The access is NOT a write target (not `ref.prop = value`)\n * - The access is NOT a template binding argument (those are unaffected)\n */\nfunction tryRewritePropertyAccess(\n node: ts.PropertyAccessExpression,\n euiRefs: Map<string, ReadonlySet<string>>,\n sourceFile: ts.SourceFile,\n source: string,\n): Edit | null {\n const propName = node.name.text;\n\n // Resolve the reference name — handle `this.ref.prop` and `ref.prop`\n const refName = resolveRefName(node.expression);\n if (!refName) return null;\n\n const signalProps = euiRefs.get(refName);\n if (!signalProps || !signalProps.has(propName)) return null;\n\n // Skip if parent is already a call expression on this property: ref.prop(...)\n if (ts.isCallExpression(node.parent) && node.parent.expression === node) {\n return null;\n }\n\n // Skip if this is the left side of an assignment: ref.prop = value\n if (ts.isBinaryExpression(node.parent) &&\n node.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken &&\n node.parent.left === node) {\n return null;\n }\n\n // Skip if this is a compound assignment: ref.prop += value, ref.prop &&= value, etc.\n if (ts.isBinaryExpression(node.parent) && isAssignmentOperator(node.parent.operatorToken.kind) && node.parent.left === node) {\n return null;\n }\n\n // Skip if this is used with delete: delete ref.prop\n if (ts.isDeleteExpression(node.parent) && node.parent.expression === node) {\n return null;\n }\n\n // Skip if inside a typeof: typeof ref.prop\n if (ts.isTypeOfExpression(node.parent) && node.parent.expression === node) {\n return null;\n }\n\n // Skip property access chains that go deeper: ref.prop.something → ref.prop().something\n // This IS correct — we still want to add ()\n\n // Rewrite: insert () after the property name\n const end = node.name.getEnd();\n return {\n start: end,\n end: end,\n replacement: '()',\n };\n}\n\nfunction isAssignmentOperator(kind: ts.SyntaxKind): boolean {\n return kind >= ts.SyntaxKind.FirstAssignment && kind <= ts.SyntaxKind.LastAssignment;\n}\n\n/**\n * Resolves a reference name from an expression.\n * Handles: `ref`, `this.ref`, `this._ref`\n */\nfunction resolveRefName(expr: ts.Expression): string | null {\n // Direct identifier: ref.prop\n if (ts.isIdentifier(expr)) {\n return expr.text;\n }\n\n // this.ref.prop\n if (ts.isPropertyAccessExpression(expr) && expr.expression.kind === ts.SyntaxKind.ThisKeyword) {\n return expr.name.text;\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Template ref resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Builds a map of template reference variable names (#ref) to EUI class names.\n *\n * Parses the component's template (inline or external) and finds elements like:\n * <eui-alert #myAlert> → myAlert → EuiAlertComponent\n * <input euiAutocomplete #ac> → ac → EuiAutocompleteComponent\n *\n * Returns Map<refName, euiClassName>.\n */\nfunction buildTemplateRefMap(\n sourceFile: ts.SourceFile,\n source: string,\n filePath: string,\n tree: Tree,\n): Map<string, string> {\n const refMap = new Map<string, string>();\n\n // Find @Component decorator\n const componentMeta = findComponentMetadata(sourceFile, source);\n if (!componentMeta) return refMap;\n\n let templateSource: string | null = null;\n\n if (componentMeta.inlineTemplate !== null) {\n templateSource = componentMeta.inlineTemplate;\n } else if (componentMeta.templateUrl) {\n // Resolve templateUrl relative to the TS file\n const dir = filePath.substring(0, filePath.lastIndexOf('/'));\n const templatePath = `${dir}/${componentMeta.templateUrl}`;\n const buffer = tree.read(templatePath);\n if (buffer) {\n templateSource = buffer.toString('utf-8');\n }\n }\n\n if (!templateSource) return refMap;\n\n // Parse the template and collect #refs\n try {\n const parsed = parseTemplate(templateSource, '', { preserveWhitespaces: true });\n collectTemplateRefs(parsed.nodes, refMap);\n } catch {\n // Template parse failure — silently skip (might be malformed or use third-party syntax)\n }\n\n return refMap;\n}\n\n/**\n * Extracts @Component metadata: templateUrl or inline template content.\n */\nfunction findComponentMetadata(\n sourceFile: ts.SourceFile,\n source: string,\n): { templateUrl: string | null; inlineTemplate: string | null } | null {\n let result: { templateUrl: string | null; inlineTemplate: string | null } | null = null;\n\n const visit = (node: ts.Node): void => {\n if (result) return;\n\n if (ts.isDecorator(node) && ts.isCallExpression(node.expression)) {\n const name = getDecoratorName(node.expression);\n if (name !== 'Component') {\n ts.forEachChild(node, visit);\n return;\n }\n\n const args = node.expression.arguments;\n if (args.length === 0 || !ts.isObjectLiteralExpression(args[0])) {\n ts.forEachChild(node, visit);\n return;\n }\n\n const meta: { templateUrl: string | null; inlineTemplate: string | null } = {\n templateUrl: null,\n inlineTemplate: null,\n };\n\n for (const prop of args[0].properties) {\n if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) continue;\n\n if (prop.name.text === 'templateUrl') {\n const init = unwrapExpression(prop.initializer);\n if (ts.isStringLiteralLike(init)) {\n meta.templateUrl = init.text;\n }\n }\n\n if (prop.name.text === 'template') {\n const init = unwrapExpression(prop.initializer);\n if (ts.isStringLiteralLike(init)) {\n meta.inlineTemplate = init.text;\n } else if (ts.isNoSubstitutionTemplateLiteral(init)) {\n meta.inlineTemplate = init.text;\n }\n }\n }\n\n result = meta;\n return;\n }\n\n ts.forEachChild(node, visit);\n };\n\n ts.forEachChild(sourceFile, visit);\n return result;\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\n/**\n * Recursively walks template AST nodes, collecting #refName → EUI class name mappings.\n *\n * For each element with a template reference (#ref), we try to resolve its EUI class:\n * 1. Match element tag name against EUI_SELECTOR_TO_CLASS (e.g. 'eui-alert' → EuiAlertComponent)\n * 2. Match element attributes against attribute selectors (e.g. [euiAutocomplete] → EuiAutocompleteComponent)\n */\nfunction collectTemplateRefs(nodes: TmplAstNode[], refMap: Map<string, string>): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.references && node.references.length > 0) {\n const euiClass = resolveElementToEuiClass(node);\n if (euiClass) {\n for (const ref of node.references) {\n refMap.set(ref.name, euiClass);\n }\n }\n }\n collectTemplateRefs(node.children, refMap);\n }\n }\n}\n\n/**\n * Given a template element, resolves it to an EUI class name by checking:\n * 1. Tag name: <eui-alert> → look up 'eui-alert' in EUI_SELECTOR_TO_CLASS\n * 2. Attribute selectors: <input euiAutocomplete> → look up 'input[euiAutocomplete]' or '[euiAutocomplete]'\n */\nfunction resolveElementToEuiClass(element: TmplAstElement): string | null {\n const tag = element.name;\n\n // Direct tag match: eui-alert, eui-dialog, etc.\n if (EUI_SELECTOR_TO_CLASS[tag]) {\n return EUI_SELECTOR_TO_CLASS[tag];\n }\n\n // Attribute selector match: check all attributes against known patterns\n const allAttrNames = [\n ...element.attributes.map((a) => a.name),\n ...element.inputs.map((i) => i.name),\n ];\n\n for (const attrName of allAttrNames) {\n // Try tag[attr] pattern first (e.g. 'button[euiButton]', 'input[euiAutocomplete]')\n const tagAttrSelector = `${tag}[${attrName}]`;\n if (EUI_SELECTOR_TO_CLASS[tagAttrSelector]) {\n return EUI_SELECTOR_TO_CLASS[tagAttrSelector];\n }\n\n // Try [attr] pattern (e.g. '[euiTooltip]', '[euiLoading]')\n const attrSelector = `[${attrName}]`;\n if (EUI_SELECTOR_TO_CLASS[attrSelector]) {\n return EUI_SELECTOR_TO_CLASS[attrSelector];\n }\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Edit helpers\n// ---------------------------------------------------------------------------\n\nfunction applyEdits(source: string, edits: Edit[]): string {\n const unique = deduplicateEdits(edits);\n let result = source;\n for (const edit of unique.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n\nfunction deduplicateEdits(edits: Edit[]): Edit[] {\n const seen = new Map<string, Edit>();\n for (const edit of edits) {\n const key = `${edit.start}:${edit.end}`;\n seen.set(key, edit);\n }\n return Array.from(seen.values());\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n",
3155
+ "sourceCode": "import { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\ninterface Edit {\n start: number;\n end: number;\n replacement: string;\n}\n\nconst OLD_CLASS = 'EuiTooltipConfig';\nconst NEW_INTERFACE = 'EuiTooltipInterface';\n\nexport function migrateEuiTooltip(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let fileCount = 0;\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n if (!path.endsWith('.ts')) return;\n\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(OLD_CLASS)) return;\n\n const result = migrateTypeScript(original, path, context);\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would migrate EuiTooltipConfig → EuiTooltipInterface in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n fileCount++;\n }\n });\n\n context.logger.info(`Migrated EuiTooltipConfig → EuiTooltipInterface in ${fileCount} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction migrateTypeScript(source: string, filePath: string, context: SchematicContext): string {\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: Edit[] = [];\n\n // Track if EuiTooltipInterface is already imported\n let hasInterfaceImport = false;\n let classImportDecl: ts.ImportDeclaration | null = null;\n let classImportModuleSpecifier: string | null = null;\n\n // First pass: analyze imports\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const namedBindings = stmt.importClause?.namedBindings;\n if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;\n\n for (const specifier of namedBindings.elements) {\n if (specifier.name.text === NEW_INTERFACE) {\n hasInterfaceImport = true;\n }\n if (specifier.name.text === OLD_CLASS) {\n classImportDecl = stmt;\n classImportModuleSpecifier = (stmt.moduleSpecifier as ts.StringLiteral).text;\n }\n }\n }\n\n // Second pass: handle import declarations\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const namedBindings = stmt.importClause?.namedBindings;\n if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;\n\n const specifiers = namedBindings.elements;\n const classSpecifier = specifiers.find((s) => s.name.text === OLD_CLASS);\n if (!classSpecifier) continue;\n\n if (hasInterfaceImport) {\n // EuiTooltipInterface is already imported elsewhere → remove EuiTooltipConfig from this import\n removeImportSpecifier(namedBindings, classSpecifier, sourceFile, edits);\n } else {\n // Rename EuiTooltipConfig → EuiTooltipInterface in the import\n edits.push({\n start: classSpecifier.name.getStart(sourceFile),\n end: classSpecifier.name.getEnd(),\n replacement: NEW_INTERFACE,\n });\n hasInterfaceImport = true;\n }\n }\n\n // Third pass: replace `new EuiTooltipConfig(...)` → spread/cast to interface\n const visitNewExpressions = (node: ts.Node): void => {\n if (ts.isNewExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === OLD_CLASS) {\n const args = node.arguments;\n if (args && args.length === 1) {\n const arg = args[0];\n // `new EuiTooltipConfig({ ... })` → `{ ... } as EuiTooltipInterface`\n // But if the argument is just a variable, we keep it: `varName as EuiTooltipInterface`\n const argText = source.slice(arg.getStart(sourceFile), arg.getEnd());\n\n if (ts.isObjectLiteralExpression(arg)) {\n // Inline object: `new EuiTooltipConfig({ x: 1 })` → `{ x: 1 }`\n edits.push({\n start: node.getStart(sourceFile),\n end: node.getEnd(),\n replacement: argText,\n });\n } else {\n // Variable or expression: `new EuiTooltipConfig(opts)` → `opts`\n edits.push({\n start: node.getStart(sourceFile),\n end: node.getEnd(),\n replacement: argText,\n });\n }\n } else if (!args || args.length === 0) {\n // `new EuiTooltipConfig()` → `{} as EuiTooltipInterface`\n edits.push({\n start: node.getStart(sourceFile),\n end: node.getEnd(),\n replacement: `{} as ${NEW_INTERFACE}`,\n });\n }\n return; // don't recurse into children we've already replaced\n }\n ts.forEachChild(node, visitNewExpressions);\n };\n\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) {\n visitNewExpressions(stmt);\n }\n }\n\n // Fourth pass: rename all remaining identifier references (type annotations, etc.)\n const visitRefs = (node: ts.Node): void => {\n if (ts.isImportDeclaration(node)) return;\n // Skip nodes we already covered in new expressions\n if (ts.isNewExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === OLD_CLASS) return;\n\n if (ts.isIdentifier(node) && node.text === OLD_CLASS) {\n // Ensure this is not part of an import declaration\n if (!isPartOfImport(node)) {\n edits.push({\n start: node.getStart(sourceFile),\n end: node.getEnd(),\n replacement: NEW_INTERFACE,\n });\n }\n }\n ts.forEachChild(node, visitRefs);\n };\n\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) {\n visitRefs(stmt);\n }\n }\n\n return applyEdits(source, edits);\n}\n\nfunction isPartOfImport(node: ts.Node): boolean {\n let current: ts.Node | undefined = node.parent;\n while (current) {\n if (ts.isImportDeclaration(current)) return true;\n current = current.parent;\n }\n return false;\n}\n\nfunction removeImportSpecifier(\n namedImports: ts.NamedImports,\n specifier: ts.ImportSpecifier,\n sourceFile: ts.SourceFile,\n edits: Edit[],\n): void {\n const elements = namedImports.elements;\n if (elements.length === 1) {\n // Remove the entire import declaration\n const importDecl = namedImports.parent.parent;\n let end = importDecl.getEnd();\n // Also remove trailing newline if present\n const fullText = sourceFile.getFullText();\n if (fullText[end] === '\\n') end++;\n edits.push({\n start: importDecl.getStart(sourceFile),\n end,\n replacement: '',\n });\n } else {\n // Remove just this specifier with surrounding comma/whitespace\n const idx = elements.indexOf(specifier);\n let start: number;\n let end: number;\n if (idx < elements.length - 1) {\n // Not the last → remove from this specifier start to next specifier start\n start = specifier.getStart(sourceFile);\n end = elements[idx + 1].getStart(sourceFile);\n } else {\n // Last element → remove from previous element end to this end\n start = elements[idx - 1].getEnd();\n end = specifier.getEnd();\n }\n edits.push({ start, end, replacement: '' });\n }\n}\n\nfunction applyEdits(source: string, edits: Edit[]): string {\n const unique = deduplicateEdits(edits);\n let result = source;\n for (const edit of unique.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n\nfunction deduplicateEdits(edits: Edit[]): Edit[] {\n const seen = new Map<string, Edit>();\n for (const edit of edits) {\n const key = `${edit.start}:${edit.end}`;\n seen.set(key, edit);\n }\n return Array.from(seen.values());\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n",
3156
3156
  "displayName": "Schema",
3157
3157
  "properties": [
3158
3158
  {
@@ -3164,7 +3164,7 @@
3164
3164
  "indexKey": "",
3165
3165
  "optional": true,
3166
3166
  "description": "",
3167
- "line": 9,
3167
+ "line": 7,
3168
3168
  "rawdescription": "\n"
3169
3169
  },
3170
3170
  {
@@ -3176,7 +3176,7 @@
3176
3176
  "indexKey": "",
3177
3177
  "optional": true,
3178
3178
  "description": "",
3179
- "line": 8,
3179
+ "line": 6,
3180
3180
  "rawdescription": "\n"
3181
3181
  }
3182
3182
  ],
@@ -3194,12 +3194,12 @@
3194
3194
  },
3195
3195
  {
3196
3196
  "name": "Schema",
3197
- "id": "interface-Schema-817c4b549cc3eab9fcf4936acab2e71182d90a4480369f5c003cbbda10792efa587ad99d65acf049f64e7030e32589e4fabc4a6d7a5621e4fe54725ef91dc9e8-22",
3198
- "file": "packages/core/schematics/migrate-to-standalone/index.ts",
3197
+ "id": "interface-Schema-e1cd02924eb82a618c71a0b26c081bdd020519d2699aa0e2dc98640c4e0f347c3649b967c5fc87543e41bbbfabd1304835850ccc38f40684d6521c242b2030ed-22",
3198
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
3199
3199
  "deprecated": false,
3200
3200
  "deprecationMessage": "",
3201
3201
  "type": "interface",
3202
- "sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface ModuleMapping {\n importPath: string;\n selectors: Record<string, string>;\n}\n\nconst MODULE_MAPPINGS: Record<string, ModuleMapping> = {\n EuiAccordionModule: {\n importPath: '@eui/components/eui-accordion',\n selectors: {\n 'eui-accordion': 'EuiAccordionComponent',\n 'eui-accordion-item': 'EuiAccordionItemComponent',\n euiAccordionItemHeader: 'EuiAccordionItemHeaderDirective',\n },\n },\n EuiAlertModule: {\n importPath: '@eui/components/eui-alert',\n selectors: {\n 'eui-alert': 'EuiAlertComponent',\n euiAlert: 'EuiAlertComponent',\n 'eui-alert-title': 'EuiAlertTitleComponent',\n },\n },\n EuiAutocompleteModule: {\n importPath: '@eui/components/eui-autocomplete',\n selectors: {\n 'eui-autocomplete': 'EuiAutocompleteComponent',\n euiAutocomplete: 'EuiAutocompleteComponent',\n 'eui-autocomplete-option': 'EuiAutocompleteOptionComponent',\n 'eui-autocomplete-option-group': 'EuiAutocompleteOptionGroupComponent',\n 'eui-autocomplete-panel': 'EuiAutocompletePanelComponent',\n },\n },\n EuiAvatarModule: {\n importPath: '@eui/components/eui-avatar',\n selectors: {\n 'eui-avatar': 'EuiAvatarComponent',\n euiAvatar: 'EuiAvatarComponent',\n },\n },\n EuiBadgeModule: {\n importPath: '@eui/components/eui-badge',\n selectors: {\n 'eui-badge': 'EuiBadgeComponent',\n euiBadge: 'EuiBadgeComponent',\n },\n },\n EuiBlockContentModule: {\n importPath: '@eui/components/eui-block-content',\n selectors: {\n 'eui-block-content': 'EuiBlockContentComponent',\n },\n },\n EuiBreadcrumbModule: {\n importPath: '@eui/components/eui-breadcrumb',\n selectors: {\n 'eui-breadcrumb': 'EuiBreadcrumbComponent',\n },\n },\n EuiButtonModule: {\n importPath: '@eui/components/eui-button',\n selectors: {\n euiButton: 'EuiButtonComponent',\n },\n },\n EuiButtonGroupModule: {\n importPath: '@eui/components/eui-button-group',\n selectors: {\n 'eui-button-group': 'EuiButtonGroupComponent',\n },\n },\n EuiCardModule: {\n importPath: '@eui/components/eui-card',\n selectors: {\n 'eui-card': 'EuiCardComponent',\n 'eui-card-header': 'EuiCardHeaderComponent',\n 'eui-card-header-title': 'EuiCardHeaderTitleComponent',\n 'eui-card-content': 'EuiCardContentComponent',\n 'eui-card-footer': 'EuiCardFooterComponent',\n 'eui-card-media': 'EuiCardMediaComponent',\n },\n },\n EuiChipModule: {\n importPath: '@eui/components/eui-chip',\n selectors: {\n 'eui-chip': 'EuiChipComponent',\n euiChip: 'EuiChipComponent',\n },\n },\n EuiChipListModule: {\n importPath: '@eui/components/eui-chip-list',\n selectors: {\n 'eui-chip-list': 'EuiChipListComponent',\n },\n },\n EuiChipGroupModule: {\n importPath: '@eui/components/eui-chip-group',\n selectors: {\n 'eui-chip-group': 'EuiChipGroupComponent',\n },\n },\n EuiDashboardCardModule: {\n importPath: '@eui/components/eui-dashboard-card',\n selectors: {\n 'eui-dashboard-card': 'EuiDashboardCardComponent',\n 'eui-dashboard-card-content': 'EuiDashboardCardContentComponent',\n 'eui-dashboard-card-content-header': 'EuiDashboardCardContentHeaderComponent',\n 'eui-dashboard-card-content-body': 'EuiDashboardCardContentBodyComponent',\n 'eui-dashboard-card-content-footer': 'EuiDashboardCardContentFooterComponent',\n },\n },\n EuiDashboardButtonModule: {\n importPath: '@eui/components/eui-dashboard-card',\n selectors: {\n 'eui-dashboard-card': 'EuiDashboardCardComponent',\n 'eui-dashboard-card-content': 'EuiDashboardCardContentComponent',\n 'eui-dashboard-card-content-header': 'EuiDashboardCardContentHeaderComponent',\n 'eui-dashboard-card-content-body': 'EuiDashboardCardContentBodyComponent',\n 'eui-dashboard-card-content-footer': 'EuiDashboardCardContentFooterComponent',\n },\n },\n EuiDatepickerModule: {\n importPath: '@eui/components/eui-datepicker',\n selectors: {\n 'eui-datepicker': 'EuiDatepickerComponent',\n },\n },\n EuiDateRangeSelectorModule: {\n importPath: '@eui/components/eui-date-range-selector',\n selectors: {\n 'eui-date-range-selector': 'EuiDateRangeSelectorComponent',\n },\n },\n EuiDialogModule: {\n importPath: '@eui/components/eui-dialog',\n selectors: {\n 'eui-dialog': 'EuiDialogComponent',\n 'eui-dialog-header': 'EuiDialogHeaderDirective',\n 'eui-dialog-footer': 'EuiDialogFooterDirective',\n 'eui-dialog-container': 'EuiDialogContainerComponent',\n },\n },\n EuiDisableContentModule: {\n importPath: '@eui/components/eui-disable-content',\n selectors: {\n 'eui-disable-content': 'EuiDisableContentComponent',\n },\n },\n EuiDiscussionThreadModule: {\n importPath: '@eui/components/eui-discussion-thread',\n selectors: {\n 'eui-discussion-thread': 'EuiDiscussionThreadComponent',\n 'eui-discussion-thread-item': 'EuiDiscussionThreadItemComponent',\n },\n },\n EuiDropdownModule: {\n importPath: '@eui/components/eui-dropdown',\n selectors: {\n 'eui-dropdown': 'EuiDropdownComponent',\n },\n },\n EuiFeedbackMessageModule: {\n importPath: '@eui/components/eui-feedback-message',\n selectors: {\n 'eui-feedback-message': 'EuiFeedbackMessageComponent',\n },\n },\n EuiFieldsetModule: {\n importPath: '@eui/components/eui-fieldset',\n selectors: {\n 'eui-fieldset': 'EuiFieldsetComponent',\n euiFieldsetLabelRightContent: 'EuiFieldsetLabelRightContentTagDirective',\n euiFieldsetLabelExtraContent: 'EuiFieldsetLabelExtraContentTagDirective',\n },\n },\n EuiFileUploadModule: {\n importPath: '@eui/components/eui-file-upload',\n selectors: {\n 'eui-file-upload': 'EuiFileUploadComponent',\n },\n },\n EuiGrowlModule: {\n importPath: '@eui/components/eui-growl',\n selectors: {\n 'eui-growl': 'EuiGrowlComponent',\n },\n },\n EuiIconModule: {\n importPath: '@eui/components/eui-icon',\n selectors: {\n 'eui-icon-svg': 'EuiIconSvgComponent',\n euiIconSvg: 'EuiIconSvgComponent',\n },\n },\n EuiIconButtonModule: {\n importPath: '@eui/components/eui-icon-button',\n selectors: {\n 'eui-icon-button': 'EuiIconButtonComponent',\n },\n },\n EuiIconToggleModule: {\n importPath: '@eui/components/eui-icon-toggle',\n selectors: {\n 'eui-icon-toggle': 'EuiIconToggleComponent',\n },\n },\n EuiInputCheckboxModule: {\n importPath: '@eui/components/eui-input-checkbox',\n selectors: {\n euiInputCheckBox: 'EuiInputCheckboxComponent',\n },\n },\n EuiInputGroupModule: {\n importPath: '@eui/components/eui-input-group',\n selectors: {\n euiInputGroup: 'EuiInputGroupComponent',\n 'eui-input-group-addon': 'EuiInputGroupAddOnComponent',\n euiInputGroupAddOn: 'EuiInputGroupAddOnComponent',\n 'eui-input-group-addon-item': 'EuiInputGroupAddOnItemComponent',\n euiInputGroupAddOnItem: 'EuiInputGroupAddOnItemComponent',\n },\n },\n EuiInputNumberModule: {\n importPath: '@eui/components/eui-input-number',\n selectors: {\n euiInputNumber: 'EuiInputNumberComponent',\n },\n },\n EuiInputRadioModule: {\n importPath: '@eui/components/eui-input-radio',\n selectors: {\n euiInputRadio: 'EuiInputRadioComponent',\n },\n },\n EuiInputTextModule: {\n importPath: '@eui/components/eui-input-text',\n selectors: {\n euiInputText: 'EuiInputTextComponent',\n },\n },\n EuiLabelModule: {\n importPath: '@eui/components/eui-label',\n selectors: {\n 'eui-label': 'EuiLabelComponent',\n euiLabel: 'EuiLabelComponent',\n },\n },\n EuiListModule: {\n importPath: '@eui/components/eui-list',\n selectors: {\n 'eui-list': 'EuiListComponent',\n euiList: 'EuiListComponent',\n 'eui-list-item': 'EuiListItemComponent',\n euiListItem: 'EuiListItemComponent',\n },\n },\n EuiMenuModule: {\n importPath: '@eui/components/eui-menu',\n selectors: {\n 'eui-menu': 'EuiMenuComponent',\n 'eui-menu-item': 'EuiMenuItemComponent',\n },\n },\n EuiMessageBoxModule: {\n importPath: '@eui/components/eui-message-box',\n selectors: {\n 'eui-message-box': 'EuiMessageBoxComponent',\n 'eui-message-box-footer': 'EuiMessageBoxFooterDirective',\n },\n },\n EuiOverlayModule: {\n importPath: '@eui/components/eui-overlay',\n selectors: {\n 'eui-overlay': 'EuiOverlayComponent',\n },\n },\n EuiPageModule: {\n importPath: '@eui/components/eui-page',\n selectors: {\n 'eui-page': 'EuiPageComponent',\n },\n },\n EuiPaginatorModule: {\n importPath: '@eui/components/eui-paginator',\n selectors: {\n 'eui-paginator': 'EuiPaginatorComponent',\n },\n },\n EuiPopoverModule: {\n importPath: '@eui/components/eui-popover',\n selectors: {\n 'eui-popover': 'EuiPopoverComponent',\n },\n },\n EuiProgressBarModule: {\n importPath: '@eui/components/eui-progress-bar',\n selectors: {\n 'eui-progress-bar': 'EuiProgressBarComponent',\n },\n },\n EuiProgressCircleModule: {\n importPath: '@eui/components/eui-progress-circle',\n selectors: {\n 'eui-progress-circle': 'EuiProgressCircleComponent',\n },\n },\n EuiSelectModule: {\n importPath: '@eui/components/eui-select',\n selectors: {\n euiSelect: 'EuiSelectComponent',\n },\n },\n EuiSidebarMenuModule: {\n importPath: '@eui/components/eui-sidebar-menu',\n selectors: {\n 'eui-sidebar-menu': 'EuiSidebarMenuComponent',\n },\n },\n EuiSkeletonModule: {\n importPath: '@eui/components/eui-skeleton',\n selectors: {\n 'eui-skeleton': 'EuiSkeletonComponent',\n },\n },\n EuiSlideToggleModule: {\n importPath: '@eui/components/eui-slide-toggle',\n selectors: {\n 'eui-slide-toggle': 'EuiSlideToggleComponent',\n },\n },\n EuiTableModule: {\n importPath: '@eui/components/eui-table',\n selectors: {\n 'eui-table': 'EuiTableComponent',\n euiTable: 'EuiTableComponent',\n 'eui-table-filter': 'EuiTableFilterComponent',\n isSortable: 'EuiTableSortableColComponent',\n isStickyCol: 'EuiTableStickyColDirective',\n isHeaderSelectable: 'EuiTableSelectableHeaderComponent',\n isDataSelectable: 'EuiTableSelectableRowComponent',\n isExpandableRow: 'EuiTableExpandableRowDirective',\n },\n },\n EuiTableV2Module: {\n importPath: '@eui/components/eui-table',\n selectors: {\n 'eui-table': 'EuiTableComponent',\n euiTable: 'EuiTableComponent',\n 'eui-table-filter': 'EuiTableFilterComponent',\n isSortable: 'EuiTableSortableColComponent',\n isStickyCol: 'EuiTableStickyColDirective',\n isHeaderSelectable: 'EuiTableSelectableHeaderComponent',\n isDataSelectable: 'EuiTableSelectableRowComponent',\n isExpandableRow: 'EuiTableExpandableRowDirective',\n },\n },\n EuiTabsModule: {\n importPath: '@eui/components/eui-tabs',\n selectors: {\n 'eui-tabs': 'EuiTabsComponent',\n 'eui-tab': 'EuiTabComponent',\n 'eui-tab-header': 'EuiTabHeaderComponent',\n 'eui-tab-body': 'EuiTabBodyComponent',\n },\n },\n EuiTextAreaModule: {\n importPath: '@eui/components/eui-textarea',\n selectors: {\n euiTextArea: 'EuiTextareaComponent',\n },\n },\n EuiTimelineModule: {\n importPath: '@eui/components/eui-timeline',\n selectors: {\n 'eui-timeline': 'EuiTimelineComponent',\n 'eui-timeline-item': 'EuiTimelineItemComponent',\n },\n },\n EuiTimepickerModule: {\n importPath: '@eui/components/eui-timepicker',\n selectors: {\n 'eui-timepicker': 'EuiTimepickerComponent',\n },\n },\n EuiTreeModule: {\n importPath: '@eui/components/eui-tree',\n selectors: {\n 'eui-tree': 'EuiTreeComponent',\n },\n },\n EuiTreeListModule: {\n importPath: '@eui/components/eui-tree-list',\n selectors: {\n 'eui-tree-list': 'EuiTreeListComponent',\n 'eui-tree-list-item': 'EuiTreeListItemComponent',\n },\n },\n EuiUserProfileModule: {\n importPath: '@eui/components/eui-user-profile',\n selectors: {\n 'eui-user-profile': 'EuiUserProfileComponent',\n },\n },\n EuiWizardModule: {\n importPath: '@eui/components/eui-wizard',\n selectors: {\n 'eui-wizard': 'EuiWizardComponent',\n 'eui-wizard-step': 'EuiWizardStepComponent',\n },\n },\n EuiTooltipDirectiveModule: {\n importPath: '@eui/components/directives',\n selectors: {\n euiTooltip: 'EuiTooltipDirective',\n },\n },\n EuiTemplateDirectiveModule: {\n importPath: '@eui/components/directives',\n selectors: {\n euiTemplate: 'EuiTemplateDirective',\n },\n },\n EuiResizableDirectiveModule: {\n importPath: '@eui/components/directives',\n selectors: {\n euiResizable: 'EuiResizableDirective',\n 'eui-resizable': 'EuiResizableComponent',\n },\n },\n EuiMaxLengthDirectiveModule: {\n importPath: '@eui/components/directives',\n selectors: {\n euiEditorMaxlength: 'EuiMaxLengthDirective',\n },\n },\n EuiTruncatePipeModule: {\n importPath: '@eui/components/pipes',\n selectors: {\n euiTruncate: 'EuiTruncatePipe',\n },\n },\n EuiLayoutModule: {\n importPath: '@eui/components/layout',\n selectors: {\n 'eui-app': 'EuiAppComponent',\n 'eui-header': 'EuiHeaderComponent',\n 'eui-footer': 'EuiFooterComponent',\n 'eui-toolbar': 'EuiToolbarComponent',\n 'eui-sidebar-toggle': 'EuiSidebarToggleComponent',\n },\n },\n};\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nexport function migrateToStandalone(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n const allModuleNames = Object.keys(MODULE_MAPPINGS);\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const source = buffer.toString('utf-8');\n if (!allModuleNames.some((m) => source.includes(m))) return;\n\n const sourceFile = ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true);\n const componentDecorators = findComponentDecorators(sourceFile);\n\n for (const decorator of componentDecorators) {\n const importsNode = findImportsArrayNode(decorator);\n if (!importsNode) continue;\n\n const currentSource = tree.read(path)!.toString('utf-8');\n const modulesInArray = allModuleNames.filter((m) => hasModuleInArray(importsNode, m, source));\n if (modulesInArray.length === 0) continue;\n\n const templateSelectors = getTemplateSelectors(tree, path, decorator, source);\n const replacements = buildReplacements(modulesInArray, templateSelectors);\n if (replacements.size === 0) continue;\n\n const result = applyReplacements(currentSource, path, replacements);\n if (options.dryRun) {\n logDryRun(context, `Would replace module imports with standalone imports in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n }\n });\n\n context.logger.info('Migration to standalone imports complete.');\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction buildReplacements(moduleNames: string[], templateSelectors: Set<string>): Map<string, { components: string[]; importPath: string }> {\n const map = new Map<string, { components: string[]; importPath: string }>();\n\n for (const moduleName of moduleNames) {\n const mapping = MODULE_MAPPINGS[moduleName];\n const components: string[] = [];\n\n for (const [selector, component] of Object.entries(mapping.selectors)) {\n if (templateSelectors.has(selector)) {\n components.push(component);\n }\n }\n\n if (components.length > 0) {\n map.set(moduleName, { components: [...new Set(components)].sort(), importPath: mapping.importPath });\n }\n }\n\n return map;\n}\n\nfunction findComponentDecorators(sourceFile: ts.SourceFile): ts.Decorator[] {\n const decorators: ts.Decorator[] = [];\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node)) {\n const decs = ts.getDecorators(node);\n if (decs) {\n for (const dec of decs) {\n if (ts.isCallExpression(dec.expression) && ts.isIdentifier(dec.expression.expression) && dec.expression.expression.text === 'Component') {\n decorators.push(dec);\n }\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n return decorators;\n}\n\nfunction findImportsArrayNode(decorator: ts.Decorator): ts.ArrayLiteralExpression | undefined {\n const call = decorator.expression as ts.CallExpression;\n const metadata = call.arguments[0];\n if (!ts.isObjectLiteralExpression(metadata)) return undefined;\n\n for (const prop of metadata.properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'imports') {\n if (ts.isArrayLiteralExpression(prop.initializer)) {\n return prop.initializer;\n }\n }\n }\n return undefined;\n}\n\nfunction hasModuleInArray(array: ts.ArrayLiteralExpression, moduleName: string, source: string): boolean {\n return array.elements.some((el) => source.slice(el.getStart(), el.getEnd()).trim() === moduleName);\n}\n\nfunction getTemplateSelectors(tree: Tree, tsPath: string, decorator: ts.Decorator, source: string): Set<string> {\n const call = decorator.expression as ts.CallExpression;\n const metadata = call.arguments[0] as ts.ObjectLiteralExpression;\n const allSelectors = Object.values(MODULE_MAPPINGS).flatMap((m) => Object.keys(m.selectors));\n\n for (const prop of metadata.properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'template') {\n const init = prop.initializer;\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n return findSelectorsInTemplate(init.text, allSelectors);\n }\n }\n }\n\n for (const prop of metadata.properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'templateUrl') {\n if (ts.isStringLiteral(prop.initializer)) {\n const dir = tsPath.substring(0, tsPath.lastIndexOf('/'));\n const templateBuffer = tree.read(`${dir}/${prop.initializer.text}`);\n if (templateBuffer) {\n return findSelectorsInTemplate(templateBuffer.toString('utf-8'), allSelectors);\n }\n }\n }\n }\n\n return new Set();\n}\n\nfunction findSelectorsInTemplate(html: string, knownSelectors: string[]): Set<string> {\n const selectors = new Set<string>();\n const parsed = parseTemplate(html, '', { preserveWhitespaces: true });\n\n const visit = (nodes: TmplAstNode[]): void => {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (knownSelectors.includes(node.name)) selectors.add(node.name);\n for (const attr of node.attributes) {\n if (knownSelectors.includes(attr.name)) selectors.add(attr.name);\n }\n visit(node.children);\n }\n }\n };\n visit(parsed.nodes);\n return selectors;\n}\n\nfunction applyReplacements(source: string, filePath: string, replacements: Map<string, { components: string[]; importPath: string }>): string {\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);\n let result = replaceInImportsArray(source, sourceFile, replacements);\n result = updateEsImports(result, filePath, replacements);\n return result;\n}\n\nfunction replaceInImportsArray(source: string, sourceFile: ts.SourceFile, replacements: Map<string, { components: string[]; importPath: string }>): string {\n let result = source;\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node)) {\n const decs = ts.getDecorators(node);\n if (!decs) return;\n for (const dec of decs) {\n if (!ts.isCallExpression(dec.expression) || !ts.isIdentifier(dec.expression.expression) || dec.expression.expression.text !== 'Component') continue;\n const metadata = dec.expression.arguments[0];\n if (!ts.isObjectLiteralExpression(metadata)) continue;\n for (const prop of metadata.properties) {\n if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name) || prop.name.text !== 'imports') continue;\n if (!ts.isArrayLiteralExpression(prop.initializer)) continue;\n const newElements = prop.initializer.elements\n .map((el) => {\n const text = result.slice(el.getStart(sourceFile), el.getEnd()).trim();\n const replacement = replacements.get(text);\n return replacement ? replacement.components.join(', ') : text;\n })\n .join(', ');\n result = result.slice(0, prop.initializer.getStart(sourceFile) + 1) + newElements + result.slice(prop.initializer.getEnd() - 1);\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n return result;\n}\n\nfunction updateEsImports(source: string, filePath: string, replacements: Map<string, { components: string[]; importPath: string }>): string {\n let result = source;\n\n for (const [moduleName, { components, importPath }] of replacements) {\n const sf = ts.createSourceFile(filePath, result, ts.ScriptTarget.Latest, true);\n\n for (const stmt of sf.statements) {\n if (!ts.isImportDeclaration(stmt) || !stmt.importClause?.namedBindings || !ts.isNamedImports(stmt.importClause.namedBindings)) continue;\n const namedBindings = stmt.importClause.namedBindings;\n const importNames = namedBindings.elements.map((el) => el.name.text);\n if (!importNames.includes(moduleName)) continue;\n\n const moduleSpecifier = (stmt.moduleSpecifier as ts.StringLiteral).text;\n const remaining = importNames.filter((n) => n !== moduleName);\n const toAdd = components.filter((c) => !importNames.includes(c));\n\n if (moduleSpecifier === importPath) {\n const newNames = [...remaining, ...toAdd].sort();\n const newClause = `{ ${newNames.join(', ')} }`;\n result = result.slice(0, namedBindings.getStart(sf)) + newClause + result.slice(namedBindings.getEnd());\n } else {\n if (remaining.length > 0) {\n const newClause = `{ ${remaining.join(', ')} }`;\n result = result.slice(0, namedBindings.getStart(sf)) + newClause + result.slice(namedBindings.getEnd());\n } else {\n result = result.slice(0, stmt.getStart(sf)) + result.slice(stmt.getEnd()).replace(/^\\r?\\n/, '');\n }\n\n if (toAdd.length > 0) {\n const updatedSf = ts.createSourceFile(filePath, result, ts.ScriptTarget.Latest, true);\n const existing = updatedSf.statements.find(\n (s) => ts.isImportDeclaration(s) && ts.isStringLiteral(s.moduleSpecifier) && s.moduleSpecifier.text === importPath,\n ) as ts.ImportDeclaration | undefined;\n\n if (existing?.importClause?.namedBindings && ts.isNamedImports(existing.importClause.namedBindings)) {\n const existingNames = existing.importClause.namedBindings.elements.map((el) => el.name.text);\n const allNames = [...new Set([...existingNames, ...toAdd])].sort();\n const newClause = `{ ${allNames.join(', ')} }`;\n result = result.slice(0, existing.importClause.namedBindings.getStart(updatedSf)) + newClause + result.slice(existing.importClause.namedBindings.getEnd());\n } else {\n const newImport = `import { ${toAdd.join(', ')} } from '${importPath}';\\n`;\n result = newImport + result;\n }\n }\n }\n break;\n }\n }\n\n return result;\n}\n",
3202
+ "sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\nconst OLD_TAG = 'eui-toolbar-menu';\nconst NEW_TAG = 'eui-toolbar-mega-menu';\nconst OLD_COMPONENT = 'EuiToolbarMenuComponent';\nconst NEW_COMPONENT = 'EuiToolbarMegaMenuComponent';\nconst OLD_INTERFACE = 'ToolbarItem';\nconst NEW_INTERFACE = 'EuiMenuItem';\nconst NEW_COMPONENT_PATH = '@eui/components/layout';\nconst NEW_INTERFACE_PATH = '@eui/core';\nconst REMOVED_OUTPUT = 'menuItemClick';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\ninterface Edit {\n start: number;\n end: number;\n replacement: string;\n}\n\nexport function migrateEuiToolbarMenu(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let fileCount = 0;\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(OLD_TAG) && !original.includes(OLD_COMPONENT) && !original.includes(OLD_INTERFACE)) return;\n\n let result: string;\n\n if (path.endsWith('.html')) {\n result = migrateTemplate(original, path, context);\n } else {\n result = migrateTypeScript(original, path, context);\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would migrate eui-toolbar-menu → eui-toolbar-mega-menu in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n fileCount++;\n }\n });\n\n context.logger.info(`Migrated eui-toolbar-menu → eui-toolbar-mega-menu in ${fileCount} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction migrateTemplate(source: string, filePath: string, context: SchematicContext): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: Edit[] = [];\n\n visitNodes(parsed.nodes, source, edits, filePath, context);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateTypeScript(source: string, filePath: string, context: SchematicContext): string {\n let result = migrateInlineTemplates(source, filePath, context);\n result = migrateImportsAndTypes(result, filePath, context);\n return result;\n}\n\nfunction migrateInlineTemplates(source: string, filePath: string, context: SchematicContext): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: Edit[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(OLD_TAG)) {\n ts.forEachChild(node, visit); return; \n}\n const migrated = migrateTemplate(rawTemplate, filePath, context);\n if (migrated !== rawTemplate) changes.push({ start, end, replacement: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n return applyEdits(source, changes);\n}\n\nfunction migrateImportsAndTypes(source: string, filePath: string, context: SchematicContext): string {\n if (!source.includes(OLD_COMPONENT) && !source.includes(OLD_INTERFACE)) return source;\n\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: Edit[] = [];\n\n // Track if EuiMenuItem is already imported from @eui/core\n let hasEuiMenuItemImport = false;\n\n // First pass: analyze imports\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const moduleSpec = (stmt.moduleSpecifier as ts.StringLiteral).text;\n const namedBindings = stmt.importClause?.namedBindings;\n if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;\n\n for (const specifier of namedBindings.elements) {\n if (specifier.name.text === NEW_INTERFACE && moduleSpec === NEW_INTERFACE_PATH) {\n hasEuiMenuItemImport = true;\n }\n }\n }\n\n // Second pass: collect edits for import declarations\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const namedBindings = stmt.importClause?.namedBindings;\n if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;\n\n const moduleSpec = stmt.moduleSpecifier as ts.StringLiteral;\n const specifiers = namedBindings.elements;\n const hasComponent = specifiers.some((s) => s.name.text === OLD_COMPONENT);\n const hasInterface = specifiers.some((s) => s.name.text === OLD_INTERFACE);\n\n if (hasComponent && hasInterface) {\n // Both are in the same import → must split into two different paths\n const others = specifiers.filter((s) => s.name.text !== OLD_COMPONENT && s.name.text !== OLD_INTERFACE);\n const lines: string[] = [];\n lines.push(`import { ${NEW_COMPONENT} } from '${NEW_COMPONENT_PATH}';`);\n if (!hasEuiMenuItemImport) {\n lines.push(`import { ${NEW_INTERFACE} } from '${NEW_INTERFACE_PATH}';`);\n }\n if (others.length > 0) {\n const otherNames = others.map((s) => s.name.text).join(', ');\n lines.push(`import { ${otherNames} } from '${moduleSpec.text}';`);\n }\n edits.push({\n start: stmt.getStart(sourceFile),\n end: stmt.getEnd(),\n replacement: lines.join('\\n'),\n });\n } else if (hasComponent) {\n edits.push({\n start: moduleSpec.getStart(sourceFile) + 1,\n end: moduleSpec.getEnd() - 1,\n replacement: NEW_COMPONENT_PATH,\n });\n for (const specifier of specifiers) {\n if (specifier.name.text === OLD_COMPONENT) {\n edits.push({\n start: specifier.name.getStart(sourceFile),\n end: specifier.name.getEnd(),\n replacement: NEW_COMPONENT,\n });\n }\n }\n } else if (hasInterface) {\n if (hasEuiMenuItemImport) {\n removeImportSpecifier(namedBindings, specifiers.find((s) => s.name.text === OLD_INTERFACE)!, sourceFile, edits);\n } else {\n edits.push({\n start: moduleSpec.getStart(sourceFile) + 1,\n end: moduleSpec.getEnd() - 1,\n replacement: NEW_INTERFACE_PATH,\n });\n for (const specifier of specifiers) {\n if (specifier.name.text === OLD_INTERFACE) {\n edits.push({\n start: specifier.name.getStart(sourceFile),\n end: specifier.name.getEnd(),\n replacement: NEW_INTERFACE,\n });\n }\n }\n }\n }\n }\n\n // Third pass: rename identifier references in non-import positions\n const visitRefs = (node: ts.Node): void => {\n if (ts.isImportDeclaration(node)) return; // skip imports (already handled)\n if (ts.isIdentifier(node)) {\n if (node.text === OLD_COMPONENT) {\n edits.push({ start: node.getStart(sourceFile), end: node.getEnd(), replacement: NEW_COMPONENT });\n }\n if (node.text === OLD_INTERFACE) {\n edits.push({ start: node.getStart(sourceFile), end: node.getEnd(), replacement: NEW_INTERFACE });\n }\n }\n ts.forEachChild(node, visitRefs);\n };\n\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) {\n visitRefs(stmt);\n }\n }\n\n // Warn about ToolbarItem-specific properties\n warnRemovedProperties(sourceFile, filePath, context);\n\n return applyEdits(source, edits);\n}\n\nfunction removeImportSpecifier(\n namedImports: ts.NamedImports,\n specifier: ts.ImportSpecifier,\n sourceFile: ts.SourceFile,\n edits: Edit[],\n): void {\n const elements = namedImports.elements;\n if (elements.length === 1) {\n // Remove the entire import declaration\n const importDecl = namedImports.parent.parent;\n edits.push({\n start: importDecl.getStart(sourceFile),\n end: importDecl.getEnd(),\n replacement: '',\n });\n } else {\n // Remove just this specifier with surrounding comma/whitespace\n const idx = elements.indexOf(specifier);\n let start: number;\n let end: number;\n if (idx < elements.length - 1) {\n start = specifier.getStart(sourceFile);\n end = elements[idx + 1].getStart(sourceFile);\n } else {\n start = elements[idx - 1].getEnd();\n end = specifier.getEnd();\n }\n edits.push({ start, end, replacement: '' });\n }\n}\n\nfunction warnRemovedProperties(sourceFile: ts.SourceFile, filePath: string, context: SchematicContext): void {\n const deprecated = ['isHome', 'isSeparator'];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && deprecated.includes(node.name.text)) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${filePath}:${line + 1} - \"${node.name.text}\" was part of ToolbarItem but does not exist on EuiMenuItem. Review manually.`,\n );\n }\n if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && deprecated.includes(node.name.text)) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${filePath}:${line + 1} - \"${node.name.text}\" was part of ToolbarItem but does not exist on EuiMenuItem. Review manually.`,\n );\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n}\n\nfunction visitNodes(nodes: TmplAstNode[], source: string, edits: Edit[], filePath: string, context: SchematicContext): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === OLD_TAG) {\n collectTagRenames(node, source, edits);\n collectOutputRemovals(node, source, edits, filePath, context);\n }\n visitNodes(node.children, source, edits, filePath, context);\n }\n }\n}\n\nfunction collectTagRenames(element: TmplAstElement, source: string, edits: Edit[]): void {\n // Rename opening tag\n const openStart = element.startSourceSpan.start.offset + 1; // skip '<'\n edits.push({ start: openStart, end: openStart + OLD_TAG.length, replacement: NEW_TAG });\n\n // Rename closing tag\n if (element.endSourceSpan) {\n const closeStart = element.endSourceSpan.start.offset + 2; // skip '</'\n edits.push({ start: closeStart, end: closeStart + OLD_TAG.length, replacement: NEW_TAG });\n }\n}\n\nfunction collectOutputRemovals(\n element: TmplAstElement,\n source: string,\n edits: Edit[],\n filePath: string,\n context: SchematicContext,\n): void {\n for (const output of element.outputs) {\n if (output.name === REMOVED_OUTPUT) {\n let start = output.sourceSpan.start.offset;\n // Remove leading whitespace\n while (start > 0 && (source[start - 1] === ' ' || source[start - 1] === '\\t')) {\n start--;\n }\n edits.push({ start, end: output.sourceSpan.end.offset, replacement: '' });\n\n const { line } = element.startSourceSpan.start;\n context.logger.warn(\n `${filePath}:${line + 1} - \"(menuItemClick)\" has been removed. There is no equivalent on eui-toolbar-mega-menu.`,\n );\n }\n }\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction applyEdits(source: string, edits: Edit[]): string {\n // Deduplicate edits at same position (e.g. module path edits when both Component and ToolbarItem are from same source)\n const unique = deduplicateEdits(edits);\n let result = source;\n for (const edit of unique.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n\nfunction deduplicateEdits(edits: Edit[]): Edit[] {\n const seen = new Map<string, Edit>();\n for (const edit of edits) {\n const key = `${edit.start}:${edit.end}`;\n // Last wins for same range\n seen.set(key, edit);\n }\n return Array.from(seen.values());\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n",
3203
3203
  "displayName": "Schema",
3204
3204
  "properties": [
3205
3205
  {
@@ -3211,7 +3211,7 @@
3211
3211
  "indexKey": "",
3212
3212
  "optional": true,
3213
3213
  "description": "",
3214
- "line": 460,
3214
+ "line": 18,
3215
3215
  "rawdescription": "\n"
3216
3216
  },
3217
3217
  {
@@ -3223,7 +3223,7 @@
3223
3223
  "indexKey": "",
3224
3224
  "optional": true,
3225
3225
  "description": "",
3226
- "line": 459,
3226
+ "line": 17,
3227
3227
  "rawdescription": "\n"
3228
3228
  }
3229
3229
  ],
@@ -23213,23 +23213,23 @@
23213
23213
  "name": "NEW_INTERFACE",
23214
23214
  "ctype": "miscellaneous",
23215
23215
  "subtype": "variable",
23216
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
23216
+ "file": "packages/core/schematics/migrate-eui-tooltip/index.ts",
23217
23217
  "coverageIgnore": false,
23218
23218
  "deprecated": false,
23219
23219
  "deprecationMessage": "",
23220
23220
  "type": "string",
23221
- "defaultValue": "'EuiMenuItem'"
23221
+ "defaultValue": "'EuiTooltipInterface'"
23222
23222
  },
23223
23223
  {
23224
23224
  "name": "NEW_INTERFACE",
23225
23225
  "ctype": "miscellaneous",
23226
23226
  "subtype": "variable",
23227
- "file": "packages/core/schematics/migrate-eui-tooltip/index.ts",
23227
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
23228
23228
  "coverageIgnore": false,
23229
23229
  "deprecated": false,
23230
23230
  "deprecationMessage": "",
23231
23231
  "type": "string",
23232
- "defaultValue": "'EuiTooltipInterface'"
23232
+ "defaultValue": "'EuiMenuItem'"
23233
23233
  },
23234
23234
  {
23235
23235
  "name": "NEW_INTERFACE_PATH",
@@ -23615,23 +23615,23 @@
23615
23615
  "name": "REMOVED_INPUTS",
23616
23616
  "ctype": "miscellaneous",
23617
23617
  "subtype": "variable",
23618
- "file": "packages/core/schematics/migrate-eui-popover/index.ts",
23618
+ "file": "packages/core/schematics/migrate-eui-table/index.ts",
23619
23619
  "coverageIgnore": false,
23620
23620
  "deprecated": false,
23621
23621
  "deprecationMessage": "",
23622
23622
  "type": "unknown",
23623
- "defaultValue": "new Set(['type'])"
23623
+ "defaultValue": "new Set(['euiTableBordered', 'isHoverable', 'defaultMultiOrder', 'paginable'])"
23624
23624
  },
23625
23625
  {
23626
23626
  "name": "REMOVED_INPUTS",
23627
23627
  "ctype": "miscellaneous",
23628
23628
  "subtype": "variable",
23629
- "file": "packages/core/schematics/migrate-eui-table/index.ts",
23629
+ "file": "packages/core/schematics/migrate-eui-popover/index.ts",
23630
23630
  "coverageIgnore": false,
23631
23631
  "deprecated": false,
23632
23632
  "deprecationMessage": "",
23633
23633
  "type": "unknown",
23634
- "defaultValue": "new Set(['euiTableBordered', 'isHoverable', 'defaultMultiOrder', 'paginable'])"
23634
+ "defaultValue": "new Set(['type'])"
23635
23635
  },
23636
23636
  {
23637
23637
  "name": "REMOVED_OUTPUT",
@@ -24788,7 +24788,7 @@
24788
24788
  },
24789
24789
  {
24790
24790
  "name": "applyEdits",
24791
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
24791
+ "file": "packages/core/schematics/migrate-to-signals/index.ts",
24792
24792
  "ctype": "miscellaneous",
24793
24793
  "subtype": "function",
24794
24794
  "coverageIgnore": false,
@@ -24878,7 +24878,7 @@
24878
24878
  },
24879
24879
  {
24880
24880
  "name": "applyEdits",
24881
- "file": "packages/core/schematics/migrate-to-signals/index.ts",
24881
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
24882
24882
  "ctype": "miscellaneous",
24883
24883
  "subtype": "function",
24884
24884
  "coverageIgnore": false,
@@ -27038,7 +27038,7 @@
27038
27038
  },
27039
27039
  {
27040
27040
  "name": "deduplicateEdits",
27041
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
27041
+ "file": "packages/core/schematics/migrate-to-signals/index.ts",
27042
27042
  "ctype": "miscellaneous",
27043
27043
  "subtype": "function",
27044
27044
  "coverageIgnore": false,
@@ -27098,7 +27098,7 @@
27098
27098
  },
27099
27099
  {
27100
27100
  "name": "deduplicateEdits",
27101
- "file": "packages/core/schematics/migrate-to-signals/index.ts",
27101
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
27102
27102
  "ctype": "miscellaneous",
27103
27103
  "subtype": "function",
27104
27104
  "coverageIgnore": false,
@@ -32579,7 +32579,7 @@
32579
32579
  },
32580
32580
  {
32581
32581
  "name": "migrateInlineTemplates",
32582
- "file": "packages/core/schematics/migrate-eui-popover/index.ts",
32582
+ "file": "packages/core/schematics/migrate-eui-progress-circle/index.ts",
32583
32583
  "ctype": "miscellaneous",
32584
32584
  "subtype": "function",
32585
32585
  "coverageIgnore": false,
@@ -32611,7 +32611,7 @@
32611
32611
  },
32612
32612
  {
32613
32613
  "name": "migrateInlineTemplates",
32614
- "file": "packages/core/schematics/migrate-eui-progress-circle/index.ts",
32614
+ "file": "packages/core/schematics/migrate-eui-popover/index.ts",
32615
32615
  "ctype": "miscellaneous",
32616
32616
  "subtype": "function",
32617
32617
  "coverageIgnore": false,
@@ -33117,38 +33117,6 @@
33117
33117
  }
33118
33118
  ]
33119
33119
  },
33120
- {
33121
- "name": "migrateTemplate",
33122
- "file": "packages/core/schematics/migrate-eui-popover/index.ts",
33123
- "ctype": "miscellaneous",
33124
- "subtype": "function",
33125
- "coverageIgnore": false,
33126
- "deprecated": false,
33127
- "deprecationMessage": "",
33128
- "rawdescription": "",
33129
- "description": "",
33130
- "displayName": "migrateTemplate",
33131
- "args": [
33132
- {
33133
- "name": "source",
33134
- "type": "string",
33135
- "deprecated": false,
33136
- "deprecationMessage": ""
33137
- }
33138
- ],
33139
- "returnType": "string",
33140
- "jsdoctags": [
33141
- {
33142
- "name": "source",
33143
- "type": "string",
33144
- "deprecated": false,
33145
- "deprecationMessage": "",
33146
- "tagName": {
33147
- "text": "param"
33148
- }
33149
- }
33150
- ]
33151
- },
33152
33120
  {
33153
33121
  "name": "migrateTemplate",
33154
33122
  "file": "packages/core/schematics/migrate-eui-progress-circle/index.ts",
@@ -33243,6 +33211,38 @@
33243
33211
  }
33244
33212
  ]
33245
33213
  },
33214
+ {
33215
+ "name": "migrateTemplate",
33216
+ "file": "packages/core/schematics/migrate-eui-popover/index.ts",
33217
+ "ctype": "miscellaneous",
33218
+ "subtype": "function",
33219
+ "coverageIgnore": false,
33220
+ "deprecated": false,
33221
+ "deprecationMessage": "",
33222
+ "rawdescription": "",
33223
+ "description": "",
33224
+ "displayName": "migrateTemplate",
33225
+ "args": [
33226
+ {
33227
+ "name": "source",
33228
+ "type": "string",
33229
+ "deprecated": false,
33230
+ "deprecationMessage": ""
33231
+ }
33232
+ ],
33233
+ "returnType": "string",
33234
+ "jsdoctags": [
33235
+ {
33236
+ "name": "source",
33237
+ "type": "string",
33238
+ "deprecated": false,
33239
+ "deprecationMessage": "",
33240
+ "tagName": {
33241
+ "text": "param"
33242
+ }
33243
+ }
33244
+ ]
33245
+ },
33246
33246
  {
33247
33247
  "name": "migrateTemplate",
33248
33248
  "file": "packages/core/schematics/migrate-eui-tabs/index.ts",
@@ -33561,7 +33561,7 @@
33561
33561
  },
33562
33562
  {
33563
33563
  "name": "migrateTypeScript",
33564
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
33564
+ "file": "packages/core/schematics/migrate-to-signals/index.ts",
33565
33565
  "ctype": "miscellaneous",
33566
33566
  "subtype": "function",
33567
33567
  "coverageIgnore": false,
@@ -33583,6 +33583,12 @@
33583
33583
  "deprecated": false,
33584
33584
  "deprecationMessage": ""
33585
33585
  },
33586
+ {
33587
+ "name": "tree",
33588
+ "type": "Tree",
33589
+ "deprecated": false,
33590
+ "deprecationMessage": ""
33591
+ },
33586
33592
  {
33587
33593
  "name": "context",
33588
33594
  "type": "SchematicContext",
@@ -33590,7 +33596,7 @@
33590
33596
  "deprecationMessage": ""
33591
33597
  }
33592
33598
  ],
33593
- "returnType": "string",
33599
+ "returnType": "MigrateResult",
33594
33600
  "jsdoctags": [
33595
33601
  {
33596
33602
  "name": "source",
@@ -33610,6 +33616,15 @@
33610
33616
  "text": "param"
33611
33617
  }
33612
33618
  },
33619
+ {
33620
+ "name": "tree",
33621
+ "type": "Tree",
33622
+ "deprecated": false,
33623
+ "deprecationMessage": "",
33624
+ "tagName": {
33625
+ "text": "param"
33626
+ }
33627
+ },
33613
33628
  {
33614
33629
  "name": "context",
33615
33630
  "type": "SchematicContext",
@@ -33685,7 +33700,7 @@
33685
33700
  },
33686
33701
  {
33687
33702
  "name": "migrateTypeScript",
33688
- "file": "packages/core/schematics/migrate-to-signals/index.ts",
33703
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
33689
33704
  "ctype": "miscellaneous",
33690
33705
  "subtype": "function",
33691
33706
  "coverageIgnore": false,
@@ -33707,12 +33722,6 @@
33707
33722
  "deprecated": false,
33708
33723
  "deprecationMessage": ""
33709
33724
  },
33710
- {
33711
- "name": "tree",
33712
- "type": "Tree",
33713
- "deprecated": false,
33714
- "deprecationMessage": ""
33715
- },
33716
33725
  {
33717
33726
  "name": "context",
33718
33727
  "type": "SchematicContext",
@@ -33720,7 +33729,7 @@
33720
33729
  "deprecationMessage": ""
33721
33730
  }
33722
33731
  ],
33723
- "returnType": "MigrateResult",
33732
+ "returnType": "string",
33724
33733
  "jsdoctags": [
33725
33734
  {
33726
33735
  "name": "source",
@@ -33740,15 +33749,6 @@
33740
33749
  "text": "param"
33741
33750
  }
33742
33751
  },
33743
- {
33744
- "name": "tree",
33745
- "type": "Tree",
33746
- "deprecated": false,
33747
- "deprecationMessage": "",
33748
- "tagName": {
33749
- "text": "param"
33750
- }
33751
- },
33752
33752
  {
33753
33753
  "name": "context",
33754
33754
  "type": "SchematicContext",
@@ -34139,7 +34139,7 @@
34139
34139
  },
34140
34140
  {
34141
34141
  "name": "removeImportSpecifier",
34142
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
34142
+ "file": "packages/core/schematics/migrate-eui-tooltip/index.ts",
34143
34143
  "ctype": "miscellaneous",
34144
34144
  "subtype": "function",
34145
34145
  "coverageIgnore": false,
@@ -34208,7 +34208,7 @@
34208
34208
  },
34209
34209
  {
34210
34210
  "name": "removeImportSpecifier",
34211
- "file": "packages/core/schematics/migrate-eui-tooltip/index.ts",
34211
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
34212
34212
  "ctype": "miscellaneous",
34213
34213
  "subtype": "function",
34214
34214
  "coverageIgnore": false,
@@ -35601,7 +35601,7 @@
35601
35601
  },
35602
35602
  {
35603
35603
  "name": "unwrapExpression",
35604
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
35604
+ "file": "packages/core/schematics/migrate-to-signals/index.ts",
35605
35605
  "ctype": "miscellaneous",
35606
35606
  "subtype": "function",
35607
35607
  "coverageIgnore": false,
@@ -35631,7 +35631,7 @@
35631
35631
  },
35632
35632
  {
35633
35633
  "name": "unwrapExpression",
35634
- "file": "packages/core/schematics/migrate-to-signals/index.ts",
35634
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
35635
35635
  "ctype": "miscellaneous",
35636
35636
  "subtype": "function",
35637
35637
  "coverageIgnore": false,
@@ -35854,7 +35854,7 @@
35854
35854
  },
35855
35855
  {
35856
35856
  "name": "visitDir",
35857
- "file": "packages/core/schematics/add-eui-imports/index.ts",
35857
+ "file": "packages/core/schematics/fix-no-multiple-empty-lines/index.ts",
35858
35858
  "ctype": "miscellaneous",
35859
35859
  "subtype": "function",
35860
35860
  "coverageIgnore": false,
@@ -35899,7 +35899,7 @@
35899
35899
  },
35900
35900
  {
35901
35901
  "name": "visitDir",
35902
- "file": "packages/core/schematics/fix-no-multiple-empty-lines/index.ts",
35902
+ "file": "packages/core/schematics/migrate-eui-accent/index.ts",
35903
35903
  "ctype": "miscellaneous",
35904
35904
  "subtype": "function",
35905
35905
  "coverageIgnore": false,
@@ -35944,7 +35944,7 @@
35944
35944
  },
35945
35945
  {
35946
35946
  "name": "visitDir",
35947
- "file": "packages/core/schematics/migrate-eui-accent/index.ts",
35947
+ "file": "packages/core/schematics/migrate-eui-alert/index.ts",
35948
35948
  "ctype": "miscellaneous",
35949
35949
  "subtype": "function",
35950
35950
  "coverageIgnore": false,
@@ -35989,7 +35989,7 @@
35989
35989
  },
35990
35990
  {
35991
35991
  "name": "visitDir",
35992
- "file": "packages/core/schematics/migrate-eui-alert/index.ts",
35992
+ "file": "packages/core/schematics/migrate-eui-avatar/index.ts",
35993
35993
  "ctype": "miscellaneous",
35994
35994
  "subtype": "function",
35995
35995
  "coverageIgnore": false,
@@ -36034,7 +36034,7 @@
36034
36034
  },
36035
36035
  {
36036
36036
  "name": "visitDir",
36037
- "file": "packages/core/schematics/migrate-eui-avatar/index.ts",
36037
+ "file": "packages/core/schematics/add-eui-imports/index.ts",
36038
36038
  "ctype": "miscellaneous",
36039
36039
  "subtype": "function",
36040
36040
  "coverageIgnore": false,
@@ -36439,7 +36439,7 @@
36439
36439
  },
36440
36440
  {
36441
36441
  "name": "visitDir",
36442
- "file": "packages/core/schematics/migrate-eui-popover/index.ts",
36442
+ "file": "packages/core/schematics/migrate-eui-progress-circle/index.ts",
36443
36443
  "ctype": "miscellaneous",
36444
36444
  "subtype": "function",
36445
36445
  "coverageIgnore": false,
@@ -36484,7 +36484,7 @@
36484
36484
  },
36485
36485
  {
36486
36486
  "name": "visitDir",
36487
- "file": "packages/core/schematics/migrate-eui-progress-circle/index.ts",
36487
+ "file": "packages/core/schematics/migrate-eui-table/index.ts",
36488
36488
  "ctype": "miscellaneous",
36489
36489
  "subtype": "function",
36490
36490
  "coverageIgnore": false,
@@ -36529,7 +36529,7 @@
36529
36529
  },
36530
36530
  {
36531
36531
  "name": "visitDir",
36532
- "file": "packages/core/schematics/migrate-eui-table/index.ts",
36532
+ "file": "packages/core/schematics/migrate-eui-popover/index.ts",
36533
36533
  "ctype": "miscellaneous",
36534
36534
  "subtype": "function",
36535
36535
  "coverageIgnore": false,
@@ -36619,7 +36619,7 @@
36619
36619
  },
36620
36620
  {
36621
36621
  "name": "visitDir",
36622
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
36622
+ "file": "packages/core/schematics/migrate-to-signals/index.ts",
36623
36623
  "ctype": "miscellaneous",
36624
36624
  "subtype": "function",
36625
36625
  "coverageIgnore": false,
@@ -36664,7 +36664,7 @@
36664
36664
  },
36665
36665
  {
36666
36666
  "name": "visitDir",
36667
- "file": "packages/core/schematics/migrate-eui-tooltip/index.ts",
36667
+ "file": "packages/core/schematics/migrate-to-standalone/index.ts",
36668
36668
  "ctype": "miscellaneous",
36669
36669
  "subtype": "function",
36670
36670
  "coverageIgnore": false,
@@ -36709,7 +36709,7 @@
36709
36709
  },
36710
36710
  {
36711
36711
  "name": "visitDir",
36712
- "file": "packages/core/schematics/migrate-to-signals/index.ts",
36712
+ "file": "packages/core/schematics/migrate-eui-tooltip/index.ts",
36713
36713
  "ctype": "miscellaneous",
36714
36714
  "subtype": "function",
36715
36715
  "coverageIgnore": false,
@@ -36754,7 +36754,7 @@
36754
36754
  },
36755
36755
  {
36756
36756
  "name": "visitDir",
36757
- "file": "packages/core/schematics/migrate-to-standalone/index.ts",
36757
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
36758
36758
  "ctype": "miscellaneous",
36759
36759
  "subtype": "function",
36760
36760
  "coverageIgnore": false,
@@ -37347,7 +37347,7 @@
37347
37347
  },
37348
37348
  {
37349
37349
  "name": "visitNodes",
37350
- "file": "packages/core/schematics/migrate-eui-popover/index.ts",
37350
+ "file": "packages/core/schematics/migrate-eui-progress-circle/index.ts",
37351
37351
  "ctype": "miscellaneous",
37352
37352
  "subtype": "function",
37353
37353
  "coverageIgnore": false,
@@ -37363,7 +37363,7 @@
37363
37363
  "deprecationMessage": ""
37364
37364
  },
37365
37365
  {
37366
- "name": "removals",
37366
+ "name": "edits",
37367
37367
  "deprecated": false,
37368
37368
  "deprecationMessage": ""
37369
37369
  }
@@ -37379,7 +37379,7 @@
37379
37379
  }
37380
37380
  },
37381
37381
  {
37382
- "name": "removals",
37382
+ "name": "edits",
37383
37383
  "deprecated": false,
37384
37384
  "deprecationMessage": "",
37385
37385
  "tagName": {
@@ -37390,7 +37390,7 @@
37390
37390
  },
37391
37391
  {
37392
37392
  "name": "visitNodes",
37393
- "file": "packages/core/schematics/migrate-eui-progress-circle/index.ts",
37393
+ "file": "packages/core/schematics/migrate-eui-popover/index.ts",
37394
37394
  "ctype": "miscellaneous",
37395
37395
  "subtype": "function",
37396
37396
  "coverageIgnore": false,
@@ -37406,7 +37406,7 @@
37406
37406
  "deprecationMessage": ""
37407
37407
  },
37408
37408
  {
37409
- "name": "edits",
37409
+ "name": "removals",
37410
37410
  "deprecated": false,
37411
37411
  "deprecationMessage": ""
37412
37412
  }
@@ -37422,7 +37422,7 @@
37422
37422
  }
37423
37423
  },
37424
37424
  {
37425
- "name": "edits",
37425
+ "name": "removals",
37426
37426
  "deprecated": false,
37427
37427
  "deprecationMessage": "",
37428
37428
  "tagName": {
@@ -47529,10 +47529,10 @@
47529
47529
  ]
47530
47530
  }
47531
47531
  ],
47532
- "packages/core/schematics/migrate-eui-toolbar-menu/index.ts": [
47532
+ "packages/core/schematics/migrate-to-signals/index.ts": [
47533
47533
  {
47534
47534
  "name": "applyEdits",
47535
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
47535
+ "file": "packages/core/schematics/migrate-to-signals/index.ts",
47536
47536
  "ctype": "miscellaneous",
47537
47537
  "subtype": "function",
47538
47538
  "coverageIgnore": false,
@@ -47576,20 +47576,19 @@
47576
47576
  ]
47577
47577
  },
47578
47578
  {
47579
- "name": "collectOutputRemovals",
47580
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
47579
+ "name": "buildTemplateRefMap",
47580
+ "file": "packages/core/schematics/migrate-to-signals/index.ts",
47581
47581
  "ctype": "miscellaneous",
47582
47582
  "subtype": "function",
47583
47583
  "coverageIgnore": false,
47584
47584
  "deprecated": false,
47585
47585
  "deprecationMessage": "",
47586
- "rawdescription": "",
47587
- "description": "",
47588
- "displayName": "collectOutputRemovals",
47586
+ "rawdescription": "Builds a map of template reference variable names (#ref) to EUI class names.\n\nParses the component's template (inline or external) and finds elements like:\n <eui-alert #myAlert> → myAlert → EuiAlertComponent\n <input euiAutocomplete #ac> → ac → EuiAutocompleteComponent\n\nReturns Map<refName, euiClassName>.",
47587
+ "description": "<p>Builds a map of template reference variable names (#ref) to EUI class names.</p>\n<p>Parses the component&#39;s template (inline or external) and finds elements like:\n &lt;eui-alert #myAlert&gt; → myAlert → EuiAlertComponent\n &lt;input euiAutocomplete #ac&gt; → ac → EuiAutocompleteComponent</p>\n<p>Returns Map&lt;refName, euiClassName&gt;.</p>\n",
47588
+ "displayName": "buildTemplateRefMap",
47589
47589
  "args": [
47590
47590
  {
47591
- "name": "element",
47592
- "type": "TmplAstElement",
47591
+ "name": "sourceFile",
47593
47592
  "deprecated": false,
47594
47593
  "deprecationMessage": ""
47595
47594
  },
@@ -47599,11 +47598,6 @@
47599
47598
  "deprecated": false,
47600
47599
  "deprecationMessage": ""
47601
47600
  },
47602
- {
47603
- "name": "edits",
47604
- "deprecated": false,
47605
- "deprecationMessage": ""
47606
- },
47607
47601
  {
47608
47602
  "name": "filePath",
47609
47603
  "type": "string",
@@ -47611,17 +47605,16 @@
47611
47605
  "deprecationMessage": ""
47612
47606
  },
47613
47607
  {
47614
- "name": "context",
47615
- "type": "SchematicContext",
47608
+ "name": "tree",
47609
+ "type": "Tree",
47616
47610
  "deprecated": false,
47617
47611
  "deprecationMessage": ""
47618
47612
  }
47619
47613
  ],
47620
- "returnType": "void",
47614
+ "returnType": "Map<string, string>",
47621
47615
  "jsdoctags": [
47622
47616
  {
47623
- "name": "element",
47624
- "type": "TmplAstElement",
47617
+ "name": "sourceFile",
47625
47618
  "deprecated": false,
47626
47619
  "deprecationMessage": "",
47627
47620
  "tagName": {
@@ -47637,14 +47630,6 @@
47637
47630
  "text": "param"
47638
47631
  }
47639
47632
  },
47640
- {
47641
- "name": "edits",
47642
- "deprecated": false,
47643
- "deprecationMessage": "",
47644
- "tagName": {
47645
- "text": "param"
47646
- }
47647
- },
47648
47633
  {
47649
47634
  "name": "filePath",
47650
47635
  "type": "string",
@@ -47655,8 +47640,8 @@
47655
47640
  }
47656
47641
  },
47657
47642
  {
47658
- "name": "context",
47659
- "type": "SchematicContext",
47643
+ "name": "tree",
47644
+ "type": "Tree",
47660
47645
  "deprecated": false,
47661
47646
  "deprecationMessage": "",
47662
47647
  "tagName": {
@@ -47666,20 +47651,19 @@
47666
47651
  ]
47667
47652
  },
47668
47653
  {
47669
- "name": "collectTagRenames",
47670
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
47654
+ "name": "collectEuiReferences",
47655
+ "file": "packages/core/schematics/migrate-to-signals/index.ts",
47671
47656
  "ctype": "miscellaneous",
47672
47657
  "subtype": "function",
47673
47658
  "coverageIgnore": false,
47674
47659
  "deprecated": false,
47675
47660
  "deprecationMessage": "",
47676
- "rawdescription": "",
47677
- "description": "",
47678
- "displayName": "collectTagRenames",
47661
+ "rawdescription": "Scans the source file for class properties or variables typed as EUI component classes.\nReturns a map: localName → Set of signal property names.\n\nHandles:",
47662
+ "description": "<p>Scans the source file for class properties or variables typed as EUI component classes.\nReturns a map: localName → Set of signal property names.</p>\n<p>Handles:</p>\n",
47663
+ "displayName": "collectEuiReferences",
47679
47664
  "args": [
47680
47665
  {
47681
- "name": "element",
47682
- "type": "TmplAstElement",
47666
+ "name": "sourceFile",
47683
47667
  "deprecated": false,
47684
47668
  "deprecationMessage": ""
47685
47669
  },
@@ -47690,16 +47674,16 @@
47690
47674
  "deprecationMessage": ""
47691
47675
  },
47692
47676
  {
47693
- "name": "edits",
47677
+ "name": "templateRefMap",
47678
+ "type": "Map",
47694
47679
  "deprecated": false,
47695
47680
  "deprecationMessage": ""
47696
47681
  }
47697
47682
  ],
47698
- "returnType": "void",
47683
+ "returnType": "Map<string, ReadonlySet<string>>",
47699
47684
  "jsdoctags": [
47700
47685
  {
47701
- "name": "element",
47702
- "type": "TmplAstElement",
47686
+ "name": "sourceFile",
47703
47687
  "deprecated": false,
47704
47688
  "deprecationMessage": "",
47705
47689
  "tagName": {
@@ -47716,7 +47700,53 @@
47716
47700
  }
47717
47701
  },
47718
47702
  {
47719
- "name": "edits",
47703
+ "name": "templateRefMap",
47704
+ "type": "Map",
47705
+ "deprecated": false,
47706
+ "deprecationMessage": "",
47707
+ "tagName": {
47708
+ "text": "param"
47709
+ }
47710
+ }
47711
+ ]
47712
+ },
47713
+ {
47714
+ "name": "collectTemplateRefs",
47715
+ "file": "packages/core/schematics/migrate-to-signals/index.ts",
47716
+ "ctype": "miscellaneous",
47717
+ "subtype": "function",
47718
+ "coverageIgnore": false,
47719
+ "deprecated": false,
47720
+ "deprecationMessage": "",
47721
+ "rawdescription": "Recursively walks template AST nodes, collecting #refName → EUI class name mappings.\n\nFor each element with a template reference (#ref), we try to resolve its EUI class:\n1. Match element tag name against EUI_SELECTOR_TO_CLASS (e.g. 'eui-alert' → EuiAlertComponent)\n2. Match element attributes against attribute selectors (e.g. [euiAutocomplete] → EuiAutocompleteComponent)",
47722
+ "description": "<p>Recursively walks template AST nodes, collecting #refName → EUI class name mappings.</p>\n<p>For each element with a template reference (#ref), we try to resolve its EUI class:</p>\n<ol>\n<li>Match element tag name against EUI_SELECTOR_TO_CLASS (e.g. &#39;eui-alert&#39; → EuiAlertComponent)</li>\n<li>Match element attributes against attribute selectors (e.g. [euiAutocomplete] → EuiAutocompleteComponent)</li>\n</ol>\n",
47723
+ "displayName": "collectTemplateRefs",
47724
+ "args": [
47725
+ {
47726
+ "name": "nodes",
47727
+ "deprecated": false,
47728
+ "deprecationMessage": ""
47729
+ },
47730
+ {
47731
+ "name": "refMap",
47732
+ "type": "Map",
47733
+ "deprecated": false,
47734
+ "deprecationMessage": ""
47735
+ }
47736
+ ],
47737
+ "returnType": "void",
47738
+ "jsdoctags": [
47739
+ {
47740
+ "name": "nodes",
47741
+ "deprecated": false,
47742
+ "deprecationMessage": "",
47743
+ "tagName": {
47744
+ "text": "param"
47745
+ }
47746
+ },
47747
+ {
47748
+ "name": "refMap",
47749
+ "type": "Map",
47720
47750
  "deprecated": false,
47721
47751
  "deprecationMessage": "",
47722
47752
  "tagName": {
@@ -47727,7 +47757,7 @@
47727
47757
  },
47728
47758
  {
47729
47759
  "name": "deduplicateEdits",
47730
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
47760
+ "file": "packages/core/schematics/migrate-to-signals/index.ts",
47731
47761
  "ctype": "miscellaneous",
47732
47762
  "subtype": "function",
47733
47763
  "coverageIgnore": false,
@@ -47756,27 +47786,42 @@
47756
47786
  ]
47757
47787
  },
47758
47788
  {
47759
- "name": "isComponentMetadataProperty",
47760
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
47789
+ "name": "findComponentMetadata",
47790
+ "file": "packages/core/schematics/migrate-to-signals/index.ts",
47761
47791
  "ctype": "miscellaneous",
47762
47792
  "subtype": "function",
47763
47793
  "coverageIgnore": false,
47764
47794
  "deprecated": false,
47765
47795
  "deprecationMessage": "",
47766
- "rawdescription": "",
47767
- "description": "",
47768
- "displayName": "isComponentMetadataProperty",
47796
+ "rawdescription": "Extracts",
47797
+ "description": "<p>Extracts</p>\n",
47798
+ "displayName": "findComponentMetadata",
47769
47799
  "args": [
47770
47800
  {
47771
- "name": "node",
47801
+ "name": "sourceFile",
47802
+ "deprecated": false,
47803
+ "deprecationMessage": ""
47804
+ },
47805
+ {
47806
+ "name": "source",
47807
+ "type": "string",
47772
47808
  "deprecated": false,
47773
47809
  "deprecationMessage": ""
47774
47810
  }
47775
47811
  ],
47776
- "returnType": "boolean",
47812
+ "returnType": "literal type | null",
47777
47813
  "jsdoctags": [
47778
47814
  {
47779
- "name": "node",
47815
+ "name": "sourceFile",
47816
+ "deprecated": false,
47817
+ "deprecationMessage": "",
47818
+ "tagName": {
47819
+ "text": "param"
47820
+ }
47821
+ },
47822
+ {
47823
+ "name": "source",
47824
+ "type": "string",
47780
47825
  "deprecated": false,
47781
47826
  "deprecationMessage": "",
47782
47827
  "tagName": {
@@ -47786,8 +47831,8 @@
47786
47831
  ]
47787
47832
  },
47788
47833
  {
47789
- "name": "isTemplateProperty",
47790
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
47834
+ "name": "getDecoratorName",
47835
+ "file": "packages/core/schematics/migrate-to-signals/index.ts",
47791
47836
  "ctype": "miscellaneous",
47792
47837
  "subtype": "function",
47793
47838
  "coverageIgnore": false,
@@ -47795,18 +47840,18 @@
47795
47840
  "deprecationMessage": "",
47796
47841
  "rawdescription": "",
47797
47842
  "description": "",
47798
- "displayName": "isTemplateProperty",
47843
+ "displayName": "getDecoratorName",
47799
47844
  "args": [
47800
47845
  {
47801
- "name": "node",
47846
+ "name": "callExpr",
47802
47847
  "deprecated": false,
47803
47848
  "deprecationMessage": ""
47804
47849
  }
47805
47850
  ],
47806
- "returnType": "boolean",
47851
+ "returnType": "string | null",
47807
47852
  "jsdoctags": [
47808
47853
  {
47809
- "name": "node",
47854
+ "name": "callExpr",
47810
47855
  "deprecated": false,
47811
47856
  "deprecationMessage": "",
47812
47857
  "tagName": {
@@ -47816,8 +47861,8 @@
47816
47861
  ]
47817
47862
  },
47818
47863
  {
47819
- "name": "migrateEuiToolbarMenu",
47820
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
47864
+ "name": "getTypeAnnotationName",
47865
+ "file": "packages/core/schematics/migrate-to-signals/index.ts",
47821
47866
  "ctype": "miscellaneous",
47822
47867
  "subtype": "function",
47823
47868
  "coverageIgnore": false,
@@ -47825,24 +47870,20 @@
47825
47870
  "deprecationMessage": "",
47826
47871
  "rawdescription": "",
47827
47872
  "description": "",
47828
- "displayName": "migrateEuiToolbarMenu",
47873
+ "displayName": "getTypeAnnotationName",
47829
47874
  "args": [
47830
47875
  {
47831
- "name": "options",
47832
- "type": "Schema",
47876
+ "name": "typeNode",
47833
47877
  "deprecated": false,
47834
- "deprecationMessage": "",
47835
- "defaultValue": "{}"
47878
+ "deprecationMessage": ""
47836
47879
  }
47837
47880
  ],
47838
- "returnType": "Rule",
47881
+ "returnType": "string | null",
47839
47882
  "jsdoctags": [
47840
47883
  {
47841
- "name": "options",
47842
- "type": "Schema",
47884
+ "name": "typeNode",
47843
47885
  "deprecated": false,
47844
47886
  "deprecationMessage": "",
47845
- "defaultValue": "{}",
47846
47887
  "tagName": {
47847
47888
  "text": "param"
47848
47889
  }
@@ -47850,37 +47891,25 @@
47850
47891
  ]
47851
47892
  },
47852
47893
  {
47853
- "name": "migrateImportsAndTypes",
47854
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
47894
+ "name": "hasEuiReference",
47895
+ "file": "packages/core/schematics/migrate-to-signals/index.ts",
47855
47896
  "ctype": "miscellaneous",
47856
47897
  "subtype": "function",
47857
47898
  "coverageIgnore": false,
47858
47899
  "deprecated": false,
47859
47900
  "deprecationMessage": "",
47860
- "rawdescription": "",
47861
- "description": "",
47862
- "displayName": "migrateImportsAndTypes",
47901
+ "rawdescription": "Quick check: does the file reference any EUI class name from the signal map?",
47902
+ "description": "<p>Quick check: does the file reference any EUI class name from the signal map?</p>\n",
47903
+ "displayName": "hasEuiReference",
47863
47904
  "args": [
47864
47905
  {
47865
47906
  "name": "source",
47866
47907
  "type": "string",
47867
47908
  "deprecated": false,
47868
47909
  "deprecationMessage": ""
47869
- },
47870
- {
47871
- "name": "filePath",
47872
- "type": "string",
47873
- "deprecated": false,
47874
- "deprecationMessage": ""
47875
- },
47876
- {
47877
- "name": "context",
47878
- "type": "SchematicContext",
47879
- "deprecated": false,
47880
- "deprecationMessage": ""
47881
47910
  }
47882
47911
  ],
47883
- "returnType": "string",
47912
+ "returnType": "boolean",
47884
47913
  "jsdoctags": [
47885
47914
  {
47886
47915
  "name": "source",
@@ -47890,59 +47919,29 @@
47890
47919
  "tagName": {
47891
47920
  "text": "param"
47892
47921
  }
47893
- },
47894
- {
47895
- "name": "filePath",
47896
- "type": "string",
47897
- "deprecated": false,
47898
- "deprecationMessage": "",
47899
- "tagName": {
47900
- "text": "param"
47901
- }
47902
- },
47903
- {
47904
- "name": "context",
47905
- "type": "SchematicContext",
47906
- "deprecated": false,
47907
- "deprecationMessage": "",
47908
- "tagName": {
47909
- "text": "param"
47910
- }
47911
47922
  }
47912
47923
  ]
47913
47924
  },
47914
47925
  {
47915
- "name": "migrateInlineTemplates",
47916
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
47926
+ "name": "hasStringRefQuery",
47927
+ "file": "packages/core/schematics/migrate-to-signals/index.ts",
47917
47928
  "ctype": "miscellaneous",
47918
47929
  "subtype": "function",
47919
47930
  "coverageIgnore": false,
47920
47931
  "deprecated": false,
47921
47932
  "deprecationMessage": "",
47922
- "rawdescription": "",
47923
- "description": "",
47924
- "displayName": "migrateInlineTemplates",
47933
+ "rawdescription": "Quick check: does the file have",
47934
+ "description": "<p>Quick check: does the file have</p>\n",
47935
+ "displayName": "hasStringRefQuery",
47925
47936
  "args": [
47926
47937
  {
47927
47938
  "name": "source",
47928
47939
  "type": "string",
47929
47940
  "deprecated": false,
47930
47941
  "deprecationMessage": ""
47931
- },
47932
- {
47933
- "name": "filePath",
47934
- "type": "string",
47935
- "deprecated": false,
47936
- "deprecationMessage": ""
47937
- },
47938
- {
47939
- "name": "context",
47940
- "type": "SchematicContext",
47941
- "deprecated": false,
47942
- "deprecationMessage": ""
47943
47942
  }
47944
47943
  ],
47945
- "returnType": "string",
47944
+ "returnType": "boolean",
47946
47945
  "jsdoctags": [
47947
47946
  {
47948
47947
  "name": "source",
@@ -47952,30 +47951,12 @@
47952
47951
  "tagName": {
47953
47952
  "text": "param"
47954
47953
  }
47955
- },
47956
- {
47957
- "name": "filePath",
47958
- "type": "string",
47959
- "deprecated": false,
47960
- "deprecationMessage": "",
47961
- "tagName": {
47962
- "text": "param"
47963
- }
47964
- },
47965
- {
47966
- "name": "context",
47967
- "type": "SchematicContext",
47968
- "deprecated": false,
47969
- "deprecationMessage": "",
47970
- "tagName": {
47971
- "text": "param"
47972
- }
47973
47954
  }
47974
47955
  ]
47975
47956
  },
47976
47957
  {
47977
- "name": "migrateTemplate",
47978
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
47958
+ "name": "isAssignmentOperator",
47959
+ "file": "packages/core/schematics/migrate-to-signals/index.ts",
47979
47960
  "ctype": "miscellaneous",
47980
47961
  "subtype": "function",
47981
47962
  "coverageIgnore": false,
@@ -47983,52 +47964,54 @@
47983
47964
  "deprecationMessage": "",
47984
47965
  "rawdescription": "",
47985
47966
  "description": "",
47986
- "displayName": "migrateTemplate",
47967
+ "displayName": "isAssignmentOperator",
47987
47968
  "args": [
47988
47969
  {
47989
- "name": "source",
47990
- "type": "string",
47991
- "deprecated": false,
47992
- "deprecationMessage": ""
47993
- },
47994
- {
47995
- "name": "filePath",
47996
- "type": "string",
47997
- "deprecated": false,
47998
- "deprecationMessage": ""
47999
- },
48000
- {
48001
- "name": "context",
48002
- "type": "SchematicContext",
47970
+ "name": "kind",
48003
47971
  "deprecated": false,
48004
47972
  "deprecationMessage": ""
48005
47973
  }
48006
47974
  ],
48007
- "returnType": "string",
47975
+ "returnType": "boolean",
48008
47976
  "jsdoctags": [
48009
47977
  {
48010
- "name": "source",
48011
- "type": "string",
47978
+ "name": "kind",
48012
47979
  "deprecated": false,
48013
47980
  "deprecationMessage": "",
48014
47981
  "tagName": {
48015
47982
  "text": "param"
48016
47983
  }
48017
- },
47984
+ }
47985
+ ]
47986
+ },
47987
+ {
47988
+ "name": "migrateToSignals",
47989
+ "file": "packages/core/schematics/migrate-to-signals/index.ts",
47990
+ "ctype": "miscellaneous",
47991
+ "subtype": "function",
47992
+ "coverageIgnore": false,
47993
+ "deprecated": false,
47994
+ "deprecationMessage": "",
47995
+ "rawdescription": "Migrates consumer code that accesses EUI component instances programmatically.\n\nWhen EUI migrated",
47996
+ "description": "<p>Migrates consumer code that accesses EUI component instances programmatically.</p>\n<p>When EUI migrated</p>\n",
47997
+ "displayName": "migrateToSignals",
47998
+ "args": [
48018
47999
  {
48019
- "name": "filePath",
48020
- "type": "string",
48000
+ "name": "options",
48001
+ "type": "Schema",
48021
48002
  "deprecated": false,
48022
48003
  "deprecationMessage": "",
48023
- "tagName": {
48024
- "text": "param"
48025
- }
48026
- },
48004
+ "defaultValue": "{}"
48005
+ }
48006
+ ],
48007
+ "returnType": "Rule",
48008
+ "jsdoctags": [
48027
48009
  {
48028
- "name": "context",
48029
- "type": "SchematicContext",
48010
+ "name": "options",
48011
+ "type": "Schema",
48030
48012
  "deprecated": false,
48031
48013
  "deprecationMessage": "",
48014
+ "defaultValue": "{}",
48032
48015
  "tagName": {
48033
48016
  "text": "param"
48034
48017
  }
@@ -48037,7 +48020,7 @@
48037
48020
  },
48038
48021
  {
48039
48022
  "name": "migrateTypeScript",
48040
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
48023
+ "file": "packages/core/schematics/migrate-to-signals/index.ts",
48041
48024
  "ctype": "miscellaneous",
48042
48025
  "subtype": "function",
48043
48026
  "coverageIgnore": false,
@@ -48059,6 +48042,12 @@
48059
48042
  "deprecated": false,
48060
48043
  "deprecationMessage": ""
48061
48044
  },
48045
+ {
48046
+ "name": "tree",
48047
+ "type": "Tree",
48048
+ "deprecated": false,
48049
+ "deprecationMessage": ""
48050
+ },
48062
48051
  {
48063
48052
  "name": "context",
48064
48053
  "type": "SchematicContext",
@@ -48066,7 +48055,7 @@
48066
48055
  "deprecationMessage": ""
48067
48056
  }
48068
48057
  ],
48069
- "returnType": "string",
48058
+ "returnType": "MigrateResult",
48070
48059
  "jsdoctags": [
48071
48060
  {
48072
48061
  "name": "source",
@@ -48086,6 +48075,15 @@
48086
48075
  "text": "param"
48087
48076
  }
48088
48077
  },
48078
+ {
48079
+ "name": "tree",
48080
+ "type": "Tree",
48081
+ "deprecated": false,
48082
+ "deprecationMessage": "",
48083
+ "tagName": {
48084
+ "text": "param"
48085
+ }
48086
+ },
48089
48087
  {
48090
48088
  "name": "context",
48091
48089
  "type": "SchematicContext",
@@ -48098,24 +48096,51 @@
48098
48096
  ]
48099
48097
  },
48100
48098
  {
48101
- "name": "removeImportSpecifier",
48102
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
48099
+ "name": "resolveElementToEuiClass",
48100
+ "file": "packages/core/schematics/migrate-to-signals/index.ts",
48103
48101
  "ctype": "miscellaneous",
48104
48102
  "subtype": "function",
48105
48103
  "coverageIgnore": false,
48106
48104
  "deprecated": false,
48107
48105
  "deprecationMessage": "",
48108
- "rawdescription": "",
48109
- "description": "",
48110
- "displayName": "removeImportSpecifier",
48106
+ "rawdescription": "Given a template element, resolves it to an EUI class name by checking:\n1. Tag name: <eui-alert> → look up 'eui-alert' in EUI_SELECTOR_TO_CLASS\n2. Attribute selectors: <input euiAutocomplete> → look up 'input[euiAutocomplete]' or '[euiAutocomplete]'",
48107
+ "description": "<p>Given a template element, resolves it to an EUI class name by checking:</p>\n<ol>\n<li>Tag name: <eui-alert> → look up &#39;eui-alert&#39; in EUI_SELECTOR_TO_CLASS</li>\n<li>Attribute selectors: <input euiAutocomplete> → look up &#39;input[euiAutocomplete]&#39; or &#39;[euiAutocomplete]&#39;</li>\n</ol>\n",
48108
+ "displayName": "resolveElementToEuiClass",
48111
48109
  "args": [
48112
48110
  {
48113
- "name": "namedImports",
48111
+ "name": "element",
48112
+ "type": "TmplAstElement",
48114
48113
  "deprecated": false,
48115
48114
  "deprecationMessage": ""
48116
- },
48115
+ }
48116
+ ],
48117
+ "returnType": "string | null",
48118
+ "jsdoctags": [
48117
48119
  {
48118
- "name": "specifier",
48120
+ "name": "element",
48121
+ "type": "TmplAstElement",
48122
+ "deprecated": false,
48123
+ "deprecationMessage": "",
48124
+ "tagName": {
48125
+ "text": "param"
48126
+ }
48127
+ }
48128
+ ]
48129
+ },
48130
+ {
48131
+ "name": "resolveEuiClassName",
48132
+ "file": "packages/core/schematics/migrate-to-signals/index.ts",
48133
+ "ctype": "miscellaneous",
48134
+ "subtype": "function",
48135
+ "coverageIgnore": false,
48136
+ "deprecated": false,
48137
+ "deprecationMessage": "",
48138
+ "rawdescription": "Resolves the EUI class name from a property declaration.\nChecks: type annotation → decorator class argument → template ref map.",
48139
+ "description": "<p>Resolves the EUI class name from a property declaration.\nChecks: type annotation → decorator class argument → template ref map.</p>\n",
48140
+ "displayName": "resolveEuiClassName",
48141
+ "args": [
48142
+ {
48143
+ "name": "node",
48119
48144
  "deprecated": false,
48120
48145
  "deprecationMessage": ""
48121
48146
  },
@@ -48125,23 +48150,16 @@
48125
48150
  "deprecationMessage": ""
48126
48151
  },
48127
48152
  {
48128
- "name": "edits",
48153
+ "name": "templateRefMap",
48154
+ "type": "Map",
48129
48155
  "deprecated": false,
48130
48156
  "deprecationMessage": ""
48131
48157
  }
48132
48158
  ],
48133
- "returnType": "void",
48159
+ "returnType": "string | null",
48134
48160
  "jsdoctags": [
48135
48161
  {
48136
- "name": "namedImports",
48137
- "deprecated": false,
48138
- "deprecationMessage": "",
48139
- "tagName": {
48140
- "text": "param"
48141
- }
48142
- },
48143
- {
48144
- "name": "specifier",
48162
+ "name": "node",
48145
48163
  "deprecated": false,
48146
48164
  "deprecationMessage": "",
48147
48165
  "tagName": {
@@ -48157,7 +48175,8 @@
48157
48175
  }
48158
48176
  },
48159
48177
  {
48160
- "name": "edits",
48178
+ "name": "templateRefMap",
48179
+ "type": "Map",
48161
48180
  "deprecated": false,
48162
48181
  "deprecationMessage": "",
48163
48182
  "tagName": {
@@ -48167,27 +48186,27 @@
48167
48186
  ]
48168
48187
  },
48169
48188
  {
48170
- "name": "unwrapExpression",
48171
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
48189
+ "name": "resolveRefName",
48190
+ "file": "packages/core/schematics/migrate-to-signals/index.ts",
48172
48191
  "ctype": "miscellaneous",
48173
48192
  "subtype": "function",
48174
48193
  "coverageIgnore": false,
48175
48194
  "deprecated": false,
48176
48195
  "deprecationMessage": "",
48177
- "rawdescription": "",
48178
- "description": "",
48179
- "displayName": "unwrapExpression",
48196
+ "rawdescription": "Resolves a reference name from an expression.\nHandles: `ref`, `this.ref`, `this._ref`",
48197
+ "description": "<p>Resolves a reference name from an expression.\nHandles: <code>ref</code>, <code>this.ref</code>, <code>this._ref</code></p>\n",
48198
+ "displayName": "resolveRefName",
48180
48199
  "args": [
48181
48200
  {
48182
- "name": "expression",
48201
+ "name": "expr",
48183
48202
  "deprecated": false,
48184
48203
  "deprecationMessage": ""
48185
48204
  }
48186
48205
  ],
48187
- "returnType": "ts.Expression",
48206
+ "returnType": "string | null",
48188
48207
  "jsdoctags": [
48189
48208
  {
48190
- "name": "expression",
48209
+ "name": "expr",
48191
48210
  "deprecated": false,
48192
48211
  "deprecationMessage": "",
48193
48212
  "tagName": {
@@ -48197,64 +48216,30 @@
48197
48216
  ]
48198
48217
  },
48199
48218
  {
48200
- "name": "visitDir",
48201
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
48219
+ "name": "tryRewritePropertyAccess",
48220
+ "file": "packages/core/schematics/migrate-to-signals/index.ts",
48202
48221
  "ctype": "miscellaneous",
48203
48222
  "subtype": "function",
48204
48223
  "coverageIgnore": false,
48205
48224
  "deprecated": false,
48206
48225
  "deprecationMessage": "",
48207
- "rawdescription": "",
48208
- "description": "",
48209
- "displayName": "visitDir",
48226
+ "rawdescription": "Checks if a property access `ref.prop` should be rewritten to `ref.prop()`.\n\nWe rewrite if:\n- `ref` is tracked in euiRefs\n- `prop` is in the signal set for that EUI class\n- The access is NOT already a call expression (not `ref.prop()` or `ref.prop(args)`)\n- The access is NOT a write target (not `ref.prop = value`)\n- The access is NOT a template binding argument (those are unaffected)",
48227
+ "description": "<p>Checks if a property access <code>ref.prop</code> should be rewritten to <code>ref.prop()</code>.</p>\n<p>We rewrite if:</p>\n<ul>\n<li><code>ref</code> is tracked in euiRefs</li>\n<li><code>prop</code> is in the signal set for that EUI class</li>\n<li>The access is NOT already a call expression (not <code>ref.prop()</code> or <code>ref.prop(args)</code>)</li>\n<li>The access is NOT a write target (not <code>ref.prop = value</code>)</li>\n<li>The access is NOT a template binding argument (those are unaffected)</li>\n</ul>\n",
48228
+ "displayName": "tryRewritePropertyAccess",
48210
48229
  "args": [
48211
48230
  {
48212
- "name": "dir",
48213
- "type": "DirEntry",
48231
+ "name": "node",
48214
48232
  "deprecated": false,
48215
48233
  "deprecationMessage": ""
48216
48234
  },
48217
48235
  {
48218
- "name": "callback",
48236
+ "name": "euiRefs",
48237
+ "type": "Map",
48219
48238
  "deprecated": false,
48220
48239
  "deprecationMessage": ""
48221
- }
48222
- ],
48223
- "returnType": "void",
48224
- "jsdoctags": [
48225
- {
48226
- "name": "dir",
48227
- "type": "DirEntry",
48228
- "deprecated": false,
48229
- "deprecationMessage": "",
48230
- "tagName": {
48231
- "text": "param"
48232
- }
48233
48240
  },
48234
48241
  {
48235
- "name": "callback",
48236
- "deprecated": false,
48237
- "deprecationMessage": "",
48238
- "tagName": {
48239
- "text": "param"
48240
- }
48241
- }
48242
- ]
48243
- },
48244
- {
48245
- "name": "visitNodes",
48246
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
48247
- "ctype": "miscellaneous",
48248
- "subtype": "function",
48249
- "coverageIgnore": false,
48250
- "deprecated": false,
48251
- "deprecationMessage": "",
48252
- "rawdescription": "",
48253
- "description": "",
48254
- "displayName": "visitNodes",
48255
- "args": [
48256
- {
48257
- "name": "nodes",
48242
+ "name": "sourceFile",
48258
48243
  "deprecated": false,
48259
48244
  "deprecationMessage": ""
48260
48245
  },
@@ -48263,29 +48248,12 @@
48263
48248
  "type": "string",
48264
48249
  "deprecated": false,
48265
48250
  "deprecationMessage": ""
48266
- },
48267
- {
48268
- "name": "edits",
48269
- "deprecated": false,
48270
- "deprecationMessage": ""
48271
- },
48272
- {
48273
- "name": "filePath",
48274
- "type": "string",
48275
- "deprecated": false,
48276
- "deprecationMessage": ""
48277
- },
48278
- {
48279
- "name": "context",
48280
- "type": "SchematicContext",
48281
- "deprecated": false,
48282
- "deprecationMessage": ""
48283
48251
  }
48284
48252
  ],
48285
- "returnType": "void",
48253
+ "returnType": "Edit | null",
48286
48254
  "jsdoctags": [
48287
48255
  {
48288
- "name": "nodes",
48256
+ "name": "node",
48289
48257
  "deprecated": false,
48290
48258
  "deprecationMessage": "",
48291
48259
  "tagName": {
@@ -48293,8 +48261,8 @@
48293
48261
  }
48294
48262
  },
48295
48263
  {
48296
- "name": "source",
48297
- "type": "string",
48264
+ "name": "euiRefs",
48265
+ "type": "Map",
48298
48266
  "deprecated": false,
48299
48267
  "deprecationMessage": "",
48300
48268
  "tagName": {
@@ -48302,7 +48270,7 @@
48302
48270
  }
48303
48271
  },
48304
48272
  {
48305
- "name": "edits",
48273
+ "name": "sourceFile",
48306
48274
  "deprecated": false,
48307
48275
  "deprecationMessage": "",
48308
48276
  "tagName": {
@@ -48310,17 +48278,38 @@
48310
48278
  }
48311
48279
  },
48312
48280
  {
48313
- "name": "filePath",
48281
+ "name": "source",
48314
48282
  "type": "string",
48315
48283
  "deprecated": false,
48316
48284
  "deprecationMessage": "",
48317
48285
  "tagName": {
48318
48286
  "text": "param"
48319
48287
  }
48320
- },
48288
+ }
48289
+ ]
48290
+ },
48291
+ {
48292
+ "name": "unwrapExpression",
48293
+ "file": "packages/core/schematics/migrate-to-signals/index.ts",
48294
+ "ctype": "miscellaneous",
48295
+ "subtype": "function",
48296
+ "coverageIgnore": false,
48297
+ "deprecated": false,
48298
+ "deprecationMessage": "",
48299
+ "rawdescription": "",
48300
+ "description": "",
48301
+ "displayName": "unwrapExpression",
48302
+ "args": [
48321
48303
  {
48322
- "name": "context",
48323
- "type": "SchematicContext",
48304
+ "name": "expression",
48305
+ "deprecated": false,
48306
+ "deprecationMessage": ""
48307
+ }
48308
+ ],
48309
+ "returnType": "ts.Expression",
48310
+ "jsdoctags": [
48311
+ {
48312
+ "name": "expression",
48324
48313
  "deprecated": false,
48325
48314
  "deprecationMessage": "",
48326
48315
  "tagName": {
@@ -48330,8 +48319,8 @@
48330
48319
  ]
48331
48320
  },
48332
48321
  {
48333
- "name": "warnRemovedProperties",
48334
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
48322
+ "name": "visitDir",
48323
+ "file": "packages/core/schematics/migrate-to-signals/index.ts",
48335
48324
  "ctype": "miscellaneous",
48336
48325
  "subtype": "function",
48337
48326
  "coverageIgnore": false,
@@ -48339,22 +48328,16 @@
48339
48328
  "deprecationMessage": "",
48340
48329
  "rawdescription": "",
48341
48330
  "description": "",
48342
- "displayName": "warnRemovedProperties",
48331
+ "displayName": "visitDir",
48343
48332
  "args": [
48344
48333
  {
48345
- "name": "sourceFile",
48346
- "deprecated": false,
48347
- "deprecationMessage": ""
48348
- },
48349
- {
48350
- "name": "filePath",
48351
- "type": "string",
48334
+ "name": "dir",
48335
+ "type": "DirEntry",
48352
48336
  "deprecated": false,
48353
48337
  "deprecationMessage": ""
48354
48338
  },
48355
48339
  {
48356
- "name": "context",
48357
- "type": "SchematicContext",
48340
+ "name": "callback",
48358
48341
  "deprecated": false,
48359
48342
  "deprecationMessage": ""
48360
48343
  }
@@ -48362,16 +48345,8 @@
48362
48345
  "returnType": "void",
48363
48346
  "jsdoctags": [
48364
48347
  {
48365
- "name": "sourceFile",
48366
- "deprecated": false,
48367
- "deprecationMessage": "",
48368
- "tagName": {
48369
- "text": "param"
48370
- }
48371
- },
48372
- {
48373
- "name": "filePath",
48374
- "type": "string",
48348
+ "name": "dir",
48349
+ "type": "DirEntry",
48375
48350
  "deprecated": false,
48376
48351
  "deprecationMessage": "",
48377
48352
  "tagName": {
@@ -48379,8 +48354,7 @@
48379
48354
  }
48380
48355
  },
48381
48356
  {
48382
- "name": "context",
48383
- "type": "SchematicContext",
48357
+ "name": "callback",
48384
48358
  "deprecated": false,
48385
48359
  "deprecationMessage": "",
48386
48360
  "tagName": {
@@ -48707,10 +48681,10 @@
48707
48681
  ]
48708
48682
  }
48709
48683
  ],
48710
- "packages/core/schematics/migrate-to-signals/index.ts": [
48684
+ "packages/core/schematics/migrate-eui-toolbar-menu/index.ts": [
48711
48685
  {
48712
48686
  "name": "applyEdits",
48713
- "file": "packages/core/schematics/migrate-to-signals/index.ts",
48687
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
48714
48688
  "ctype": "miscellaneous",
48715
48689
  "subtype": "function",
48716
48690
  "coverageIgnore": false,
@@ -48754,19 +48728,20 @@
48754
48728
  ]
48755
48729
  },
48756
48730
  {
48757
- "name": "buildTemplateRefMap",
48758
- "file": "packages/core/schematics/migrate-to-signals/index.ts",
48731
+ "name": "collectOutputRemovals",
48732
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
48759
48733
  "ctype": "miscellaneous",
48760
48734
  "subtype": "function",
48761
48735
  "coverageIgnore": false,
48762
48736
  "deprecated": false,
48763
48737
  "deprecationMessage": "",
48764
- "rawdescription": "Builds a map of template reference variable names (#ref) to EUI class names.\n\nParses the component's template (inline or external) and finds elements like:\n <eui-alert #myAlert> → myAlert → EuiAlertComponent\n <input euiAutocomplete #ac> → ac → EuiAutocompleteComponent\n\nReturns Map<refName, euiClassName>.",
48765
- "description": "<p>Builds a map of template reference variable names (#ref) to EUI class names.</p>\n<p>Parses the component&#39;s template (inline or external) and finds elements like:\n &lt;eui-alert #myAlert&gt; → myAlert → EuiAlertComponent\n &lt;input euiAutocomplete #ac&gt; → ac → EuiAutocompleteComponent</p>\n<p>Returns Map&lt;refName, euiClassName&gt;.</p>\n",
48766
- "displayName": "buildTemplateRefMap",
48738
+ "rawdescription": "",
48739
+ "description": "",
48740
+ "displayName": "collectOutputRemovals",
48767
48741
  "args": [
48768
48742
  {
48769
- "name": "sourceFile",
48743
+ "name": "element",
48744
+ "type": "TmplAstElement",
48770
48745
  "deprecated": false,
48771
48746
  "deprecationMessage": ""
48772
48747
  },
@@ -48776,6 +48751,11 @@
48776
48751
  "deprecated": false,
48777
48752
  "deprecationMessage": ""
48778
48753
  },
48754
+ {
48755
+ "name": "edits",
48756
+ "deprecated": false,
48757
+ "deprecationMessage": ""
48758
+ },
48779
48759
  {
48780
48760
  "name": "filePath",
48781
48761
  "type": "string",
@@ -48783,16 +48763,17 @@
48783
48763
  "deprecationMessage": ""
48784
48764
  },
48785
48765
  {
48786
- "name": "tree",
48787
- "type": "Tree",
48766
+ "name": "context",
48767
+ "type": "SchematicContext",
48788
48768
  "deprecated": false,
48789
48769
  "deprecationMessage": ""
48790
48770
  }
48791
48771
  ],
48792
- "returnType": "Map<string, string>",
48772
+ "returnType": "void",
48793
48773
  "jsdoctags": [
48794
48774
  {
48795
- "name": "sourceFile",
48775
+ "name": "element",
48776
+ "type": "TmplAstElement",
48796
48777
  "deprecated": false,
48797
48778
  "deprecationMessage": "",
48798
48779
  "tagName": {
@@ -48808,6 +48789,14 @@
48808
48789
  "text": "param"
48809
48790
  }
48810
48791
  },
48792
+ {
48793
+ "name": "edits",
48794
+ "deprecated": false,
48795
+ "deprecationMessage": "",
48796
+ "tagName": {
48797
+ "text": "param"
48798
+ }
48799
+ },
48811
48800
  {
48812
48801
  "name": "filePath",
48813
48802
  "type": "string",
@@ -48818,8 +48807,8 @@
48818
48807
  }
48819
48808
  },
48820
48809
  {
48821
- "name": "tree",
48822
- "type": "Tree",
48810
+ "name": "context",
48811
+ "type": "SchematicContext",
48823
48812
  "deprecated": false,
48824
48813
  "deprecationMessage": "",
48825
48814
  "tagName": {
@@ -48829,19 +48818,20 @@
48829
48818
  ]
48830
48819
  },
48831
48820
  {
48832
- "name": "collectEuiReferences",
48833
- "file": "packages/core/schematics/migrate-to-signals/index.ts",
48821
+ "name": "collectTagRenames",
48822
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
48834
48823
  "ctype": "miscellaneous",
48835
48824
  "subtype": "function",
48836
48825
  "coverageIgnore": false,
48837
48826
  "deprecated": false,
48838
48827
  "deprecationMessage": "",
48839
- "rawdescription": "Scans the source file for class properties or variables typed as EUI component classes.\nReturns a map: localName → Set of signal property names.\n\nHandles:",
48840
- "description": "<p>Scans the source file for class properties or variables typed as EUI component classes.\nReturns a map: localName → Set of signal property names.</p>\n<p>Handles:</p>\n",
48841
- "displayName": "collectEuiReferences",
48828
+ "rawdescription": "",
48829
+ "description": "",
48830
+ "displayName": "collectTagRenames",
48842
48831
  "args": [
48843
48832
  {
48844
- "name": "sourceFile",
48833
+ "name": "element",
48834
+ "type": "TmplAstElement",
48845
48835
  "deprecated": false,
48846
48836
  "deprecationMessage": ""
48847
48837
  },
@@ -48852,16 +48842,16 @@
48852
48842
  "deprecationMessage": ""
48853
48843
  },
48854
48844
  {
48855
- "name": "templateRefMap",
48856
- "type": "Map",
48845
+ "name": "edits",
48857
48846
  "deprecated": false,
48858
48847
  "deprecationMessage": ""
48859
48848
  }
48860
48849
  ],
48861
- "returnType": "Map<string, ReadonlySet<string>>",
48850
+ "returnType": "void",
48862
48851
  "jsdoctags": [
48863
48852
  {
48864
- "name": "sourceFile",
48853
+ "name": "element",
48854
+ "type": "TmplAstElement",
48865
48855
  "deprecated": false,
48866
48856
  "deprecationMessage": "",
48867
48857
  "tagName": {
@@ -48878,53 +48868,7 @@
48878
48868
  }
48879
48869
  },
48880
48870
  {
48881
- "name": "templateRefMap",
48882
- "type": "Map",
48883
- "deprecated": false,
48884
- "deprecationMessage": "",
48885
- "tagName": {
48886
- "text": "param"
48887
- }
48888
- }
48889
- ]
48890
- },
48891
- {
48892
- "name": "collectTemplateRefs",
48893
- "file": "packages/core/schematics/migrate-to-signals/index.ts",
48894
- "ctype": "miscellaneous",
48895
- "subtype": "function",
48896
- "coverageIgnore": false,
48897
- "deprecated": false,
48898
- "deprecationMessage": "",
48899
- "rawdescription": "Recursively walks template AST nodes, collecting #refName → EUI class name mappings.\n\nFor each element with a template reference (#ref), we try to resolve its EUI class:\n1. Match element tag name against EUI_SELECTOR_TO_CLASS (e.g. 'eui-alert' → EuiAlertComponent)\n2. Match element attributes against attribute selectors (e.g. [euiAutocomplete] → EuiAutocompleteComponent)",
48900
- "description": "<p>Recursively walks template AST nodes, collecting #refName → EUI class name mappings.</p>\n<p>For each element with a template reference (#ref), we try to resolve its EUI class:</p>\n<ol>\n<li>Match element tag name against EUI_SELECTOR_TO_CLASS (e.g. &#39;eui-alert&#39; → EuiAlertComponent)</li>\n<li>Match element attributes against attribute selectors (e.g. [euiAutocomplete] → EuiAutocompleteComponent)</li>\n</ol>\n",
48901
- "displayName": "collectTemplateRefs",
48902
- "args": [
48903
- {
48904
- "name": "nodes",
48905
- "deprecated": false,
48906
- "deprecationMessage": ""
48907
- },
48908
- {
48909
- "name": "refMap",
48910
- "type": "Map",
48911
- "deprecated": false,
48912
- "deprecationMessage": ""
48913
- }
48914
- ],
48915
- "returnType": "void",
48916
- "jsdoctags": [
48917
- {
48918
- "name": "nodes",
48919
- "deprecated": false,
48920
- "deprecationMessage": "",
48921
- "tagName": {
48922
- "text": "param"
48923
- }
48924
- },
48925
- {
48926
- "name": "refMap",
48927
- "type": "Map",
48871
+ "name": "edits",
48928
48872
  "deprecated": false,
48929
48873
  "deprecationMessage": "",
48930
48874
  "tagName": {
@@ -48935,7 +48879,7 @@
48935
48879
  },
48936
48880
  {
48937
48881
  "name": "deduplicateEdits",
48938
- "file": "packages/core/schematics/migrate-to-signals/index.ts",
48882
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
48939
48883
  "ctype": "miscellaneous",
48940
48884
  "subtype": "function",
48941
48885
  "coverageIgnore": false,
@@ -48964,42 +48908,27 @@
48964
48908
  ]
48965
48909
  },
48966
48910
  {
48967
- "name": "findComponentMetadata",
48968
- "file": "packages/core/schematics/migrate-to-signals/index.ts",
48911
+ "name": "isComponentMetadataProperty",
48912
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
48969
48913
  "ctype": "miscellaneous",
48970
48914
  "subtype": "function",
48971
48915
  "coverageIgnore": false,
48972
48916
  "deprecated": false,
48973
48917
  "deprecationMessage": "",
48974
- "rawdescription": "Extracts",
48975
- "description": "<p>Extracts</p>\n",
48976
- "displayName": "findComponentMetadata",
48918
+ "rawdescription": "",
48919
+ "description": "",
48920
+ "displayName": "isComponentMetadataProperty",
48977
48921
  "args": [
48978
48922
  {
48979
- "name": "sourceFile",
48980
- "deprecated": false,
48981
- "deprecationMessage": ""
48982
- },
48983
- {
48984
- "name": "source",
48985
- "type": "string",
48923
+ "name": "node",
48986
48924
  "deprecated": false,
48987
48925
  "deprecationMessage": ""
48988
48926
  }
48989
48927
  ],
48990
- "returnType": "literal type | null",
48928
+ "returnType": "boolean",
48991
48929
  "jsdoctags": [
48992
48930
  {
48993
- "name": "sourceFile",
48994
- "deprecated": false,
48995
- "deprecationMessage": "",
48996
- "tagName": {
48997
- "text": "param"
48998
- }
48999
- },
49000
- {
49001
- "name": "source",
49002
- "type": "string",
48931
+ "name": "node",
49003
48932
  "deprecated": false,
49004
48933
  "deprecationMessage": "",
49005
48934
  "tagName": {
@@ -49009,8 +48938,8 @@
49009
48938
  ]
49010
48939
  },
49011
48940
  {
49012
- "name": "getDecoratorName",
49013
- "file": "packages/core/schematics/migrate-to-signals/index.ts",
48941
+ "name": "isTemplateProperty",
48942
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
49014
48943
  "ctype": "miscellaneous",
49015
48944
  "subtype": "function",
49016
48945
  "coverageIgnore": false,
@@ -49018,18 +48947,18 @@
49018
48947
  "deprecationMessage": "",
49019
48948
  "rawdescription": "",
49020
48949
  "description": "",
49021
- "displayName": "getDecoratorName",
48950
+ "displayName": "isTemplateProperty",
49022
48951
  "args": [
49023
48952
  {
49024
- "name": "callExpr",
48953
+ "name": "node",
49025
48954
  "deprecated": false,
49026
48955
  "deprecationMessage": ""
49027
48956
  }
49028
48957
  ],
49029
- "returnType": "string | null",
48958
+ "returnType": "boolean",
49030
48959
  "jsdoctags": [
49031
48960
  {
49032
- "name": "callExpr",
48961
+ "name": "node",
49033
48962
  "deprecated": false,
49034
48963
  "deprecationMessage": "",
49035
48964
  "tagName": {
@@ -49039,8 +48968,8 @@
49039
48968
  ]
49040
48969
  },
49041
48970
  {
49042
- "name": "getTypeAnnotationName",
49043
- "file": "packages/core/schematics/migrate-to-signals/index.ts",
48971
+ "name": "migrateEuiToolbarMenu",
48972
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
49044
48973
  "ctype": "miscellaneous",
49045
48974
  "subtype": "function",
49046
48975
  "coverageIgnore": false,
@@ -49048,20 +48977,24 @@
49048
48977
  "deprecationMessage": "",
49049
48978
  "rawdescription": "",
49050
48979
  "description": "",
49051
- "displayName": "getTypeAnnotationName",
48980
+ "displayName": "migrateEuiToolbarMenu",
49052
48981
  "args": [
49053
48982
  {
49054
- "name": "typeNode",
48983
+ "name": "options",
48984
+ "type": "Schema",
49055
48985
  "deprecated": false,
49056
- "deprecationMessage": ""
48986
+ "deprecationMessage": "",
48987
+ "defaultValue": "{}"
49057
48988
  }
49058
48989
  ],
49059
- "returnType": "string | null",
48990
+ "returnType": "Rule",
49060
48991
  "jsdoctags": [
49061
48992
  {
49062
- "name": "typeNode",
48993
+ "name": "options",
48994
+ "type": "Schema",
49063
48995
  "deprecated": false,
49064
48996
  "deprecationMessage": "",
48997
+ "defaultValue": "{}",
49065
48998
  "tagName": {
49066
48999
  "text": "param"
49067
49000
  }
@@ -49069,25 +49002,37 @@
49069
49002
  ]
49070
49003
  },
49071
49004
  {
49072
- "name": "hasEuiReference",
49073
- "file": "packages/core/schematics/migrate-to-signals/index.ts",
49005
+ "name": "migrateImportsAndTypes",
49006
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
49074
49007
  "ctype": "miscellaneous",
49075
49008
  "subtype": "function",
49076
49009
  "coverageIgnore": false,
49077
49010
  "deprecated": false,
49078
49011
  "deprecationMessage": "",
49079
- "rawdescription": "Quick check: does the file reference any EUI class name from the signal map?",
49080
- "description": "<p>Quick check: does the file reference any EUI class name from the signal map?</p>\n",
49081
- "displayName": "hasEuiReference",
49012
+ "rawdescription": "",
49013
+ "description": "",
49014
+ "displayName": "migrateImportsAndTypes",
49082
49015
  "args": [
49083
49016
  {
49084
49017
  "name": "source",
49085
49018
  "type": "string",
49086
49019
  "deprecated": false,
49087
49020
  "deprecationMessage": ""
49021
+ },
49022
+ {
49023
+ "name": "filePath",
49024
+ "type": "string",
49025
+ "deprecated": false,
49026
+ "deprecationMessage": ""
49027
+ },
49028
+ {
49029
+ "name": "context",
49030
+ "type": "SchematicContext",
49031
+ "deprecated": false,
49032
+ "deprecationMessage": ""
49088
49033
  }
49089
49034
  ],
49090
- "returnType": "boolean",
49035
+ "returnType": "string",
49091
49036
  "jsdoctags": [
49092
49037
  {
49093
49038
  "name": "source",
@@ -49097,33 +49042,19 @@
49097
49042
  "tagName": {
49098
49043
  "text": "param"
49099
49044
  }
49100
- }
49101
- ]
49102
- },
49103
- {
49104
- "name": "hasStringRefQuery",
49105
- "file": "packages/core/schematics/migrate-to-signals/index.ts",
49106
- "ctype": "miscellaneous",
49107
- "subtype": "function",
49108
- "coverageIgnore": false,
49109
- "deprecated": false,
49110
- "deprecationMessage": "",
49111
- "rawdescription": "Quick check: does the file have",
49112
- "description": "<p>Quick check: does the file have</p>\n",
49113
- "displayName": "hasStringRefQuery",
49114
- "args": [
49045
+ },
49115
49046
  {
49116
- "name": "source",
49047
+ "name": "filePath",
49117
49048
  "type": "string",
49118
49049
  "deprecated": false,
49119
- "deprecationMessage": ""
49120
- }
49121
- ],
49122
- "returnType": "boolean",
49123
- "jsdoctags": [
49050
+ "deprecationMessage": "",
49051
+ "tagName": {
49052
+ "text": "param"
49053
+ }
49054
+ },
49124
49055
  {
49125
- "name": "source",
49126
- "type": "string",
49056
+ "name": "context",
49057
+ "type": "SchematicContext",
49127
49058
  "deprecated": false,
49128
49059
  "deprecationMessage": "",
49129
49060
  "tagName": {
@@ -49133,8 +49064,8 @@
49133
49064
  ]
49134
49065
  },
49135
49066
  {
49136
- "name": "isAssignmentOperator",
49137
- "file": "packages/core/schematics/migrate-to-signals/index.ts",
49067
+ "name": "migrateInlineTemplates",
49068
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
49138
49069
  "ctype": "miscellaneous",
49139
49070
  "subtype": "function",
49140
49071
  "coverageIgnore": false,
@@ -49142,54 +49073,52 @@
49142
49073
  "deprecationMessage": "",
49143
49074
  "rawdescription": "",
49144
49075
  "description": "",
49145
- "displayName": "isAssignmentOperator",
49076
+ "displayName": "migrateInlineTemplates",
49146
49077
  "args": [
49147
49078
  {
49148
- "name": "kind",
49079
+ "name": "source",
49080
+ "type": "string",
49081
+ "deprecated": false,
49082
+ "deprecationMessage": ""
49083
+ },
49084
+ {
49085
+ "name": "filePath",
49086
+ "type": "string",
49087
+ "deprecated": false,
49088
+ "deprecationMessage": ""
49089
+ },
49090
+ {
49091
+ "name": "context",
49092
+ "type": "SchematicContext",
49149
49093
  "deprecated": false,
49150
49094
  "deprecationMessage": ""
49151
49095
  }
49152
49096
  ],
49153
- "returnType": "boolean",
49097
+ "returnType": "string",
49154
49098
  "jsdoctags": [
49155
49099
  {
49156
- "name": "kind",
49100
+ "name": "source",
49101
+ "type": "string",
49157
49102
  "deprecated": false,
49158
49103
  "deprecationMessage": "",
49159
49104
  "tagName": {
49160
49105
  "text": "param"
49161
49106
  }
49162
- }
49163
- ]
49164
- },
49165
- {
49166
- "name": "migrateToSignals",
49167
- "file": "packages/core/schematics/migrate-to-signals/index.ts",
49168
- "ctype": "miscellaneous",
49169
- "subtype": "function",
49170
- "coverageIgnore": false,
49171
- "deprecated": false,
49172
- "deprecationMessage": "",
49173
- "rawdescription": "Migrates consumer code that accesses EUI component instances programmatically.\n\nWhen EUI migrated",
49174
- "description": "<p>Migrates consumer code that accesses EUI component instances programmatically.</p>\n<p>When EUI migrated</p>\n",
49175
- "displayName": "migrateToSignals",
49176
- "args": [
49107
+ },
49177
49108
  {
49178
- "name": "options",
49179
- "type": "Schema",
49109
+ "name": "filePath",
49110
+ "type": "string",
49180
49111
  "deprecated": false,
49181
49112
  "deprecationMessage": "",
49182
- "defaultValue": "{}"
49183
- }
49184
- ],
49185
- "returnType": "Rule",
49186
- "jsdoctags": [
49113
+ "tagName": {
49114
+ "text": "param"
49115
+ }
49116
+ },
49187
49117
  {
49188
- "name": "options",
49189
- "type": "Schema",
49118
+ "name": "context",
49119
+ "type": "SchematicContext",
49190
49120
  "deprecated": false,
49191
49121
  "deprecationMessage": "",
49192
- "defaultValue": "{}",
49193
49122
  "tagName": {
49194
49123
  "text": "param"
49195
49124
  }
@@ -49197,8 +49126,8 @@
49197
49126
  ]
49198
49127
  },
49199
49128
  {
49200
- "name": "migrateTypeScript",
49201
- "file": "packages/core/schematics/migrate-to-signals/index.ts",
49129
+ "name": "migrateTemplate",
49130
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
49202
49131
  "ctype": "miscellaneous",
49203
49132
  "subtype": "function",
49204
49133
  "coverageIgnore": false,
@@ -49206,7 +49135,7 @@
49206
49135
  "deprecationMessage": "",
49207
49136
  "rawdescription": "",
49208
49137
  "description": "",
49209
- "displayName": "migrateTypeScript",
49138
+ "displayName": "migrateTemplate",
49210
49139
  "args": [
49211
49140
  {
49212
49141
  "name": "source",
@@ -49220,12 +49149,6 @@
49220
49149
  "deprecated": false,
49221
49150
  "deprecationMessage": ""
49222
49151
  },
49223
- {
49224
- "name": "tree",
49225
- "type": "Tree",
49226
- "deprecated": false,
49227
- "deprecationMessage": ""
49228
- },
49229
49152
  {
49230
49153
  "name": "context",
49231
49154
  "type": "SchematicContext",
@@ -49233,7 +49156,7 @@
49233
49156
  "deprecationMessage": ""
49234
49157
  }
49235
49158
  ],
49236
- "returnType": "MigrateResult",
49159
+ "returnType": "string",
49237
49160
  "jsdoctags": [
49238
49161
  {
49239
49162
  "name": "source",
@@ -49253,15 +49176,6 @@
49253
49176
  "text": "param"
49254
49177
  }
49255
49178
  },
49256
- {
49257
- "name": "tree",
49258
- "type": "Tree",
49259
- "deprecated": false,
49260
- "deprecationMessage": "",
49261
- "tagName": {
49262
- "text": "param"
49263
- }
49264
- },
49265
49179
  {
49266
49180
  "name": "context",
49267
49181
  "type": "SchematicContext",
@@ -49274,29 +49188,59 @@
49274
49188
  ]
49275
49189
  },
49276
49190
  {
49277
- "name": "resolveElementToEuiClass",
49278
- "file": "packages/core/schematics/migrate-to-signals/index.ts",
49191
+ "name": "migrateTypeScript",
49192
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
49279
49193
  "ctype": "miscellaneous",
49280
49194
  "subtype": "function",
49281
49195
  "coverageIgnore": false,
49282
49196
  "deprecated": false,
49283
49197
  "deprecationMessage": "",
49284
- "rawdescription": "Given a template element, resolves it to an EUI class name by checking:\n1. Tag name: <eui-alert> → look up 'eui-alert' in EUI_SELECTOR_TO_CLASS\n2. Attribute selectors: <input euiAutocomplete> → look up 'input[euiAutocomplete]' or '[euiAutocomplete]'",
49285
- "description": "<p>Given a template element, resolves it to an EUI class name by checking:</p>\n<ol>\n<li>Tag name: <eui-alert> → look up &#39;eui-alert&#39; in EUI_SELECTOR_TO_CLASS</li>\n<li>Attribute selectors: <input euiAutocomplete> → look up &#39;input[euiAutocomplete]&#39; or &#39;[euiAutocomplete]&#39;</li>\n</ol>\n",
49286
- "displayName": "resolveElementToEuiClass",
49198
+ "rawdescription": "",
49199
+ "description": "",
49200
+ "displayName": "migrateTypeScript",
49287
49201
  "args": [
49288
49202
  {
49289
- "name": "element",
49290
- "type": "TmplAstElement",
49203
+ "name": "source",
49204
+ "type": "string",
49205
+ "deprecated": false,
49206
+ "deprecationMessage": ""
49207
+ },
49208
+ {
49209
+ "name": "filePath",
49210
+ "type": "string",
49211
+ "deprecated": false,
49212
+ "deprecationMessage": ""
49213
+ },
49214
+ {
49215
+ "name": "context",
49216
+ "type": "SchematicContext",
49291
49217
  "deprecated": false,
49292
49218
  "deprecationMessage": ""
49293
49219
  }
49294
49220
  ],
49295
- "returnType": "string | null",
49221
+ "returnType": "string",
49296
49222
  "jsdoctags": [
49297
49223
  {
49298
- "name": "element",
49299
- "type": "TmplAstElement",
49224
+ "name": "source",
49225
+ "type": "string",
49226
+ "deprecated": false,
49227
+ "deprecationMessage": "",
49228
+ "tagName": {
49229
+ "text": "param"
49230
+ }
49231
+ },
49232
+ {
49233
+ "name": "filePath",
49234
+ "type": "string",
49235
+ "deprecated": false,
49236
+ "deprecationMessage": "",
49237
+ "tagName": {
49238
+ "text": "param"
49239
+ }
49240
+ },
49241
+ {
49242
+ "name": "context",
49243
+ "type": "SchematicContext",
49300
49244
  "deprecated": false,
49301
49245
  "deprecationMessage": "",
49302
49246
  "tagName": {
@@ -49306,19 +49250,24 @@
49306
49250
  ]
49307
49251
  },
49308
49252
  {
49309
- "name": "resolveEuiClassName",
49310
- "file": "packages/core/schematics/migrate-to-signals/index.ts",
49253
+ "name": "removeImportSpecifier",
49254
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
49311
49255
  "ctype": "miscellaneous",
49312
49256
  "subtype": "function",
49313
49257
  "coverageIgnore": false,
49314
49258
  "deprecated": false,
49315
49259
  "deprecationMessage": "",
49316
- "rawdescription": "Resolves the EUI class name from a property declaration.\nChecks: type annotation → decorator class argument → template ref map.",
49317
- "description": "<p>Resolves the EUI class name from a property declaration.\nChecks: type annotation → decorator class argument → template ref map.</p>\n",
49318
- "displayName": "resolveEuiClassName",
49260
+ "rawdescription": "",
49261
+ "description": "",
49262
+ "displayName": "removeImportSpecifier",
49319
49263
  "args": [
49320
49264
  {
49321
- "name": "node",
49265
+ "name": "namedImports",
49266
+ "deprecated": false,
49267
+ "deprecationMessage": ""
49268
+ },
49269
+ {
49270
+ "name": "specifier",
49322
49271
  "deprecated": false,
49323
49272
  "deprecationMessage": ""
49324
49273
  },
@@ -49328,16 +49277,23 @@
49328
49277
  "deprecationMessage": ""
49329
49278
  },
49330
49279
  {
49331
- "name": "templateRefMap",
49332
- "type": "Map",
49280
+ "name": "edits",
49333
49281
  "deprecated": false,
49334
49282
  "deprecationMessage": ""
49335
49283
  }
49336
49284
  ],
49337
- "returnType": "string | null",
49285
+ "returnType": "void",
49338
49286
  "jsdoctags": [
49339
49287
  {
49340
- "name": "node",
49288
+ "name": "namedImports",
49289
+ "deprecated": false,
49290
+ "deprecationMessage": "",
49291
+ "tagName": {
49292
+ "text": "param"
49293
+ }
49294
+ },
49295
+ {
49296
+ "name": "specifier",
49341
49297
  "deprecated": false,
49342
49298
  "deprecationMessage": "",
49343
49299
  "tagName": {
@@ -49353,8 +49309,7 @@
49353
49309
  }
49354
49310
  },
49355
49311
  {
49356
- "name": "templateRefMap",
49357
- "type": "Map",
49312
+ "name": "edits",
49358
49313
  "deprecated": false,
49359
49314
  "deprecationMessage": "",
49360
49315
  "tagName": {
@@ -49364,27 +49319,27 @@
49364
49319
  ]
49365
49320
  },
49366
49321
  {
49367
- "name": "resolveRefName",
49368
- "file": "packages/core/schematics/migrate-to-signals/index.ts",
49322
+ "name": "unwrapExpression",
49323
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
49369
49324
  "ctype": "miscellaneous",
49370
49325
  "subtype": "function",
49371
49326
  "coverageIgnore": false,
49372
49327
  "deprecated": false,
49373
49328
  "deprecationMessage": "",
49374
- "rawdescription": "Resolves a reference name from an expression.\nHandles: `ref`, `this.ref`, `this._ref`",
49375
- "description": "<p>Resolves a reference name from an expression.\nHandles: <code>ref</code>, <code>this.ref</code>, <code>this._ref</code></p>\n",
49376
- "displayName": "resolveRefName",
49329
+ "rawdescription": "",
49330
+ "description": "",
49331
+ "displayName": "unwrapExpression",
49377
49332
  "args": [
49378
49333
  {
49379
- "name": "expr",
49334
+ "name": "expression",
49380
49335
  "deprecated": false,
49381
49336
  "deprecationMessage": ""
49382
49337
  }
49383
49338
  ],
49384
- "returnType": "string | null",
49339
+ "returnType": "ts.Expression",
49385
49340
  "jsdoctags": [
49386
49341
  {
49387
- "name": "expr",
49342
+ "name": "expression",
49388
49343
  "deprecated": false,
49389
49344
  "deprecationMessage": "",
49390
49345
  "tagName": {
@@ -49394,30 +49349,64 @@
49394
49349
  ]
49395
49350
  },
49396
49351
  {
49397
- "name": "tryRewritePropertyAccess",
49398
- "file": "packages/core/schematics/migrate-to-signals/index.ts",
49352
+ "name": "visitDir",
49353
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
49399
49354
  "ctype": "miscellaneous",
49400
49355
  "subtype": "function",
49401
49356
  "coverageIgnore": false,
49402
49357
  "deprecated": false,
49403
49358
  "deprecationMessage": "",
49404
- "rawdescription": "Checks if a property access `ref.prop` should be rewritten to `ref.prop()`.\n\nWe rewrite if:\n- `ref` is tracked in euiRefs\n- `prop` is in the signal set for that EUI class\n- The access is NOT already a call expression (not `ref.prop()` or `ref.prop(args)`)\n- The access is NOT a write target (not `ref.prop = value`)\n- The access is NOT a template binding argument (those are unaffected)",
49405
- "description": "<p>Checks if a property access <code>ref.prop</code> should be rewritten to <code>ref.prop()</code>.</p>\n<p>We rewrite if:</p>\n<ul>\n<li><code>ref</code> is tracked in euiRefs</li>\n<li><code>prop</code> is in the signal set for that EUI class</li>\n<li>The access is NOT already a call expression (not <code>ref.prop()</code> or <code>ref.prop(args)</code>)</li>\n<li>The access is NOT a write target (not <code>ref.prop = value</code>)</li>\n<li>The access is NOT a template binding argument (those are unaffected)</li>\n</ul>\n",
49406
- "displayName": "tryRewritePropertyAccess",
49359
+ "rawdescription": "",
49360
+ "description": "",
49361
+ "displayName": "visitDir",
49407
49362
  "args": [
49408
49363
  {
49409
- "name": "node",
49364
+ "name": "dir",
49365
+ "type": "DirEntry",
49410
49366
  "deprecated": false,
49411
49367
  "deprecationMessage": ""
49412
49368
  },
49413
49369
  {
49414
- "name": "euiRefs",
49415
- "type": "Map",
49370
+ "name": "callback",
49416
49371
  "deprecated": false,
49417
49372
  "deprecationMessage": ""
49373
+ }
49374
+ ],
49375
+ "returnType": "void",
49376
+ "jsdoctags": [
49377
+ {
49378
+ "name": "dir",
49379
+ "type": "DirEntry",
49380
+ "deprecated": false,
49381
+ "deprecationMessage": "",
49382
+ "tagName": {
49383
+ "text": "param"
49384
+ }
49418
49385
  },
49419
49386
  {
49420
- "name": "sourceFile",
49387
+ "name": "callback",
49388
+ "deprecated": false,
49389
+ "deprecationMessage": "",
49390
+ "tagName": {
49391
+ "text": "param"
49392
+ }
49393
+ }
49394
+ ]
49395
+ },
49396
+ {
49397
+ "name": "visitNodes",
49398
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
49399
+ "ctype": "miscellaneous",
49400
+ "subtype": "function",
49401
+ "coverageIgnore": false,
49402
+ "deprecated": false,
49403
+ "deprecationMessage": "",
49404
+ "rawdescription": "",
49405
+ "description": "",
49406
+ "displayName": "visitNodes",
49407
+ "args": [
49408
+ {
49409
+ "name": "nodes",
49421
49410
  "deprecated": false,
49422
49411
  "deprecationMessage": ""
49423
49412
  },
@@ -49426,12 +49415,29 @@
49426
49415
  "type": "string",
49427
49416
  "deprecated": false,
49428
49417
  "deprecationMessage": ""
49418
+ },
49419
+ {
49420
+ "name": "edits",
49421
+ "deprecated": false,
49422
+ "deprecationMessage": ""
49423
+ },
49424
+ {
49425
+ "name": "filePath",
49426
+ "type": "string",
49427
+ "deprecated": false,
49428
+ "deprecationMessage": ""
49429
+ },
49430
+ {
49431
+ "name": "context",
49432
+ "type": "SchematicContext",
49433
+ "deprecated": false,
49434
+ "deprecationMessage": ""
49429
49435
  }
49430
49436
  ],
49431
- "returnType": "Edit | null",
49437
+ "returnType": "void",
49432
49438
  "jsdoctags": [
49433
49439
  {
49434
- "name": "node",
49440
+ "name": "nodes",
49435
49441
  "deprecated": false,
49436
49442
  "deprecationMessage": "",
49437
49443
  "tagName": {
@@ -49439,8 +49445,8 @@
49439
49445
  }
49440
49446
  },
49441
49447
  {
49442
- "name": "euiRefs",
49443
- "type": "Map",
49448
+ "name": "source",
49449
+ "type": "string",
49444
49450
  "deprecated": false,
49445
49451
  "deprecationMessage": "",
49446
49452
  "tagName": {
@@ -49448,7 +49454,7 @@
49448
49454
  }
49449
49455
  },
49450
49456
  {
49451
- "name": "sourceFile",
49457
+ "name": "edits",
49452
49458
  "deprecated": false,
49453
49459
  "deprecationMessage": "",
49454
49460
  "tagName": {
@@ -49456,38 +49462,17 @@
49456
49462
  }
49457
49463
  },
49458
49464
  {
49459
- "name": "source",
49465
+ "name": "filePath",
49460
49466
  "type": "string",
49461
49467
  "deprecated": false,
49462
49468
  "deprecationMessage": "",
49463
49469
  "tagName": {
49464
49470
  "text": "param"
49465
49471
  }
49466
- }
49467
- ]
49468
- },
49469
- {
49470
- "name": "unwrapExpression",
49471
- "file": "packages/core/schematics/migrate-to-signals/index.ts",
49472
- "ctype": "miscellaneous",
49473
- "subtype": "function",
49474
- "coverageIgnore": false,
49475
- "deprecated": false,
49476
- "deprecationMessage": "",
49477
- "rawdescription": "",
49478
- "description": "",
49479
- "displayName": "unwrapExpression",
49480
- "args": [
49481
- {
49482
- "name": "expression",
49483
- "deprecated": false,
49484
- "deprecationMessage": ""
49485
- }
49486
- ],
49487
- "returnType": "ts.Expression",
49488
- "jsdoctags": [
49472
+ },
49489
49473
  {
49490
- "name": "expression",
49474
+ "name": "context",
49475
+ "type": "SchematicContext",
49491
49476
  "deprecated": false,
49492
49477
  "deprecationMessage": "",
49493
49478
  "tagName": {
@@ -49497,8 +49482,8 @@
49497
49482
  ]
49498
49483
  },
49499
49484
  {
49500
- "name": "visitDir",
49501
- "file": "packages/core/schematics/migrate-to-signals/index.ts",
49485
+ "name": "warnRemovedProperties",
49486
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
49502
49487
  "ctype": "miscellaneous",
49503
49488
  "subtype": "function",
49504
49489
  "coverageIgnore": false,
@@ -49506,16 +49491,22 @@
49506
49491
  "deprecationMessage": "",
49507
49492
  "rawdescription": "",
49508
49493
  "description": "",
49509
- "displayName": "visitDir",
49494
+ "displayName": "warnRemovedProperties",
49510
49495
  "args": [
49511
49496
  {
49512
- "name": "dir",
49513
- "type": "DirEntry",
49497
+ "name": "sourceFile",
49514
49498
  "deprecated": false,
49515
49499
  "deprecationMessage": ""
49516
49500
  },
49517
49501
  {
49518
- "name": "callback",
49502
+ "name": "filePath",
49503
+ "type": "string",
49504
+ "deprecated": false,
49505
+ "deprecationMessage": ""
49506
+ },
49507
+ {
49508
+ "name": "context",
49509
+ "type": "SchematicContext",
49519
49510
  "deprecated": false,
49520
49511
  "deprecationMessage": ""
49521
49512
  }
@@ -49523,8 +49514,7 @@
49523
49514
  "returnType": "void",
49524
49515
  "jsdoctags": [
49525
49516
  {
49526
- "name": "dir",
49527
- "type": "DirEntry",
49517
+ "name": "sourceFile",
49528
49518
  "deprecated": false,
49529
49519
  "deprecationMessage": "",
49530
49520
  "tagName": {
@@ -49532,7 +49522,17 @@
49532
49522
  }
49533
49523
  },
49534
49524
  {
49535
- "name": "callback",
49525
+ "name": "filePath",
49526
+ "type": "string",
49527
+ "deprecated": false,
49528
+ "deprecationMessage": "",
49529
+ "tagName": {
49530
+ "text": "param"
49531
+ }
49532
+ },
49533
+ {
49534
+ "name": "context",
49535
+ "type": "SchematicContext",
49536
49536
  "deprecated": false,
49537
49537
  "deprecationMessage": "",
49538
49538
  "tagName": {