@eui/core 22.0.0-alpha.19 → 22.0.0-alpha.20

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.
@@ -2195,12 +2195,12 @@
2195
2195
  },
2196
2196
  {
2197
2197
  "name": "Schema",
2198
- "id": "interface-Schema-5cd6db1920bd5b70a44c0b8a7f7e30f600bfd16a9950f218e6d451a1755ced95b462ef9a62ee87e39bcb8c392981b8d2597895bf0a272fd8aea71f03429ed976-1",
2199
- "file": "packages/core/schematics/icon-migrate/schema.ts",
2198
+ "id": "interface-Schema-4fe31ff3e9f1d34845a6b865d605e215f33552094b88c3d0eab0b180187fe64ce4d68d687516cb3d62c57d2678a103969b2dacbb18a49b26060f78096678fcce-1",
2199
+ "file": "packages/core/schematics/fix-no-multiple-empty-lines/index.ts",
2200
2200
  "deprecated": false,
2201
2201
  "deprecationMessage": "",
2202
2202
  "type": "interface",
2203
- "sourceCode": "export interface Schema {\n /** The path to scan for files to migrate */\n path?: string;\n /** Whether to perform a dry run without making changes */\n dryRun?: boolean;\n}\n",
2203
+ "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\nconst MULTIPLE_EMPTY_LINES = /\\n{3,}/g;\n\nexport function fixNoMultipleEmptyLines(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, (filePath) => {\n const buffer = tree.read(filePath);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n const result = original.replace(MULTIPLE_EMPTY_LINES, '\\n\\n');\n\n if (result !== original) {\n if (filePath.endsWith('.ts')) {\n const sourceFile = ts.createSourceFile(filePath, result, ts.ScriptTarget.Latest, true);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n if ((sourceFile as any).parseDiagnostics?.length) {\n context.logger.warn(`Skipping ${filePath}: file would not parse after transformation.`);\n return;\n }\n }\n if (options.dryRun) {\n logDryRun(context, `Would collapse multiple empty lines in ${filePath}`);\n } else {\n tree.overwrite(filePath, result);\n }\n count++;\n }\n });\n\n context.logger.info(`Fixed multiple empty lines 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('.ts') && !file.endsWith('.html') && !file.endsWith('.scss') && !file.endsWith('.css')) 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",
2204
2204
  "displayName": "Schema",
2205
2205
  "properties": [
2206
2206
  {
@@ -2211,9 +2211,9 @@
2211
2211
  "type": "boolean",
2212
2212
  "indexKey": "",
2213
2213
  "optional": true,
2214
- "description": "<p>Whether to perform a dry run without making changes</p>\n",
2215
- "line": 5,
2216
- "rawdescription": "\nWhether to perform a dry run without making changes"
2214
+ "description": "",
2215
+ "line": 7,
2216
+ "rawdescription": "\n"
2217
2217
  },
2218
2218
  {
2219
2219
  "name": "path",
@@ -2223,9 +2223,9 @@
2223
2223
  "type": "string",
2224
2224
  "indexKey": "",
2225
2225
  "optional": true,
2226
- "description": "<p>The path to scan for files to migrate</p>\n",
2227
- "line": 3,
2228
- "rawdescription": "\nThe path to scan for files to migrate"
2226
+ "description": "",
2227
+ "line": 6,
2228
+ "rawdescription": "\n"
2229
2229
  }
2230
2230
  ],
2231
2231
  "indexSignatures": [],
@@ -2242,12 +2242,12 @@
2242
2242
  },
2243
2243
  {
2244
2244
  "name": "Schema",
2245
- "id": "interface-Schema-4fe31ff3e9f1d34845a6b865d605e215f33552094b88c3d0eab0b180187fe64ce4d68d687516cb3d62c57d2678a103969b2dacbb18a49b26060f78096678fcce-2",
2246
- "file": "packages/core/schematics/fix-no-multiple-empty-lines/index.ts",
2245
+ "id": "interface-Schema-5cd6db1920bd5b70a44c0b8a7f7e30f600bfd16a9950f218e6d451a1755ced95b462ef9a62ee87e39bcb8c392981b8d2597895bf0a272fd8aea71f03429ed976-2",
2246
+ "file": "packages/core/schematics/icon-migrate/schema.ts",
2247
2247
  "deprecated": false,
2248
2248
  "deprecationMessage": "",
2249
2249
  "type": "interface",
2250
- "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\nconst MULTIPLE_EMPTY_LINES = /\\n{3,}/g;\n\nexport function fixNoMultipleEmptyLines(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, (filePath) => {\n const buffer = tree.read(filePath);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n const result = original.replace(MULTIPLE_EMPTY_LINES, '\\n\\n');\n\n if (result !== original) {\n if (filePath.endsWith('.ts')) {\n const sourceFile = ts.createSourceFile(filePath, result, ts.ScriptTarget.Latest, true);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n if ((sourceFile as any).parseDiagnostics?.length) {\n context.logger.warn(`Skipping ${filePath}: file would not parse after transformation.`);\n return;\n }\n }\n if (options.dryRun) {\n logDryRun(context, `Would collapse multiple empty lines in ${filePath}`);\n } else {\n tree.overwrite(filePath, result);\n }\n count++;\n }\n });\n\n context.logger.info(`Fixed multiple empty lines 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('.ts') && !file.endsWith('.html') && !file.endsWith('.scss') && !file.endsWith('.css')) 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",
2250
+ "sourceCode": "export interface Schema {\n /** The path to scan for files to migrate */\n path?: string;\n /** Whether to perform a dry run without making changes */\n dryRun?: boolean;\n}\n",
2251
2251
  "displayName": "Schema",
2252
2252
  "properties": [
2253
2253
  {
@@ -2258,9 +2258,9 @@
2258
2258
  "type": "boolean",
2259
2259
  "indexKey": "",
2260
2260
  "optional": true,
2261
- "description": "",
2262
- "line": 7,
2263
- "rawdescription": "\n"
2261
+ "description": "<p>Whether to perform a dry run without making changes</p>\n",
2262
+ "line": 5,
2263
+ "rawdescription": "\nWhether to perform a dry run without making changes"
2264
2264
  },
2265
2265
  {
2266
2266
  "name": "path",
@@ -2270,9 +2270,9 @@
2270
2270
  "type": "string",
2271
2271
  "indexKey": "",
2272
2272
  "optional": true,
2273
- "description": "",
2274
- "line": 6,
2275
- "rawdescription": "\n"
2273
+ "description": "<p>The path to scan for files to migrate</p>\n",
2274
+ "line": 3,
2275
+ "rawdescription": "\nThe path to scan for files to migrate"
2276
2276
  }
2277
2277
  ],
2278
2278
  "indexSignatures": [],
@@ -2348,12 +2348,12 @@
2348
2348
  },
2349
2349
  {
2350
2350
  "name": "Schema",
2351
- "id": "interface-Schema-585b211d989563bbec5bd67bcdb58d5f264396adbe0ed2e51d0b4a0ecaf8b3ec36d821647c170d616bb409fc603c5a4c0e699eb7511e02c6add8d8670fc9cb9f-4",
2352
- "file": "packages/core/schematics/migrate-eui-alert/index.ts",
2351
+ "id": "interface-Schema-e96d29c1785a16147de773cebe7ab5d4ed82aa23c6b1f7737113252a14d156e236f46f7a16340432dec900b664c8005be0aef5d0a628c2f5b8dd2ec7e41edf56-4",
2352
+ "file": "packages/core/schematics/migrate-eui-accent/index.ts",
2353
2353
  "deprecated": false,
2354
2354
  "deprecationMessage": "",
2355
2355
  "type": "interface",
2356
- "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(['alertIconType', 'alertIconFillColor', 'isMuted', 'isBordered']);\n\nexport function migrateEuiAlert(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-alert') && !original.includes('euiAlert')) 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 deprecated inputs 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 (![...REMOVED_INPUTS].some((input) => original.includes(input))) 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) && REMOVED_INPUTS.has(node.name.text)) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${path}:${line + 1} - Manual action needed: \"${node.name.text}\" is no longer a valid input on eui-alert. Remove this assignment.`,\n );\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n });\n\n context.logger.info(`Removed deprecated eui-alert inputs 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 // Apply removals in reverse order\n let result = source;\n for (const { start, end } of removals.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, start) + result.slice(end);\n }\n\n return result;\n}\n\nfunction migrateInlineTemplates(source: string): string {\n // Simple approach: find template strings containing eui-alert and process them\n const templateRegex = /template\\s*:\\s*`([^`]*)`/gs;\n return source.replace(templateRegex, (match, templateContent: string) => {\n if (!templateContent.includes('eui-alert') && !templateContent.includes('euiAlert')) 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-alert' || hasAttribute(node, 'euiAlert')) {\n collectRemovals(node, removals);\n }\n visitNodes(node.children, removals);\n }\n }\n}\n\nfunction hasAttribute(element: TmplAstElement, name: string): boolean {\n return element.attributes.some((a) => a.name === name);\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",
2356
+ "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 = 'euiAccent';\nconst NEW_NAME = 'euiPrimary';\nconst EUI_DIRECTIVES = ['euiButton', 'euiList', 'euiListItem'];\n\nexport function migrateEuiAccent(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 result = renameTsPropertyAccesses(result);\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would replace '${OLD_NAME}' → '${NEW_NAME}' in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n });\n\n context.logger.info(`Renamed '${OLD_NAME}' → '${NEW_NAME}' on EUI components 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 isEuiElement(element: TmplAstElement): boolean {\n if (element.name.startsWith('eui-')) return true;\n return element.attributes.some((a) => EUI_DIRECTIVES.includes(a.name)) ||\n element.inputs.some((i) => EUI_DIRECTIVES.includes(i.name));\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 renameTsPropertyAccesses(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: { start: number; end: number; replacement: string }[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === OLD_NAME) {\n edits.push({ start: node.name.getStart(sourceFile), end: node.name.getEnd(), replacement: NEW_NAME });\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n return applyEdits(source, edits);\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 (isEuiElement(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",
2357
2357
  "displayName": "Schema",
2358
2358
  "properties": [
2359
2359
  {
@@ -2395,12 +2395,12 @@
2395
2395
  },
2396
2396
  {
2397
2397
  "name": "Schema",
2398
- "id": "interface-Schema-e96d29c1785a16147de773cebe7ab5d4ed82aa23c6b1f7737113252a14d156e236f46f7a16340432dec900b664c8005be0aef5d0a628c2f5b8dd2ec7e41edf56-5",
2399
- "file": "packages/core/schematics/migrate-eui-accent/index.ts",
2398
+ "id": "interface-Schema-585b211d989563bbec5bd67bcdb58d5f264396adbe0ed2e51d0b4a0ecaf8b3ec36d821647c170d616bb409fc603c5a4c0e699eb7511e02c6add8d8670fc9cb9f-5",
2399
+ "file": "packages/core/schematics/migrate-eui-alert/index.ts",
2400
2400
  "deprecated": false,
2401
2401
  "deprecationMessage": "",
2402
2402
  "type": "interface",
2403
- "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 = 'euiAccent';\nconst NEW_NAME = 'euiPrimary';\nconst EUI_DIRECTIVES = ['euiButton', 'euiList', 'euiListItem'];\n\nexport function migrateEuiAccent(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 result = renameTsPropertyAccesses(result);\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would replace '${OLD_NAME}' → '${NEW_NAME}' in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n });\n\n context.logger.info(`Renamed '${OLD_NAME}' → '${NEW_NAME}' on EUI components 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 isEuiElement(element: TmplAstElement): boolean {\n if (element.name.startsWith('eui-')) return true;\n return element.attributes.some((a) => EUI_DIRECTIVES.includes(a.name)) ||\n element.inputs.some((i) => EUI_DIRECTIVES.includes(i.name));\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 renameTsPropertyAccesses(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: { start: number; end: number; replacement: string }[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === OLD_NAME) {\n edits.push({ start: node.name.getStart(sourceFile), end: node.name.getEnd(), replacement: NEW_NAME });\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n return applyEdits(source, edits);\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 (isEuiElement(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",
2403
+ "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(['alertIconType', 'alertIconFillColor', 'isMuted', 'isBordered']);\n\nexport function migrateEuiAlert(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-alert') && !original.includes('euiAlert')) 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 deprecated inputs 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 (![...REMOVED_INPUTS].some((input) => original.includes(input))) 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) && REMOVED_INPUTS.has(node.name.text)) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${path}:${line + 1} - Manual action needed: \"${node.name.text}\" is no longer a valid input on eui-alert. Remove this assignment.`,\n );\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n });\n\n context.logger.info(`Removed deprecated eui-alert inputs 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 // Apply removals in reverse order\n let result = source;\n for (const { start, end } of removals.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, start) + result.slice(end);\n }\n\n return result;\n}\n\nfunction migrateInlineTemplates(source: string): string {\n // Simple approach: find template strings containing eui-alert and process them\n const templateRegex = /template\\s*:\\s*`([^`]*)`/gs;\n return source.replace(templateRegex, (match, templateContent: string) => {\n if (!templateContent.includes('eui-alert') && !templateContent.includes('euiAlert')) 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-alert' || hasAttribute(node, 'euiAlert')) {\n collectRemovals(node, removals);\n }\n visitNodes(node.children, removals);\n }\n }\n}\n\nfunction hasAttribute(element: TmplAstElement, name: string): boolean {\n return element.attributes.some((a) => a.name === name);\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",
2404
2404
  "displayName": "Schema",
2405
2405
  "properties": [
2406
2406
  {
@@ -2536,12 +2536,12 @@
2536
2536
  },
2537
2537
  {
2538
2538
  "name": "Schema",
2539
- "id": "interface-Schema-311bef7d5e4b40b356b0fdb5e35dd89e48a5c5beec29eeb865c22c2ca1c4c4fb1fd0bb4391ed0b33825a6d1d744bc3a2e9822b347fbc686a537e15b170c279dd-8",
2540
- "file": "packages/core/schematics/migrate-eui-chip-list/index.ts",
2539
+ "id": "interface-Schema-39d3d52e788050bebc4e0b99ed0756f7e9727556cb090ae8796bd4261a56463fe1f479c322b23028cd4134d71e4d1dc44cd248ac62a9a148b5fc46a665466f7c-8",
2540
+ "file": "packages/core/schematics/migrate-eui-chip/index.ts",
2541
2541
  "deprecated": false,
2542
2542
  "deprecationMessage": "",
2543
2543
  "type": "interface",
2544
- "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",
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
2545
  "displayName": "Schema",
2546
2546
  "properties": [
2547
2547
  {
@@ -2553,7 +2553,7 @@
2553
2553
  "indexKey": "",
2554
2554
  "optional": true,
2555
2555
  "description": "",
2556
- "line": 28,
2556
+ "line": 8,
2557
2557
  "rawdescription": "\n"
2558
2558
  },
2559
2559
  {
@@ -2565,7 +2565,7 @@
2565
2565
  "indexKey": "",
2566
2566
  "optional": true,
2567
2567
  "description": "",
2568
- "line": 27,
2568
+ "line": 7,
2569
2569
  "rawdescription": "\n"
2570
2570
  }
2571
2571
  ],
@@ -2583,12 +2583,12 @@
2583
2583
  },
2584
2584
  {
2585
2585
  "name": "Schema",
2586
- "id": "interface-Schema-39d3d52e788050bebc4e0b99ed0756f7e9727556cb090ae8796bd4261a56463fe1f479c322b23028cd4134d71e4d1dc44cd248ac62a9a148b5fc46a665466f7c-9",
2587
- "file": "packages/core/schematics/migrate-eui-chip/index.ts",
2586
+ "id": "interface-Schema-311bef7d5e4b40b356b0fdb5e35dd89e48a5c5beec29eeb865c22c2ca1c4c4fb1fd0bb4391ed0b33825a6d1d744bc3a2e9822b347fbc686a537e15b170c279dd-9",
2587
+ "file": "packages/core/schematics/migrate-eui-chip-list/index.ts",
2588
2588
  "deprecated": false,
2589
2589
  "deprecationMessage": "",
2590
2590
  "type": "interface",
2591
- "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",
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
2592
  "displayName": "Schema",
2593
2593
  "properties": [
2594
2594
  {
@@ -2600,7 +2600,7 @@
2600
2600
  "indexKey": "",
2601
2601
  "optional": true,
2602
2602
  "description": "",
2603
- "line": 8,
2603
+ "line": 28,
2604
2604
  "rawdescription": "\n"
2605
2605
  },
2606
2606
  {
@@ -2612,7 +2612,7 @@
2612
2612
  "indexKey": "",
2613
2613
  "optional": true,
2614
2614
  "description": "",
2615
- "line": 7,
2615
+ "line": 27,
2616
2616
  "rawdescription": "\n"
2617
2617
  }
2618
2618
  ],
@@ -2865,12 +2865,12 @@
2865
2865
  },
2866
2866
  {
2867
2867
  "name": "Schema",
2868
- "id": "interface-Schema-90e62c16ce9ada881e8d434336bb633dae9745236106ddf60964afbd11de6aa579255a13d6036b384281860eb6ed2ee329194b519c51fdaf20f79bc511d0f64f-15",
2869
- "file": "packages/core/schematics/migrate-eui-progress-circle/index.ts",
2868
+ "id": "interface-Schema-a806c1769fb526271565003a6a3ef9ab4c67e421d93ee0cb54e1f6de223807afc1f16ad294656ef614a77a91a38cc914ce41ac97ed3a8a4195a1e74a729c0c08-15",
2869
+ "file": "packages/core/schematics/migrate-eui-popover/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 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",
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",
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-91c9f900bf3ebb82488fb65644938ef486ceaa447cff8bfd9d710df9595d82acb267b26f3c252173bc684353f362473120226426ed7ee5d98b33e30011dd9383-16",
2916
- "file": "packages/core/schematics/migrate-eui-table/index.ts",
2915
+ "id": "interface-Schema-90e62c16ce9ada881e8d434336bb633dae9745236106ddf60964afbd11de6aa579255a13d6036b384281860eb6ed2ee329194b519c51fdaf20f79bc511d0f64f-16",
2916
+ "file": "packages/core/schematics/migrate-eui-progress-circle/index.ts",
2917
2917
  "deprecated": false,
2918
2918
  "deprecationMessage": "",
2919
2919
  "type": "interface",
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",
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",
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": 49,
2932
+ "line": 8,
2933
2933
  "rawdescription": "\n"
2934
2934
  },
2935
2935
  {
@@ -2941,7 +2941,7 @@
2941
2941
  "indexKey": "",
2942
2942
  "optional": true,
2943
2943
  "description": "",
2944
- "line": 48,
2944
+ "line": 7,
2945
2945
  "rawdescription": "\n"
2946
2946
  }
2947
2947
  ],
@@ -2959,12 +2959,12 @@
2959
2959
  },
2960
2960
  {
2961
2961
  "name": "Schema",
2962
- "id": "interface-Schema-a806c1769fb526271565003a6a3ef9ab4c67e421d93ee0cb54e1f6de223807afc1f16ad294656ef614a77a91a38cc914ce41ac97ed3a8a4195a1e74a729c0c08-17",
2963
- "file": "packages/core/schematics/migrate-eui-popover/index.ts",
2962
+ "id": "interface-Schema-91c9f900bf3ebb82488fb65644938ef486ceaa447cff8bfd9d710df9595d82acb267b26f3c252173bc684353f362473120226426ed7ee5d98b33e30011dd9383-17",
2963
+ "file": "packages/core/schematics/migrate-eui-table/index.ts",
2964
2964
  "deprecated": false,
2965
2965
  "deprecationMessage": "",
2966
2966
  "type": "interface",
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",
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",
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": 8,
2979
+ "line": 49,
2980
2980
  "rawdescription": "\n"
2981
2981
  },
2982
2982
  {
@@ -2988,7 +2988,7 @@
2988
2988
  "indexKey": "",
2989
2989
  "optional": true,
2990
2990
  "description": "",
2991
- "line": 7,
2991
+ "line": 48,
2992
2992
  "rawdescription": "\n"
2993
2993
  }
2994
2994
  ],
@@ -3006,12 +3006,12 @@
3006
3006
  },
3007
3007
  {
3008
3008
  "name": "Schema",
3009
- "id": "interface-Schema-e1cd02924eb82a618c71a0b26c081bdd020519d2699aa0e2dc98640c4e0f347c3649b967c5fc87543e41bbbfabd1304835850ccc38f40684d6521c242b2030ed-18",
3010
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
3009
+ "id": "interface-Schema-760dec88d3f709d5b38226e55f7cbfb1e5fabcea6c004fd46e611be1dd563b8b247e9e6c77667e5582fe7b443374ba2c4facd2b2db2985edb46c9a4a37a8a876-18",
3010
+ "file": "packages/core/schematics/migrate-eui-tabs/index.ts",
3011
3011
  "deprecated": false,
3012
3012
  "deprecationMessage": "",
3013
3013
  "type": "interface",
3014
- "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",
3014
+ "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 TextChange {\n start: number;\n end: number;\n text: string;\n}\n\ninterface MigrationResult {\n content: string;\n migrated: number;\n skipped: number;\n}\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst OLD_LABEL = 'eui-tab-label';\nconst OLD_SUB_LABEL = 'euiTabSubLabel';\nconst OLD_CONTENT = 'eui-tab-content';\nconst NEW_HEADER = 'eui-tab-header';\nconst NEW_HEADER_LABEL = 'eui-tab-header-label';\nconst NEW_HEADER_SUB_LABEL = 'eui-tab-header-sub-label';\nconst NEW_BODY = 'eui-tab-body';\n\nexport function migrateEuiTabs(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let migrated = 0;\n let skipped = 0;\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n const buffer = tree.read(path);\n if (!buffer) {\n return;\n }\n\n const original = buffer.toString('utf-8');\n const result = path.endsWith('.html')\n ? migrateTemplate(original, path, context)\n : migrateInlineTemplates(original, path, context);\n\n migrated += result.migrated;\n skipped += result.skipped;\n\n if (result.content !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would migrate ${result.migrated} tab block(s) in ${path}`);\n } else {\n tree.overwrite(path, result.content);\n }\n }\n });\n\n context.logger.info(`Migrated ${migrated} EUI tab template block(s).`);\n if (skipped > 0) {\n context.logger.warn(`Skipped ${skipped} malformed EUI tab template block(s).`);\n }\n if (options.dryRun) {\n logDryRunNote(context);\n }\n\n return tree;\n };\n}\n\nfunction migrateInlineTemplates(source: string, filePath: string, context: SchematicContext): MigrationResult {\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: TextChange[] = [];\n let migrated = 0;\n let skipped = 0;\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const initializer = unwrapExpression(node.initializer);\n\n if (ts.isStringLiteral(initializer) || ts.isNoSubstitutionTemplateLiteral(initializer)) {\n const start = initializer.getStart(sourceFile) + 1;\n const end = initializer.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n const result = migrateTemplate(rawTemplate, `${filePath}@inline-template`, context);\n\n migrated += result.migrated;\n skipped += result.skipped;\n\n if (result.content !== rawTemplate) {\n changes.push({ start, end, text: result.content });\n }\n } else if (ts.isTemplateExpression(initializer)) {\n context.logger.warn(`Skipping interpolated inline template in ${filePath}.`);\n skipped++;\n }\n }\n\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n return {\n content: applyChanges(source, changes),\n migrated,\n skipped,\n };\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (\n (ts.isIdentifier(name) && name.text === 'template') ||\n (ts.isStringLiteral(name) && name.text === 'template')\n );\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n\n while (ts.isParenthesizedExpression(current)) {\n current = current.expression;\n }\n\n return current;\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) {\n return false;\n }\n\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) {\n return false;\n }\n\n return (\n ts.isDecorator(callExpression.parent) &&\n ts.isIdentifier(callExpression.expression) &&\n callExpression.expression.text === 'Component'\n );\n}\n\nfunction migrateTemplate(source: string, filePath: string, context: SchematicContext): MigrationResult {\n const parsed = parseTemplate(source, filePath, { preserveWhitespaces: true });\n const changes: TextChange[] = [];\n let migrated = 0;\n let skipped = 0;\n\n for (const error of parsed.errors ?? []) {\n context.logger.warn(`Template parse warning in ${filePath}: ${error.msg}`);\n }\n\n for (const tab of findElements(parsed.nodes, 'eui-tab')) {\n const label = findDirectChild(tab, OLD_LABEL);\n const content = findDirectChild(tab, OLD_CONTENT);\n const hasOldMarkup = Boolean(label || content);\n const hasNewMarkup = Boolean(findDirectChild(tab, NEW_HEADER) || findDirectChild(tab, NEW_BODY));\n\n if (!hasOldMarkup || hasNewMarkup) {\n continue;\n }\n\n if (!label || !content) {\n context.logger.warn(`Skipping malformed <eui-tab> in ${filePath}.`);\n skipped++;\n continue;\n }\n\n changes.push({\n start: label.sourceSpan.start.offset,\n end: label.sourceSpan.end.offset,\n text: buildHeaderReplacement(source, label),\n });\n changes.push({\n start: content.sourceSpan.start.offset,\n end: content.sourceSpan.end.offset,\n text: buildBodyReplacement(source, content),\n });\n migrated++;\n }\n\n return {\n content: applyChanges(source, withoutOverlaps(changes)),\n migrated,\n skipped,\n };\n}\n\nfunction findElements(nodes: readonly TmplAstNode[], name: string): TmplAstElement[] {\n const matches: TmplAstElement[] = [];\n\n for (const node of nodes) {\n if (isElement(node)) {\n if (node.name === name) {\n matches.push(node);\n }\n matches.push(...findElements(node.children, name));\n }\n }\n\n return matches;\n}\n\nfunction findDirectChild(element: TmplAstElement, name: string): TmplAstElement | undefined {\n return element.children.find((child): child is TmplAstElement => isElement(child) && child.name === name);\n}\n\nfunction isElement(node: TmplAstNode): node is TmplAstElement {\n return node instanceof TmplAstElement;\n}\n\nfunction buildHeaderReplacement(source: string, label: TmplAstElement): string {\n const indent = getIndent(source, label.sourceSpan.start.offset);\n const labelAttributes = getAttributeText(source, label, OLD_LABEL);\n const subLabels = findElements(label.children, OLD_SUB_LABEL);\n const mainLabelContent = removeElementRanges(getInnerText(source, label), label, subLabels);\n const lines = [\n `<${NEW_HEADER}>`,\n `${indent} <${NEW_HEADER_LABEL}${labelAttributes}>`,\n ...formatInnerLines(mainLabelContent, `${indent} `),\n `${indent} </${NEW_HEADER_LABEL}>`,\n ];\n\n for (const subLabel of subLabels) {\n const subLabelAttributes = getAttributeText(source, subLabel, OLD_SUB_LABEL);\n lines.push(\n `${indent} <${NEW_HEADER_SUB_LABEL}${subLabelAttributes}>`,\n ...formatInnerLines(getInnerText(source, subLabel), `${indent} `),\n `${indent} </${NEW_HEADER_SUB_LABEL}>`,\n );\n }\n\n lines.push(`${indent}</${NEW_HEADER}>`);\n\n return lines.join('\\n');\n}\n\nfunction buildBodyReplacement(source: string, content: TmplAstElement): string {\n const indent = getIndent(source, content.sourceSpan.start.offset);\n const attributes = getAttributeText(source, content, OLD_CONTENT);\n const innerText = getInnerText(source, content);\n const trimmed = innerText.trim();\n\n if (trimmed && !trimmed.includes('\\n')) {\n return `<${NEW_BODY}${attributes}>${trimmed}</${NEW_BODY}>`;\n }\n\n return [\n `<${NEW_BODY}${attributes}>`,\n ...formatInnerLines(innerText, `${indent} `),\n `${indent}</${NEW_BODY}>`,\n ].join('\\n');\n}\n\nfunction getInnerText(source: string, element: TmplAstElement): string {\n if (!element.endSourceSpan) {\n return '';\n }\n\n return source.slice(element.startSourceSpan.end.offset, element.endSourceSpan.start.offset);\n}\n\nfunction removeElementRanges(source: string, parent: TmplAstElement, elements: readonly TmplAstElement[]): string {\n const parentContentStart = parent.startSourceSpan.end.offset;\n let result = source;\n\n for (const element of [...elements].sort((a, b) => b.sourceSpan.start.offset - a.sourceSpan.start.offset)) {\n const start = element.sourceSpan.start.offset - parentContentStart;\n const end = element.sourceSpan.end.offset - parentContentStart;\n result = result.slice(0, start) + result.slice(end);\n }\n\n return result;\n}\n\nfunction getAttributeText(source: string, element: TmplAstElement, tagName: string): string {\n const startTag = source.slice(element.startSourceSpan.start.offset, element.startSourceSpan.end.offset);\n const tagStart = startTag.indexOf(tagName);\n\n if (tagStart === -1) {\n return '';\n }\n\n const contentEnd = startTag.endsWith('/>') ? startTag.length - 2 : startTag.length - 1;\n return startTag.slice(tagStart + tagName.length, contentEnd).trimEnd();\n}\n\nfunction getIndent(source: string, offset: number): string {\n const lineStart = source.lastIndexOf('\\n', offset - 1) + 1;\n const prefix = source.slice(lineStart, offset);\n return prefix.match(/^\\s*/)?.[0] ?? '';\n}\n\nfunction formatInnerLines(source: string, indent: string): string[] {\n const trimmed = source.trim();\n\n if (!trimmed) {\n return [];\n }\n\n return trimmed.split(/\\r?\\n/).map((line) => `${indent}${line.trim()}`);\n}\n\nfunction withoutOverlaps(changes: TextChange[]): TextChange[] {\n const accepted: TextChange[] = [];\n\n for (const change of [...changes].sort((a, b) => a.start - b.start || b.end - a.end)) {\n if (!accepted.some((current) => change.start < current.end && current.start < change.end)) {\n accepted.push(change);\n }\n }\n\n return accepted;\n}\n\nfunction applyChanges(source: string, changes: readonly TextChange[]): string {\n let result = source;\n\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\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",
3015
3015
  "displayName": "Schema",
3016
3016
  "properties": [
3017
3017
  {
@@ -3023,7 +3023,7 @@
3023
3023
  "indexKey": "",
3024
3024
  "optional": true,
3025
3025
  "description": "",
3026
- "line": 18,
3026
+ "line": 20,
3027
3027
  "rawdescription": "\n"
3028
3028
  },
3029
3029
  {
@@ -3035,7 +3035,7 @@
3035
3035
  "indexKey": "",
3036
3036
  "optional": true,
3037
3037
  "description": "",
3038
- "line": 17,
3038
+ "line": 19,
3039
3039
  "rawdescription": "\n"
3040
3040
  }
3041
3041
  ],
@@ -3053,12 +3053,12 @@
3053
3053
  },
3054
3054
  {
3055
3055
  "name": "Schema",
3056
- "id": "interface-Schema-760dec88d3f709d5b38226e55f7cbfb1e5fabcea6c004fd46e611be1dd563b8b247e9e6c77667e5582fe7b443374ba2c4facd2b2db2985edb46c9a4a37a8a876-19",
3057
- "file": "packages/core/schematics/migrate-eui-tabs/index.ts",
3056
+ "id": "interface-Schema-e1cd02924eb82a618c71a0b26c081bdd020519d2699aa0e2dc98640c4e0f347c3649b967c5fc87543e41bbbfabd1304835850ccc38f40684d6521c242b2030ed-19",
3057
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/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\ninterface TextChange {\n start: number;\n end: number;\n text: string;\n}\n\ninterface MigrationResult {\n content: string;\n migrated: number;\n skipped: number;\n}\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst OLD_LABEL = 'eui-tab-label';\nconst OLD_SUB_LABEL = 'euiTabSubLabel';\nconst OLD_CONTENT = 'eui-tab-content';\nconst NEW_HEADER = 'eui-tab-header';\nconst NEW_HEADER_LABEL = 'eui-tab-header-label';\nconst NEW_HEADER_SUB_LABEL = 'eui-tab-header-sub-label';\nconst NEW_BODY = 'eui-tab-body';\n\nexport function migrateEuiTabs(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let migrated = 0;\n let skipped = 0;\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n const buffer = tree.read(path);\n if (!buffer) {\n return;\n }\n\n const original = buffer.toString('utf-8');\n const result = path.endsWith('.html')\n ? migrateTemplate(original, path, context)\n : migrateInlineTemplates(original, path, context);\n\n migrated += result.migrated;\n skipped += result.skipped;\n\n if (result.content !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would migrate ${result.migrated} tab block(s) in ${path}`);\n } else {\n tree.overwrite(path, result.content);\n }\n }\n });\n\n context.logger.info(`Migrated ${migrated} EUI tab template block(s).`);\n if (skipped > 0) {\n context.logger.warn(`Skipped ${skipped} malformed EUI tab template block(s).`);\n }\n if (options.dryRun) {\n logDryRunNote(context);\n }\n\n return tree;\n };\n}\n\nfunction migrateInlineTemplates(source: string, filePath: string, context: SchematicContext): MigrationResult {\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: TextChange[] = [];\n let migrated = 0;\n let skipped = 0;\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const initializer = unwrapExpression(node.initializer);\n\n if (ts.isStringLiteral(initializer) || ts.isNoSubstitutionTemplateLiteral(initializer)) {\n const start = initializer.getStart(sourceFile) + 1;\n const end = initializer.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n const result = migrateTemplate(rawTemplate, `${filePath}@inline-template`, context);\n\n migrated += result.migrated;\n skipped += result.skipped;\n\n if (result.content !== rawTemplate) {\n changes.push({ start, end, text: result.content });\n }\n } else if (ts.isTemplateExpression(initializer)) {\n context.logger.warn(`Skipping interpolated inline template in ${filePath}.`);\n skipped++;\n }\n }\n\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n return {\n content: applyChanges(source, changes),\n migrated,\n skipped,\n };\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (\n (ts.isIdentifier(name) && name.text === 'template') ||\n (ts.isStringLiteral(name) && name.text === 'template')\n );\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n\n while (ts.isParenthesizedExpression(current)) {\n current = current.expression;\n }\n\n return current;\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) {\n return false;\n }\n\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) {\n return false;\n }\n\n return (\n ts.isDecorator(callExpression.parent) &&\n ts.isIdentifier(callExpression.expression) &&\n callExpression.expression.text === 'Component'\n );\n}\n\nfunction migrateTemplate(source: string, filePath: string, context: SchematicContext): MigrationResult {\n const parsed = parseTemplate(source, filePath, { preserveWhitespaces: true });\n const changes: TextChange[] = [];\n let migrated = 0;\n let skipped = 0;\n\n for (const error of parsed.errors ?? []) {\n context.logger.warn(`Template parse warning in ${filePath}: ${error.msg}`);\n }\n\n for (const tab of findElements(parsed.nodes, 'eui-tab')) {\n const label = findDirectChild(tab, OLD_LABEL);\n const content = findDirectChild(tab, OLD_CONTENT);\n const hasOldMarkup = Boolean(label || content);\n const hasNewMarkup = Boolean(findDirectChild(tab, NEW_HEADER) || findDirectChild(tab, NEW_BODY));\n\n if (!hasOldMarkup || hasNewMarkup) {\n continue;\n }\n\n if (!label || !content) {\n context.logger.warn(`Skipping malformed <eui-tab> in ${filePath}.`);\n skipped++;\n continue;\n }\n\n changes.push({\n start: label.sourceSpan.start.offset,\n end: label.sourceSpan.end.offset,\n text: buildHeaderReplacement(source, label),\n });\n changes.push({\n start: content.sourceSpan.start.offset,\n end: content.sourceSpan.end.offset,\n text: buildBodyReplacement(source, content),\n });\n migrated++;\n }\n\n return {\n content: applyChanges(source, withoutOverlaps(changes)),\n migrated,\n skipped,\n };\n}\n\nfunction findElements(nodes: readonly TmplAstNode[], name: string): TmplAstElement[] {\n const matches: TmplAstElement[] = [];\n\n for (const node of nodes) {\n if (isElement(node)) {\n if (node.name === name) {\n matches.push(node);\n }\n matches.push(...findElements(node.children, name));\n }\n }\n\n return matches;\n}\n\nfunction findDirectChild(element: TmplAstElement, name: string): TmplAstElement | undefined {\n return element.children.find((child): child is TmplAstElement => isElement(child) && child.name === name);\n}\n\nfunction isElement(node: TmplAstNode): node is TmplAstElement {\n return node instanceof TmplAstElement;\n}\n\nfunction buildHeaderReplacement(source: string, label: TmplAstElement): string {\n const indent = getIndent(source, label.sourceSpan.start.offset);\n const labelAttributes = getAttributeText(source, label, OLD_LABEL);\n const subLabels = findElements(label.children, OLD_SUB_LABEL);\n const mainLabelContent = removeElementRanges(getInnerText(source, label), label, subLabels);\n const lines = [\n `<${NEW_HEADER}>`,\n `${indent} <${NEW_HEADER_LABEL}${labelAttributes}>`,\n ...formatInnerLines(mainLabelContent, `${indent} `),\n `${indent} </${NEW_HEADER_LABEL}>`,\n ];\n\n for (const subLabel of subLabels) {\n const subLabelAttributes = getAttributeText(source, subLabel, OLD_SUB_LABEL);\n lines.push(\n `${indent} <${NEW_HEADER_SUB_LABEL}${subLabelAttributes}>`,\n ...formatInnerLines(getInnerText(source, subLabel), `${indent} `),\n `${indent} </${NEW_HEADER_SUB_LABEL}>`,\n );\n }\n\n lines.push(`${indent}</${NEW_HEADER}>`);\n\n return lines.join('\\n');\n}\n\nfunction buildBodyReplacement(source: string, content: TmplAstElement): string {\n const indent = getIndent(source, content.sourceSpan.start.offset);\n const attributes = getAttributeText(source, content, OLD_CONTENT);\n const innerText = getInnerText(source, content);\n const trimmed = innerText.trim();\n\n if (trimmed && !trimmed.includes('\\n')) {\n return `<${NEW_BODY}${attributes}>${trimmed}</${NEW_BODY}>`;\n }\n\n return [\n `<${NEW_BODY}${attributes}>`,\n ...formatInnerLines(innerText, `${indent} `),\n `${indent}</${NEW_BODY}>`,\n ].join('\\n');\n}\n\nfunction getInnerText(source: string, element: TmplAstElement): string {\n if (!element.endSourceSpan) {\n return '';\n }\n\n return source.slice(element.startSourceSpan.end.offset, element.endSourceSpan.start.offset);\n}\n\nfunction removeElementRanges(source: string, parent: TmplAstElement, elements: readonly TmplAstElement[]): string {\n const parentContentStart = parent.startSourceSpan.end.offset;\n let result = source;\n\n for (const element of [...elements].sort((a, b) => b.sourceSpan.start.offset - a.sourceSpan.start.offset)) {\n const start = element.sourceSpan.start.offset - parentContentStart;\n const end = element.sourceSpan.end.offset - parentContentStart;\n result = result.slice(0, start) + result.slice(end);\n }\n\n return result;\n}\n\nfunction getAttributeText(source: string, element: TmplAstElement, tagName: string): string {\n const startTag = source.slice(element.startSourceSpan.start.offset, element.startSourceSpan.end.offset);\n const tagStart = startTag.indexOf(tagName);\n\n if (tagStart === -1) {\n return '';\n }\n\n const contentEnd = startTag.endsWith('/>') ? startTag.length - 2 : startTag.length - 1;\n return startTag.slice(tagStart + tagName.length, contentEnd).trimEnd();\n}\n\nfunction getIndent(source: string, offset: number): string {\n const lineStart = source.lastIndexOf('\\n', offset - 1) + 1;\n const prefix = source.slice(lineStart, offset);\n return prefix.match(/^\\s*/)?.[0] ?? '';\n}\n\nfunction formatInnerLines(source: string, indent: string): string[] {\n const trimmed = source.trim();\n\n if (!trimmed) {\n return [];\n }\n\n return trimmed.split(/\\r?\\n/).map((line) => `${indent}${line.trim()}`);\n}\n\nfunction withoutOverlaps(changes: TextChange[]): TextChange[] {\n const accepted: TextChange[] = [];\n\n for (const change of [...changes].sort((a, b) => a.start - b.start || b.end - a.end)) {\n if (!accepted.some((current) => change.start < current.end && current.start < change.end)) {\n accepted.push(change);\n }\n }\n\n return accepted;\n}\n\nfunction applyChanges(source: string, changes: readonly TextChange[]): string {\n let result = source;\n\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\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",
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",
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": 20,
3073
+ "line": 18,
3074
3074
  "rawdescription": "\n"
3075
3075
  },
3076
3076
  {
@@ -3082,7 +3082,7 @@
3082
3082
  "indexKey": "",
3083
3083
  "optional": true,
3084
3084
  "description": "",
3085
- "line": 19,
3085
+ "line": 17,
3086
3086
  "rawdescription": "\n"
3087
3087
  }
3088
3088
  ],
@@ -3147,12 +3147,12 @@
3147
3147
  },
3148
3148
  {
3149
3149
  "name": "Schema",
3150
- "id": "interface-Schema-817c4b549cc3eab9fcf4936acab2e71182d90a4480369f5c003cbbda10792efa587ad99d65acf049f64e7030e32589e4fabc4a6d7a5621e4fe54725ef91dc9e8-21",
3151
- "file": "packages/core/schematics/migrate-to-standalone/index.ts",
3150
+ "id": "interface-Schema-cac2ac952d8245647e8a48b450e8f127a36bcffb394cf2cd81fe9c0810ed02c19e5e1629dbf29a68a5c6f675aed99570e9ac04ae470e13315e20c92cc0c6c453-21",
3151
+ "file": "packages/core/schematics/migrate-to-signals/index.ts",
3152
3152
  "deprecated": false,
3153
3153
  "deprecationMessage": "",
3154
3154
  "type": "interface",
3155
- "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",
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",
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": 460,
3167
+ "line": 9,
3168
3168
  "rawdescription": "\n"
3169
3169
  },
3170
3170
  {
@@ -3176,7 +3176,7 @@
3176
3176
  "indexKey": "",
3177
3177
  "optional": true,
3178
3178
  "description": "",
3179
- "line": 459,
3179
+ "line": 8,
3180
3180
  "rawdescription": "\n"
3181
3181
  }
3182
3182
  ],
@@ -3194,12 +3194,12 @@
3194
3194
  },
3195
3195
  {
3196
3196
  "name": "Schema",
3197
- "id": "interface-Schema-cac2ac952d8245647e8a48b450e8f127a36bcffb394cf2cd81fe9c0810ed02c19e5e1629dbf29a68a5c6f675aed99570e9ac04ae470e13315e20c92cc0c6c453-22",
3198
- "file": "packages/core/schematics/migrate-to-signals/index.ts",
3197
+ "id": "interface-Schema-817c4b549cc3eab9fcf4936acab2e71182d90a4480369f5c003cbbda10792efa587ad99d65acf049f64e7030e32589e4fabc4a6d7a5621e4fe54725ef91dc9e8-22",
3198
+ "file": "packages/core/schematics/migrate-to-standalone/index.ts",
3199
3199
  "deprecated": false,
3200
3200
  "deprecationMessage": "",
3201
3201
  "type": "interface",
3202
- "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",
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",
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": 9,
3214
+ "line": 460,
3215
3215
  "rawdescription": "\n"
3216
3216
  },
3217
3217
  {
@@ -3223,7 +3223,7 @@
3223
3223
  "indexKey": "",
3224
3224
  "optional": true,
3225
3225
  "description": "",
3226
- "line": 8,
3226
+ "line": 459,
3227
3227
  "rawdescription": "\n"
3228
3228
  }
3229
3229
  ],
@@ -23593,45 +23593,45 @@
23593
23593
  "name": "REMOVED_INPUTS",
23594
23594
  "ctype": "miscellaneous",
23595
23595
  "subtype": "variable",
23596
- "file": "packages/core/schematics/migrate-eui-chip-list/index.ts",
23596
+ "file": "packages/core/schematics/migrate-eui-chip/index.ts",
23597
23597
  "coverageIgnore": false,
23598
23598
  "deprecated": false,
23599
23599
  "deprecationMessage": "",
23600
23600
  "type": "unknown",
23601
- "defaultValue": "new Set(['maxVisibleChipsCount', 'isMaxVisibleChipsOpened', 'toggleLinkMoreLabel', 'toggleLinkLessLabel', 'isChipsSorted', 'chipsSortOrder'])"
23601
+ "defaultValue": "new Set(['isSquared'])"
23602
23602
  },
23603
23603
  {
23604
23604
  "name": "REMOVED_INPUTS",
23605
23605
  "ctype": "miscellaneous",
23606
23606
  "subtype": "variable",
23607
- "file": "packages/core/schematics/migrate-eui-chip/index.ts",
23607
+ "file": "packages/core/schematics/migrate-eui-chip-list/index.ts",
23608
23608
  "coverageIgnore": false,
23609
23609
  "deprecated": false,
23610
23610
  "deprecationMessage": "",
23611
23611
  "type": "unknown",
23612
- "defaultValue": "new Set(['isSquared'])"
23612
+ "defaultValue": "new Set(['maxVisibleChipsCount', 'isMaxVisibleChipsOpened', 'toggleLinkMoreLabel', 'toggleLinkLessLabel', 'isChipsSorted', 'chipsSortOrder'])"
23613
23613
  },
23614
23614
  {
23615
23615
  "name": "REMOVED_INPUTS",
23616
23616
  "ctype": "miscellaneous",
23617
23617
  "subtype": "variable",
23618
- "file": "packages/core/schematics/migrate-eui-table/index.ts",
23618
+ "file": "packages/core/schematics/migrate-eui-popover/index.ts",
23619
23619
  "coverageIgnore": false,
23620
23620
  "deprecated": false,
23621
23621
  "deprecationMessage": "",
23622
23622
  "type": "unknown",
23623
- "defaultValue": "new Set(['euiTableBordered', 'isHoverable', 'defaultMultiOrder', 'paginable'])"
23623
+ "defaultValue": "new Set(['type'])"
23624
23624
  },
23625
23625
  {
23626
23626
  "name": "REMOVED_INPUTS",
23627
23627
  "ctype": "miscellaneous",
23628
23628
  "subtype": "variable",
23629
- "file": "packages/core/schematics/migrate-eui-popover/index.ts",
23629
+ "file": "packages/core/schematics/migrate-eui-table/index.ts",
23630
23630
  "coverageIgnore": false,
23631
23631
  "deprecated": false,
23632
23632
  "deprecationMessage": "",
23633
23633
  "type": "unknown",
23634
- "defaultValue": "new Set(['type'])"
23634
+ "defaultValue": "new Set(['euiTableBordered', 'isHoverable', 'defaultMultiOrder', 'paginable'])"
23635
23635
  },
23636
23636
  {
23637
23637
  "name": "REMOVED_OUTPUT",
@@ -29754,7 +29754,7 @@
29754
29754
  },
29755
29755
  {
29756
29756
  "name": "isChipElement",
29757
- "file": "packages/core/schematics/migrate-eui-chip-list/index.ts",
29757
+ "file": "packages/core/schematics/migrate-eui-chip/index.ts",
29758
29758
  "ctype": "miscellaneous",
29759
29759
  "subtype": "function",
29760
29760
  "coverageIgnore": false,
@@ -29786,7 +29786,7 @@
29786
29786
  },
29787
29787
  {
29788
29788
  "name": "isChipElement",
29789
- "file": "packages/core/schematics/migrate-eui-chip/index.ts",
29789
+ "file": "packages/core/schematics/migrate-eui-chip-list/index.ts",
29790
29790
  "ctype": "miscellaneous",
29791
29791
  "subtype": "function",
29792
29792
  "coverageIgnore": false,
@@ -30120,7 +30120,7 @@
30120
30120
  },
30121
30121
  {
30122
30122
  "name": "isComponentMetadataProperty",
30123
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
30123
+ "file": "packages/core/schematics/migrate-eui-tabs/index.ts",
30124
30124
  "ctype": "miscellaneous",
30125
30125
  "subtype": "function",
30126
30126
  "coverageIgnore": false,
@@ -30150,7 +30150,7 @@
30150
30150
  },
30151
30151
  {
30152
30152
  "name": "isComponentMetadataProperty",
30153
- "file": "packages/core/schematics/migrate-eui-tabs/index.ts",
30153
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
30154
30154
  "ctype": "miscellaneous",
30155
30155
  "subtype": "function",
30156
30156
  "coverageIgnore": false,
@@ -30665,7 +30665,7 @@
30665
30665
  },
30666
30666
  {
30667
30667
  "name": "isTemplateProperty",
30668
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
30668
+ "file": "packages/core/schematics/migrate-eui-tabs/index.ts",
30669
30669
  "ctype": "miscellaneous",
30670
30670
  "subtype": "function",
30671
30671
  "coverageIgnore": false,
@@ -30695,7 +30695,7 @@
30695
30695
  },
30696
30696
  {
30697
30697
  "name": "isTemplateProperty",
30698
- "file": "packages/core/schematics/migrate-eui-tabs/index.ts",
30698
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
30699
30699
  "ctype": "miscellaneous",
30700
30700
  "subtype": "function",
30701
30701
  "coverageIgnore": false,
@@ -32227,7 +32227,7 @@
32227
32227
  },
32228
32228
  {
32229
32229
  "name": "migrateInlineTemplates",
32230
- "file": "packages/core/schematics/migrate-eui-alert/index.ts",
32230
+ "file": "packages/core/schematics/migrate-eui-accent/index.ts",
32231
32231
  "ctype": "miscellaneous",
32232
32232
  "subtype": "function",
32233
32233
  "coverageIgnore": false,
@@ -32259,7 +32259,7 @@
32259
32259
  },
32260
32260
  {
32261
32261
  "name": "migrateInlineTemplates",
32262
- "file": "packages/core/schematics/migrate-eui-accent/index.ts",
32262
+ "file": "packages/core/schematics/migrate-eui-alert/index.ts",
32263
32263
  "ctype": "miscellaneous",
32264
32264
  "subtype": "function",
32265
32265
  "coverageIgnore": false,
@@ -32355,7 +32355,7 @@
32355
32355
  },
32356
32356
  {
32357
32357
  "name": "migrateInlineTemplates",
32358
- "file": "packages/core/schematics/migrate-eui-chip-list/index.ts",
32358
+ "file": "packages/core/schematics/migrate-eui-chip/index.ts",
32359
32359
  "ctype": "miscellaneous",
32360
32360
  "subtype": "function",
32361
32361
  "coverageIgnore": false,
@@ -32387,7 +32387,7 @@
32387
32387
  },
32388
32388
  {
32389
32389
  "name": "migrateInlineTemplates",
32390
- "file": "packages/core/schematics/migrate-eui-chip/index.ts",
32390
+ "file": "packages/core/schematics/migrate-eui-chip-list/index.ts",
32391
32391
  "ctype": "miscellaneous",
32392
32392
  "subtype": "function",
32393
32393
  "coverageIgnore": false,
@@ -32579,7 +32579,7 @@
32579
32579
  },
32580
32580
  {
32581
32581
  "name": "migrateInlineTemplates",
32582
- "file": "packages/core/schematics/migrate-eui-progress-circle/index.ts",
32582
+ "file": "packages/core/schematics/migrate-eui-popover/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-popover/index.ts",
32614
+ "file": "packages/core/schematics/migrate-eui-progress-circle/index.ts",
32615
32615
  "ctype": "miscellaneous",
32616
32616
  "subtype": "function",
32617
32617
  "coverageIgnore": false,
@@ -32643,7 +32643,7 @@
32643
32643
  },
32644
32644
  {
32645
32645
  "name": "migrateInlineTemplates",
32646
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
32646
+ "file": "packages/core/schematics/migrate-eui-tabs/index.ts",
32647
32647
  "ctype": "miscellaneous",
32648
32648
  "subtype": "function",
32649
32649
  "coverageIgnore": false,
@@ -32672,7 +32672,7 @@
32672
32672
  "deprecationMessage": ""
32673
32673
  }
32674
32674
  ],
32675
- "returnType": "string",
32675
+ "returnType": "MigrationResult",
32676
32676
  "jsdoctags": [
32677
32677
  {
32678
32678
  "name": "source",
@@ -32705,7 +32705,7 @@
32705
32705
  },
32706
32706
  {
32707
32707
  "name": "migrateInlineTemplates",
32708
- "file": "packages/core/schematics/migrate-eui-tabs/index.ts",
32708
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
32709
32709
  "ctype": "miscellaneous",
32710
32710
  "subtype": "function",
32711
32711
  "coverageIgnore": false,
@@ -32734,7 +32734,7 @@
32734
32734
  "deprecationMessage": ""
32735
32735
  }
32736
32736
  ],
32737
- "returnType": "MigrationResult",
32737
+ "returnType": "string",
32738
32738
  "jsdoctags": [
32739
32739
  {
32740
32740
  "name": "source",
@@ -32767,7 +32767,7 @@
32767
32767
  },
32768
32768
  {
32769
32769
  "name": "migrateTemplate",
32770
- "file": "packages/core/schematics/migrate-eui-alert/index.ts",
32770
+ "file": "packages/core/schematics/migrate-eui-accent/index.ts",
32771
32771
  "ctype": "miscellaneous",
32772
32772
  "subtype": "function",
32773
32773
  "coverageIgnore": false,
@@ -32799,7 +32799,7 @@
32799
32799
  },
32800
32800
  {
32801
32801
  "name": "migrateTemplate",
32802
- "file": "packages/core/schematics/migrate-eui-accent/index.ts",
32802
+ "file": "packages/core/schematics/migrate-eui-alert/index.ts",
32803
32803
  "ctype": "miscellaneous",
32804
32804
  "subtype": "function",
32805
32805
  "coverageIgnore": false,
@@ -32895,7 +32895,7 @@
32895
32895
  },
32896
32896
  {
32897
32897
  "name": "migrateTemplate",
32898
- "file": "packages/core/schematics/migrate-eui-chip-list/index.ts",
32898
+ "file": "packages/core/schematics/migrate-eui-chip/index.ts",
32899
32899
  "ctype": "miscellaneous",
32900
32900
  "subtype": "function",
32901
32901
  "coverageIgnore": false,
@@ -32927,7 +32927,7 @@
32927
32927
  },
32928
32928
  {
32929
32929
  "name": "migrateTemplate",
32930
- "file": "packages/core/schematics/migrate-eui-chip/index.ts",
32930
+ "file": "packages/core/schematics/migrate-eui-chip-list/index.ts",
32931
32931
  "ctype": "miscellaneous",
32932
32932
  "subtype": "function",
32933
32933
  "coverageIgnore": false,
@@ -33117,6 +33117,38 @@
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
+ },
33120
33152
  {
33121
33153
  "name": "migrateTemplate",
33122
33154
  "file": "packages/core/schematics/migrate-eui-progress-circle/index.ts",
@@ -33213,39 +33245,7 @@
33213
33245
  },
33214
33246
  {
33215
33247
  "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
- {
33247
- "name": "migrateTemplate",
33248
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
33248
+ "file": "packages/core/schematics/migrate-eui-tabs/index.ts",
33249
33249
  "ctype": "miscellaneous",
33250
33250
  "subtype": "function",
33251
33251
  "coverageIgnore": false,
@@ -33274,7 +33274,7 @@
33274
33274
  "deprecationMessage": ""
33275
33275
  }
33276
33276
  ],
33277
- "returnType": "string",
33277
+ "returnType": "MigrationResult",
33278
33278
  "jsdoctags": [
33279
33279
  {
33280
33280
  "name": "source",
@@ -33307,7 +33307,7 @@
33307
33307
  },
33308
33308
  {
33309
33309
  "name": "migrateTemplate",
33310
- "file": "packages/core/schematics/migrate-eui-tabs/index.ts",
33310
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
33311
33311
  "ctype": "miscellaneous",
33312
33312
  "subtype": "function",
33313
33313
  "coverageIgnore": false,
@@ -33336,7 +33336,7 @@
33336
33336
  "deprecationMessage": ""
33337
33337
  }
33338
33338
  ],
33339
- "returnType": "MigrationResult",
33339
+ "returnType": "string",
33340
33340
  "jsdoctags": [
33341
33341
  {
33342
33342
  "name": "source",
@@ -35571,7 +35571,7 @@
35571
35571
  },
35572
35572
  {
35573
35573
  "name": "unwrapExpression",
35574
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
35574
+ "file": "packages/core/schematics/migrate-eui-tabs/index.ts",
35575
35575
  "ctype": "miscellaneous",
35576
35576
  "subtype": "function",
35577
35577
  "coverageIgnore": false,
@@ -35601,7 +35601,7 @@
35601
35601
  },
35602
35602
  {
35603
35603
  "name": "unwrapExpression",
35604
- "file": "packages/core/schematics/migrate-eui-tabs/index.ts",
35604
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
35605
35605
  "ctype": "miscellaneous",
35606
35606
  "subtype": "function",
35607
35607
  "coverageIgnore": false,
@@ -35944,7 +35944,7 @@
35944
35944
  },
35945
35945
  {
35946
35946
  "name": "visitDir",
35947
- "file": "packages/core/schematics/migrate-eui-alert/index.ts",
35947
+ "file": "packages/core/schematics/migrate-eui-accent/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-accent/index.ts",
35992
+ "file": "packages/core/schematics/migrate-eui-alert/index.ts",
35993
35993
  "ctype": "miscellaneous",
35994
35994
  "subtype": "function",
35995
35995
  "coverageIgnore": false,
@@ -36124,7 +36124,7 @@
36124
36124
  },
36125
36125
  {
36126
36126
  "name": "visitDir",
36127
- "file": "packages/core/schematics/migrate-eui-chip-list/index.ts",
36127
+ "file": "packages/core/schematics/migrate-eui-chip/index.ts",
36128
36128
  "ctype": "miscellaneous",
36129
36129
  "subtype": "function",
36130
36130
  "coverageIgnore": false,
@@ -36169,7 +36169,7 @@
36169
36169
  },
36170
36170
  {
36171
36171
  "name": "visitDir",
36172
- "file": "packages/core/schematics/migrate-eui-chip/index.ts",
36172
+ "file": "packages/core/schematics/migrate-eui-chip-list/index.ts",
36173
36173
  "ctype": "miscellaneous",
36174
36174
  "subtype": "function",
36175
36175
  "coverageIgnore": false,
@@ -36439,7 +36439,7 @@
36439
36439
  },
36440
36440
  {
36441
36441
  "name": "visitDir",
36442
- "file": "packages/core/schematics/migrate-eui-progress-circle/index.ts",
36442
+ "file": "packages/core/schematics/migrate-eui-popover/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-table/index.ts",
36487
+ "file": "packages/core/schematics/migrate-eui-progress-circle/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-popover/index.ts",
36532
+ "file": "packages/core/schematics/migrate-eui-table/index.ts",
36533
36533
  "ctype": "miscellaneous",
36534
36534
  "subtype": "function",
36535
36535
  "coverageIgnore": false,
@@ -36574,7 +36574,7 @@
36574
36574
  },
36575
36575
  {
36576
36576
  "name": "visitDir",
36577
- "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
36577
+ "file": "packages/core/schematics/migrate-eui-tabs/index.ts",
36578
36578
  "ctype": "miscellaneous",
36579
36579
  "subtype": "function",
36580
36580
  "coverageIgnore": false,
@@ -36619,7 +36619,7 @@
36619
36619
  },
36620
36620
  {
36621
36621
  "name": "visitDir",
36622
- "file": "packages/core/schematics/migrate-eui-tabs/index.ts",
36622
+ "file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
36623
36623
  "ctype": "miscellaneous",
36624
36624
  "subtype": "function",
36625
36625
  "coverageIgnore": false,
@@ -36709,7 +36709,7 @@
36709
36709
  },
36710
36710
  {
36711
36711
  "name": "visitDir",
36712
- "file": "packages/core/schematics/migrate-to-standalone/index.ts",
36712
+ "file": "packages/core/schematics/migrate-to-signals/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-signals/index.ts",
36757
+ "file": "packages/core/schematics/migrate-to-standalone/index.ts",
36758
36758
  "ctype": "miscellaneous",
36759
36759
  "subtype": "function",
36760
36760
  "coverageIgnore": false,
@@ -36844,7 +36844,7 @@
36844
36844
  },
36845
36845
  {
36846
36846
  "name": "visitNodes",
36847
- "file": "packages/core/schematics/migrate-eui-alert/index.ts",
36847
+ "file": "packages/core/schematics/migrate-eui-accent/index.ts",
36848
36848
  "ctype": "miscellaneous",
36849
36849
  "subtype": "function",
36850
36850
  "coverageIgnore": false,
@@ -36860,7 +36860,7 @@
36860
36860
  "deprecationMessage": ""
36861
36861
  },
36862
36862
  {
36863
- "name": "removals",
36863
+ "name": "edits",
36864
36864
  "deprecated": false,
36865
36865
  "deprecationMessage": ""
36866
36866
  }
@@ -36876,7 +36876,7 @@
36876
36876
  }
36877
36877
  },
36878
36878
  {
36879
- "name": "removals",
36879
+ "name": "edits",
36880
36880
  "deprecated": false,
36881
36881
  "deprecationMessage": "",
36882
36882
  "tagName": {
@@ -36887,7 +36887,7 @@
36887
36887
  },
36888
36888
  {
36889
36889
  "name": "visitNodes",
36890
- "file": "packages/core/schematics/migrate-eui-accent/index.ts",
36890
+ "file": "packages/core/schematics/migrate-eui-alert/index.ts",
36891
36891
  "ctype": "miscellaneous",
36892
36892
  "subtype": "function",
36893
36893
  "coverageIgnore": false,
@@ -36903,7 +36903,7 @@
36903
36903
  "deprecationMessage": ""
36904
36904
  },
36905
36905
  {
36906
- "name": "edits",
36906
+ "name": "removals",
36907
36907
  "deprecated": false,
36908
36908
  "deprecationMessage": ""
36909
36909
  }
@@ -36919,7 +36919,7 @@
36919
36919
  }
36920
36920
  },
36921
36921
  {
36922
- "name": "edits",
36922
+ "name": "removals",
36923
36923
  "deprecated": false,
36924
36924
  "deprecationMessage": "",
36925
36925
  "tagName": {
@@ -37016,7 +37016,7 @@
37016
37016
  },
37017
37017
  {
37018
37018
  "name": "visitNodes",
37019
- "file": "packages/core/schematics/migrate-eui-chip-list/index.ts",
37019
+ "file": "packages/core/schematics/migrate-eui-chip/index.ts",
37020
37020
  "ctype": "miscellaneous",
37021
37021
  "subtype": "function",
37022
37022
  "coverageIgnore": false,
@@ -37032,13 +37032,7 @@
37032
37032
  "deprecationMessage": ""
37033
37033
  },
37034
37034
  {
37035
- "name": "source",
37036
- "type": "string",
37037
- "deprecated": false,
37038
- "deprecationMessage": ""
37039
- },
37040
- {
37041
- "name": "edits",
37035
+ "name": "removals",
37042
37036
  "deprecated": false,
37043
37037
  "deprecationMessage": ""
37044
37038
  }
@@ -37054,16 +37048,7 @@
37054
37048
  }
37055
37049
  },
37056
37050
  {
37057
- "name": "source",
37058
- "type": "string",
37059
- "deprecated": false,
37060
- "deprecationMessage": "",
37061
- "tagName": {
37062
- "text": "param"
37063
- }
37064
- },
37065
- {
37066
- "name": "edits",
37051
+ "name": "removals",
37067
37052
  "deprecated": false,
37068
37053
  "deprecationMessage": "",
37069
37054
  "tagName": {
@@ -37074,7 +37059,7 @@
37074
37059
  },
37075
37060
  {
37076
37061
  "name": "visitNodes",
37077
- "file": "packages/core/schematics/migrate-eui-chip/index.ts",
37062
+ "file": "packages/core/schematics/migrate-eui-chip-list/index.ts",
37078
37063
  "ctype": "miscellaneous",
37079
37064
  "subtype": "function",
37080
37065
  "coverageIgnore": false,
@@ -37090,7 +37075,13 @@
37090
37075
  "deprecationMessage": ""
37091
37076
  },
37092
37077
  {
37093
- "name": "removals",
37078
+ "name": "source",
37079
+ "type": "string",
37080
+ "deprecated": false,
37081
+ "deprecationMessage": ""
37082
+ },
37083
+ {
37084
+ "name": "edits",
37094
37085
  "deprecated": false,
37095
37086
  "deprecationMessage": ""
37096
37087
  }
@@ -37106,7 +37097,16 @@
37106
37097
  }
37107
37098
  },
37108
37099
  {
37109
- "name": "removals",
37100
+ "name": "source",
37101
+ "type": "string",
37102
+ "deprecated": false,
37103
+ "deprecationMessage": "",
37104
+ "tagName": {
37105
+ "text": "param"
37106
+ }
37107
+ },
37108
+ {
37109
+ "name": "edits",
37110
37110
  "deprecated": false,
37111
37111
  "deprecationMessage": "",
37112
37112
  "tagName": {
@@ -37347,7 +37347,7 @@
37347
37347
  },
37348
37348
  {
37349
37349
  "name": "visitNodes",
37350
- "file": "packages/core/schematics/migrate-eui-progress-circle/index.ts",
37350
+ "file": "packages/core/schematics/migrate-eui-popover/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": "edits",
37366
+ "name": "removals",
37367
37367
  "deprecated": false,
37368
37368
  "deprecationMessage": ""
37369
37369
  }
@@ -37379,7 +37379,7 @@
37379
37379
  }
37380
37380
  },
37381
37381
  {
37382
- "name": "edits",
37382
+ "name": "removals",
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-popover/index.ts",
37393
+ "file": "packages/core/schematics/migrate-eui-progress-circle/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": "removals",
37409
+ "name": "edits",
37410
37410
  "deprecated": false,
37411
37411
  "deprecationMessage": ""
37412
37412
  }
@@ -37422,7 +37422,7 @@
37422
37422
  }
37423
37423
  },
37424
37424
  {
37425
- "name": "removals",
37425
+ "name": "edits",
37426
37426
  "deprecated": false,
37427
37427
  "deprecationMessage": "",
37428
37428
  "tagName": {