@eui/core 22.0.0-alpha.34 → 22.0.0-alpha.35
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.
- package/CHANGELOG.md +18 -0
- package/docs/changelog.html +11 -0
- package/docs/interfaces/Schema-1.html +46 -1
- package/docs/interfaces/Schema.html +1 -46
- package/docs/js/search/search_index.js +2 -2
- package/docs/json/documentation.json +908 -908
- package/docs/llms.txt +6 -6
- package/docs/miscellaneous/functions.html +2 -2
- package/docs/properties.html +1 -1
- package/package.json +2 -2
|
@@ -909,12 +909,12 @@
|
|
|
909
909
|
},
|
|
910
910
|
{
|
|
911
911
|
"name": "Edit",
|
|
912
|
-
"id": "interface-Edit-
|
|
913
|
-
"file": "packages/core/schematics/migrate-
|
|
912
|
+
"id": "interface-Edit-e1cd02924eb82a618c71a0b26c081bdd020519d2699aa0e2dc98640c4e0f347c3649b967c5fc87543e41bbbfabd1304835850ccc38f40684d6521c242b2030ed-2",
|
|
913
|
+
"file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
|
|
914
914
|
"deprecated": false,
|
|
915
915
|
"deprecationMessage": "",
|
|
916
916
|
"type": "interface",
|
|
917
|
-
"sourceCode": "import { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\nimport { EUI_SIGNAL_MAP, EUI_SELECTOR_TO_CLASS } from './signal-map';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\ninterface Edit {\n start: number;\n end: number;\n replacement: string;\n}\n\n/**\n * Migrates consumer code that accesses EUI component instances programmatically.\n *\n * When EUI migrated @Input() → input(), @ViewChild() → viewChild(), and\n * @ContentChild() → contentChild(), properties that were plain class fields\n * became signal getters. Consumers who access them via @ViewChild need to\n * change `comp.prop` to `comp.prop()`.\n *\n * This schematic:\n * 1. Finds @ViewChild / @ContentChild fields typed as EUI component classes\n * 2. Resolves untyped @ViewChild('refName') by reading the component template\n * and matching #refName to an EUI element tag/selector\n * 3. Tracks which local variable names hold EUI component references (incl. aliases)\n * 4. Rewrites property accesses on those references: `ref.prop` → `ref.prop()`\n * (only for properties listed in the signal map)\n * 5. Does NOT touch template bindings — those are unaffected by signal migrations\n */\nexport function migrateToSignals(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let fileCount = 0;\n let propCount = 0;\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n if (!path.endsWith('.ts')) return;\n // Skip spec files — test code may use different patterns\n if (path.endsWith('.spec.ts')) return;\n\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n\n // Fast bail: file must mention at least one EUI class name or have\n // ViewChild/ContentChild with string refs (which might reference EUI elements)\n if (!hasEuiReference(original) && !hasStringRefQuery(original)) return;\n\n const result = migrateTypeScript(original, path, tree, context); if (result.source !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would migrate ${result.editCount} signal property access(es) in ${path}`);\n } else {\n tree.overwrite(path, result.source);\n }\n fileCount++;\n propCount += result.editCount;\n }\n });\n\n context.logger.info(`Migrated ${propCount} property access(es) to signal getters in ${fileCount} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\n/** Quick check: does the file reference any EUI class name from the signal map? */\nfunction hasEuiReference(source: string): boolean {\n for (const className of Object.keys(EUI_SIGNAL_MAP)) {\n if (source.includes(className)) return true;\n }\n return false;\n}\n\n/** Quick check: does the file have @ViewChild('...') or @ContentChild('...') with string refs? */\nfunction hasStringRefQuery(source: string): boolean {\n return /(@ViewChild|@ContentChild|@ViewChildren|@ContentChildren)\\s*\\(\\s*['\"]/.test(source);\n}\n\ninterface MigrateResult {\n source: string;\n editCount: number;\n}\n\nfunction migrateTypeScript(source: string, filePath: string, tree: Tree, context: SchematicContext): MigrateResult {\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: Edit[] = [];\n\n // Phase 0: Build template ref map (#refName → EUI class name) from the component's template\n const templateRefMap = buildTemplateRefMap(sourceFile, source, filePath, tree);\n\n // Phase 1: Collect all variables/properties typed as EUI classes\n const euiRefs = collectEuiReferences(sourceFile, source, templateRefMap);\n\n if (euiRefs.size === 0) {\n return { source, editCount: 0 };\n }\n\n // Phase 2: Find property accesses on those references and rewrite signal props\n const visitNode = (node: ts.Node): void => {\n // Match: ref.prop (PropertyAccessExpression)\n if (ts.isPropertyAccessExpression(node)) {\n const rewrite = tryRewritePropertyAccess(node, euiRefs, sourceFile, source);\n if (rewrite) {\n edits.push(rewrite);\n }\n }\n ts.forEachChild(node, visitNode);\n };\n\n ts.forEachChild(sourceFile, visitNode);\n\n if (edits.length === 0) {\n return { source, editCount: 0 };\n }\n\n return {\n source: applyEdits(source, edits),\n editCount: edits.length,\n };\n}\n\n/**\n * Scans the source file for class properties or variables typed as EUI component classes.\n * Returns a map: localName → Set of signal property names.\n *\n * Handles:\n * @ViewChild(EuiXxxComponent) name: EuiXxxComponent;\n * @ViewChild(EuiXxxComponent) name!: EuiXxxComponent;\n * @ViewChild('ref') name: EuiXxxComponent;\n * @ContentChild(EuiXxxComponent) name: EuiXxxComponent;\n * @ViewChild('ref') name: ElementRef; (resolved via template #ref → EUI selector)\n * someVar: EuiXxxComponent; (typed class field, even without decorator)\n * const alias = this.viewChildRef; (alias tracking)\n */\nfunction collectEuiReferences(\n sourceFile: ts.SourceFile,\n source: string,\n templateRefMap: Map<string, string>,\n): Map<string, ReadonlySet<string>> {\n const refs = new Map<string, ReadonlySet<string>>();\n\n // Pass 1: collect directly-typed references\n const visitDirect = (node: ts.Node): void => {\n // Class property declarations\n if (ts.isPropertyDeclaration(node) && node.name && ts.isIdentifier(node.name)) {\n const euiClass = resolveEuiClassName(node, sourceFile, templateRefMap);\n if (euiClass && EUI_SIGNAL_MAP[euiClass]) {\n refs.set(node.name.text, EUI_SIGNAL_MAP[euiClass]);\n }\n }\n\n // Variable declarations (const x: EuiXxxComponent = ...)\n if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) {\n const typeName = getTypeAnnotationName(node.type);\n if (typeName && EUI_SIGNAL_MAP[typeName]) {\n refs.set(node.name.text, EUI_SIGNAL_MAP[typeName]);\n }\n }\n\n // Parameter declarations (method(ref: EuiXxxComponent))\n if (ts.isParameter(node) && ts.isIdentifier(node.name)) {\n const typeName = getTypeAnnotationName(node.type);\n if (typeName && EUI_SIGNAL_MAP[typeName]) {\n refs.set(node.name.text, EUI_SIGNAL_MAP[typeName]);\n }\n }\n\n ts.forEachChild(node, visitDirect);\n };\n\n ts.forEachChild(sourceFile, visitDirect);\n\n // Pass 2: track aliases — variables assigned from already-tracked references\n // e.g. const alias = this.viewChildRef; or const alias = someParam;\n const visitAlias = (node: ts.Node): void => {\n if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) {\n // Skip if already tracked (has explicit type annotation)\n if (refs.has(node.name.text)) {\n ts.forEachChild(node, visitAlias);\n return;\n }\n\n const aliasedRef = resolveRefName(node.initializer);\n if (aliasedRef && refs.has(aliasedRef)) {\n refs.set(node.name.text, refs.get(aliasedRef)!);\n }\n }\n\n ts.forEachChild(node, visitAlias);\n };\n\n ts.forEachChild(sourceFile, visitAlias);\n return refs;\n}\n\n/**\n * Resolves the EUI class name from a property declaration.\n * Checks: type annotation → decorator class argument → template ref map.\n */\nfunction resolveEuiClassName(\n node: ts.PropertyDeclaration,\n sourceFile: ts.SourceFile,\n templateRefMap: Map<string, string>,\n): string | null {\n // Check type annotation first\n const typeName = getTypeAnnotationName(node.type);\n if (typeName && EUI_SIGNAL_MAP[typeName]) {\n return typeName;\n }\n\n // Check generic type argument: ElementRef<EuiXxxComponent>\n if (node.type && ts.isTypeReferenceNode(node.type) && node.type.typeArguments?.length) {\n const genericArg = getTypeAnnotationName(node.type.typeArguments[0]);\n if (genericArg && EUI_SIGNAL_MAP[genericArg]) {\n return genericArg;\n }\n }\n\n // Check decorator arguments: @ViewChild(EuiXxxComponent) or @ViewChild('refName')\n if (node.modifiers) {\n for (const modifier of node.modifiers) {\n if (!ts.isDecorator(modifier)) continue;\n if (!ts.isCallExpression(modifier.expression)) continue;\n\n const decoratorName = getDecoratorName(modifier.expression);\n if (!decoratorName) continue;\n if (!['ViewChild', 'ViewChildren', 'ContentChild', 'ContentChildren'].includes(decoratorName)) continue;\n\n const args = modifier.expression.arguments;\n if (args.length > 0) {\n const firstArg = args[0];\n\n // @ViewChild(EuiXxxComponent) — class reference\n if (ts.isIdentifier(firstArg) && EUI_SIGNAL_MAP[firstArg.text]) {\n return firstArg.text;\n }\n\n // @ViewChild('refName') — string template ref, resolve via template\n if (ts.isStringLiteralLike(firstArg)) {\n const refName = firstArg.text;\n const euiClass = templateRefMap.get(refName);\n if (euiClass && EUI_SIGNAL_MAP[euiClass]) {\n return euiClass;\n }\n }\n }\n }\n }\n\n return null;\n}\n\nfunction getDecoratorName(callExpr: ts.CallExpression): string | null {\n if (ts.isIdentifier(callExpr.expression)) {\n return callExpr.expression.text;\n }\n return null;\n}\n\nfunction getTypeAnnotationName(typeNode: ts.TypeNode | undefined): string | null {\n if (!typeNode) return null;\n if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {\n return typeNode.typeName.text;\n }\n return null;\n}\n\n/**\n * Checks if a property access `ref.prop` should be rewritten to `ref.prop()`.\n *\n * We rewrite if:\n * - `ref` is tracked in euiRefs\n * - `prop` is in the signal set for that EUI class\n * - The access is NOT already a call expression (not `ref.prop()` or `ref.prop(args)`)\n * - The access is NOT a write target (not `ref.prop = value`)\n * - The access is NOT a template binding argument (those are unaffected)\n */\nfunction tryRewritePropertyAccess(\n node: ts.PropertyAccessExpression,\n euiRefs: Map<string, ReadonlySet<string>>,\n sourceFile: ts.SourceFile,\n source: string,\n): Edit | null {\n const propName = node.name.text;\n\n // Resolve the reference name — handle `this.ref.prop` and `ref.prop`\n const refName = resolveRefName(node.expression);\n if (!refName) return null;\n\n const signalProps = euiRefs.get(refName);\n if (!signalProps || !signalProps.has(propName)) return null;\n\n // Skip if parent is already a call expression on this property: ref.prop(...)\n if (ts.isCallExpression(node.parent) && node.parent.expression === node) {\n return null;\n }\n\n // Skip if this is the left side of an assignment: ref.prop = value\n if (ts.isBinaryExpression(node.parent) &&\n node.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken &&\n node.parent.left === node) {\n return null;\n }\n\n // Skip if this is a compound assignment: ref.prop += value, ref.prop &&= value, etc.\n if (ts.isBinaryExpression(node.parent) && isAssignmentOperator(node.parent.operatorToken.kind) && node.parent.left === node) {\n return null;\n }\n\n // Skip if this is used with delete: delete ref.prop\n if (ts.isDeleteExpression(node.parent) && node.parent.expression === node) {\n return null;\n }\n\n // Skip if inside a typeof: typeof ref.prop\n if (ts.isTypeOfExpression(node.parent) && node.parent.expression === node) {\n return null;\n }\n\n // Skip property access chains that go deeper: ref.prop.something → ref.prop().something\n // This IS correct — we still want to add ()\n\n // Rewrite: insert () after the property name\n const end = node.name.getEnd();\n return {\n start: end,\n end: end,\n replacement: '()',\n };\n}\n\nfunction isAssignmentOperator(kind: ts.SyntaxKind): boolean {\n return kind >= ts.SyntaxKind.FirstAssignment && kind <= ts.SyntaxKind.LastAssignment;\n}\n\n/**\n * Resolves a reference name from an expression.\n * Handles: `ref`, `this.ref`, `this._ref`\n */\nfunction resolveRefName(expr: ts.Expression): string | null {\n // Direct identifier: ref.prop\n if (ts.isIdentifier(expr)) {\n return expr.text;\n }\n\n // this.ref.prop\n if (ts.isPropertyAccessExpression(expr) && expr.expression.kind === ts.SyntaxKind.ThisKeyword) {\n return expr.name.text;\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Template ref resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Builds a map of template reference variable names (#ref) to EUI class names.\n *\n * Parses the component's template (inline or external) and finds elements like:\n * <eui-alert #myAlert> → myAlert → EuiAlertComponent\n * <input euiAutocomplete #ac> → ac → EuiAutocompleteComponent\n *\n * Returns Map<refName, euiClassName>.\n */\nfunction buildTemplateRefMap(\n sourceFile: ts.SourceFile,\n source: string,\n filePath: string,\n tree: Tree,\n): Map<string, string> {\n const refMap = new Map<string, string>();\n\n // Find @Component decorator\n const componentMeta = findComponentMetadata(sourceFile, source);\n if (!componentMeta) return refMap;\n\n let templateSource: string | null = null;\n\n if (componentMeta.inlineTemplate !== null) {\n templateSource = componentMeta.inlineTemplate;\n } else if (componentMeta.templateUrl) {\n // Resolve templateUrl relative to the TS file\n const dir = filePath.substring(0, filePath.lastIndexOf('/'));\n const templatePath = `${dir}/${componentMeta.templateUrl}`;\n const buffer = tree.read(templatePath);\n if (buffer) {\n templateSource = buffer.toString('utf-8');\n }\n }\n\n if (!templateSource) return refMap;\n\n // Parse the template and collect #refs\n try {\n const parsed = parseTemplate(templateSource, '', { preserveWhitespaces: true });\n collectTemplateRefs(parsed.nodes, refMap);\n } catch {\n // Template parse failure — silently skip (might be malformed or use third-party syntax)\n }\n\n return refMap;\n}\n\n/**\n * Extracts @Component metadata: templateUrl or inline template content.\n */\nfunction findComponentMetadata(\n sourceFile: ts.SourceFile,\n source: string,\n): { templateUrl: string | null; inlineTemplate: string | null } | null {\n let result: { templateUrl: string | null; inlineTemplate: string | null } | null = null;\n\n const visit = (node: ts.Node): void => {\n if (result) return;\n\n if (ts.isDecorator(node) && ts.isCallExpression(node.expression)) {\n const name = getDecoratorName(node.expression);\n if (name !== 'Component') {\n ts.forEachChild(node, visit);\n return;\n }\n\n const args = node.expression.arguments;\n if (args.length === 0 || !ts.isObjectLiteralExpression(args[0])) {\n ts.forEachChild(node, visit);\n return;\n }\n\n const meta: { templateUrl: string | null; inlineTemplate: string | null } = {\n templateUrl: null,\n inlineTemplate: null,\n };\n\n for (const prop of args[0].properties) {\n if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) continue;\n\n if (prop.name.text === 'templateUrl') {\n const init = unwrapExpression(prop.initializer);\n if (ts.isStringLiteralLike(init)) {\n meta.templateUrl = init.text;\n }\n }\n\n if (prop.name.text === 'template') {\n const init = unwrapExpression(prop.initializer);\n if (ts.isStringLiteralLike(init)) {\n meta.inlineTemplate = init.text;\n } else if (ts.isNoSubstitutionTemplateLiteral(init)) {\n meta.inlineTemplate = init.text;\n }\n }\n }\n\n result = meta;\n return;\n }\n\n ts.forEachChild(node, visit);\n };\n\n ts.forEachChild(sourceFile, visit);\n return result;\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\n/**\n * Recursively walks template AST nodes, collecting #refName → EUI class name mappings.\n *\n * For each element with a template reference (#ref), we try to resolve its EUI class:\n * 1. Match element tag name against EUI_SELECTOR_TO_CLASS (e.g. 'eui-alert' → EuiAlertComponent)\n * 2. Match element attributes against attribute selectors (e.g. [euiAutocomplete] → EuiAutocompleteComponent)\n */\nfunction collectTemplateRefs(nodes: TmplAstNode[], refMap: Map<string, string>): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.references && node.references.length > 0) {\n const euiClass = resolveElementToEuiClass(node);\n if (euiClass) {\n for (const ref of node.references) {\n refMap.set(ref.name, euiClass);\n }\n }\n }\n collectTemplateRefs(node.children, refMap);\n }\n }\n}\n\n/**\n * Given a template element, resolves it to an EUI class name by checking:\n * 1. Tag name: <eui-alert> → look up 'eui-alert' in EUI_SELECTOR_TO_CLASS\n * 2. Attribute selectors: <input euiAutocomplete> → look up 'input[euiAutocomplete]' or '[euiAutocomplete]'\n */\nfunction resolveElementToEuiClass(element: TmplAstElement): string | null {\n const tag = element.name;\n\n // Direct tag match: eui-alert, eui-dialog, etc.\n if (EUI_SELECTOR_TO_CLASS[tag]) {\n return EUI_SELECTOR_TO_CLASS[tag];\n }\n\n // Attribute selector match: check all attributes against known patterns\n const allAttrNames = [\n ...element.attributes.map((a) => a.name),\n ...element.inputs.map((i) => i.name),\n ];\n\n for (const attrName of allAttrNames) {\n // Try tag[attr] pattern first (e.g. 'button[euiButton]', 'input[euiAutocomplete]')\n const tagAttrSelector = `${tag}[${attrName}]`;\n if (EUI_SELECTOR_TO_CLASS[tagAttrSelector]) {\n return EUI_SELECTOR_TO_CLASS[tagAttrSelector];\n }\n\n // Try [attr] pattern (e.g. '[euiTooltip]', '[euiLoading]')\n const attrSelector = `[${attrName}]`;\n if (EUI_SELECTOR_TO_CLASS[attrSelector]) {\n return EUI_SELECTOR_TO_CLASS[attrSelector];\n }\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Edit helpers\n// ---------------------------------------------------------------------------\n\nfunction applyEdits(source: string, edits: Edit[]): string {\n const unique = deduplicateEdits(edits);\n let result = source;\n for (const edit of unique.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n\nfunction deduplicateEdits(edits: Edit[]): Edit[] {\n const seen = new Map<string, Edit>();\n for (const edit of edits) {\n const key = `${edit.start}:${edit.end}`;\n seen.set(key, edit);\n }\n return Array.from(seen.values());\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n",
|
|
917
|
+
"sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\nconst OLD_TAG = 'eui-toolbar-menu';\nconst NEW_TAG = 'eui-toolbar-mega-menu';\nconst OLD_COMPONENT = 'EuiToolbarMenuComponent';\nconst NEW_COMPONENT = 'EuiToolbarMegaMenuComponent';\nconst OLD_INTERFACE = 'ToolbarItem';\nconst NEW_INTERFACE = 'EuiMenuItem';\nconst NEW_COMPONENT_PATH = '@eui/components/layout';\nconst NEW_INTERFACE_PATH = '@eui/core';\nconst REMOVED_OUTPUT = 'menuItemClick';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\ninterface Edit {\n start: number;\n end: number;\n replacement: string;\n}\n\nexport function migrateEuiToolbarMenu(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let fileCount = 0;\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(OLD_TAG) && !original.includes(OLD_COMPONENT) && !original.includes(OLD_INTERFACE)) return;\n\n let result: string;\n\n if (path.endsWith('.html')) {\n result = migrateTemplate(original, path, context);\n } else {\n result = migrateTypeScript(original, path, context);\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would migrate eui-toolbar-menu → eui-toolbar-mega-menu in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n fileCount++;\n }\n });\n\n context.logger.info(`Migrated eui-toolbar-menu → eui-toolbar-mega-menu in ${fileCount} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction migrateTemplate(source: string, filePath: string, context: SchematicContext): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: Edit[] = [];\n\n visitNodes(parsed.nodes, source, edits, filePath, context);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateTypeScript(source: string, filePath: string, context: SchematicContext): string {\n let result = migrateInlineTemplates(source, filePath, context);\n result = migrateImportsAndTypes(result, filePath, context);\n return result;\n}\n\nfunction migrateInlineTemplates(source: string, filePath: string, context: SchematicContext): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: Edit[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(OLD_TAG)) {\n ts.forEachChild(node, visit); return; \n}\n const migrated = migrateTemplate(rawTemplate, filePath, context);\n if (migrated !== rawTemplate) changes.push({ start, end, replacement: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n return applyEdits(source, changes);\n}\n\nfunction migrateImportsAndTypes(source: string, filePath: string, context: SchematicContext): string {\n if (!source.includes(OLD_COMPONENT) && !source.includes(OLD_INTERFACE)) return source;\n\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: Edit[] = [];\n\n // Track if EuiMenuItem is already imported from @eui/core\n let hasEuiMenuItemImport = false;\n\n // First pass: analyze imports\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const moduleSpec = (stmt.moduleSpecifier as ts.StringLiteral).text;\n const namedBindings = stmt.importClause?.namedBindings;\n if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;\n\n for (const specifier of namedBindings.elements) {\n if (specifier.name.text === NEW_INTERFACE && moduleSpec === NEW_INTERFACE_PATH) {\n hasEuiMenuItemImport = true;\n }\n }\n }\n\n // Second pass: collect edits for import declarations\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const namedBindings = stmt.importClause?.namedBindings;\n if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;\n\n const moduleSpec = stmt.moduleSpecifier as ts.StringLiteral;\n const specifiers = namedBindings.elements;\n const hasComponent = specifiers.some((s) => s.name.text === OLD_COMPONENT);\n const hasInterface = specifiers.some((s) => s.name.text === OLD_INTERFACE);\n\n if (hasComponent && hasInterface) {\n // Both are in the same import → must split into two different paths\n const others = specifiers.filter((s) => s.name.text !== OLD_COMPONENT && s.name.text !== OLD_INTERFACE);\n const lines: string[] = [];\n lines.push(`import { ${NEW_COMPONENT} } from '${NEW_COMPONENT_PATH}';`);\n if (!hasEuiMenuItemImport) {\n lines.push(`import { ${NEW_INTERFACE} } from '${NEW_INTERFACE_PATH}';`);\n }\n if (others.length > 0) {\n const otherNames = others.map((s) => s.name.text).join(', ');\n lines.push(`import { ${otherNames} } from '${moduleSpec.text}';`);\n }\n edits.push({\n start: stmt.getStart(sourceFile),\n end: stmt.getEnd(),\n replacement: lines.join('\\n'),\n });\n } else if (hasComponent) {\n edits.push({\n start: moduleSpec.getStart(sourceFile) + 1,\n end: moduleSpec.getEnd() - 1,\n replacement: NEW_COMPONENT_PATH,\n });\n for (const specifier of specifiers) {\n if (specifier.name.text === OLD_COMPONENT) {\n edits.push({\n start: specifier.name.getStart(sourceFile),\n end: specifier.name.getEnd(),\n replacement: NEW_COMPONENT,\n });\n }\n }\n } else if (hasInterface) {\n if (hasEuiMenuItemImport) {\n removeImportSpecifier(namedBindings, specifiers.find((s) => s.name.text === OLD_INTERFACE)!, sourceFile, edits);\n } else {\n edits.push({\n start: moduleSpec.getStart(sourceFile) + 1,\n end: moduleSpec.getEnd() - 1,\n replacement: NEW_INTERFACE_PATH,\n });\n for (const specifier of specifiers) {\n if (specifier.name.text === OLD_INTERFACE) {\n edits.push({\n start: specifier.name.getStart(sourceFile),\n end: specifier.name.getEnd(),\n replacement: NEW_INTERFACE,\n });\n }\n }\n }\n }\n }\n\n // Third pass: rename identifier references in non-import positions\n const visitRefs = (node: ts.Node): void => {\n if (ts.isImportDeclaration(node)) return; // skip imports (already handled)\n if (ts.isIdentifier(node)) {\n if (node.text === OLD_COMPONENT) {\n edits.push({ start: node.getStart(sourceFile), end: node.getEnd(), replacement: NEW_COMPONENT });\n }\n if (node.text === OLD_INTERFACE) {\n edits.push({ start: node.getStart(sourceFile), end: node.getEnd(), replacement: NEW_INTERFACE });\n }\n }\n ts.forEachChild(node, visitRefs);\n };\n\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) {\n visitRefs(stmt);\n }\n }\n\n // Warn about ToolbarItem-specific properties\n warnRemovedProperties(sourceFile, filePath, context);\n\n return applyEdits(source, edits);\n}\n\nfunction removeImportSpecifier(\n namedImports: ts.NamedImports,\n specifier: ts.ImportSpecifier,\n sourceFile: ts.SourceFile,\n edits: Edit[],\n): void {\n const elements = namedImports.elements;\n if (elements.length === 1) {\n // Remove the entire import declaration\n const importDecl = namedImports.parent.parent;\n edits.push({\n start: importDecl.getStart(sourceFile),\n end: importDecl.getEnd(),\n replacement: '',\n });\n } else {\n // Remove just this specifier with surrounding comma/whitespace\n const idx = elements.indexOf(specifier);\n let start: number;\n let end: number;\n if (idx < elements.length - 1) {\n start = specifier.getStart(sourceFile);\n end = elements[idx + 1].getStart(sourceFile);\n } else {\n start = elements[idx - 1].getEnd();\n end = specifier.getEnd();\n }\n edits.push({ start, end, replacement: '' });\n }\n}\n\nfunction warnRemovedProperties(sourceFile: ts.SourceFile, filePath: string, context: SchematicContext): void {\n const deprecated = ['isHome', 'isSeparator'];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && deprecated.includes(node.name.text)) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${filePath}:${line + 1} - \"${node.name.text}\" was part of ToolbarItem but does not exist on EuiMenuItem. Review manually.`,\n );\n }\n if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && deprecated.includes(node.name.text)) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${filePath}:${line + 1} - \"${node.name.text}\" was part of ToolbarItem but does not exist on EuiMenuItem. Review manually.`,\n );\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n}\n\nfunction visitNodes(nodes: TmplAstNode[], source: string, edits: Edit[], filePath: string, context: SchematicContext): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === OLD_TAG) {\n collectTagRenames(node, source, edits);\n collectOutputRemovals(node, source, edits, filePath, context);\n }\n visitNodes(node.children, source, edits, filePath, context);\n }\n }\n}\n\nfunction collectTagRenames(element: TmplAstElement, source: string, edits: Edit[]): void {\n // Rename opening tag\n const openStart = element.startSourceSpan.start.offset + 1; // skip '<'\n edits.push({ start: openStart, end: openStart + OLD_TAG.length, replacement: NEW_TAG });\n\n // Rename closing tag\n if (element.endSourceSpan) {\n const closeStart = element.endSourceSpan.start.offset + 2; // skip '</'\n edits.push({ start: closeStart, end: closeStart + OLD_TAG.length, replacement: NEW_TAG });\n }\n}\n\nfunction collectOutputRemovals(\n element: TmplAstElement,\n source: string,\n edits: Edit[],\n filePath: string,\n context: SchematicContext,\n): void {\n for (const output of element.outputs) {\n if (output.name === REMOVED_OUTPUT) {\n let start = output.sourceSpan.start.offset;\n // Remove leading whitespace\n while (start > 0 && (source[start - 1] === ' ' || source[start - 1] === '\\t')) {\n start--;\n }\n edits.push({ start, end: output.sourceSpan.end.offset, replacement: '' });\n\n const { line } = element.startSourceSpan.start;\n context.logger.warn(\n `${filePath}:${line + 1} - \"(menuItemClick)\" has been removed. There is no equivalent on eui-toolbar-mega-menu.`,\n );\n }\n }\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction applyEdits(source: string, edits: Edit[]): string {\n // Deduplicate edits at same position (e.g. module path edits when both Component and ToolbarItem are from same source)\n const unique = deduplicateEdits(edits);\n let result = source;\n for (const edit of unique.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n\nfunction deduplicateEdits(edits: Edit[]): Edit[] {\n const seen = new Map<string, Edit>();\n for (const edit of edits) {\n const key = `${edit.start}:${edit.end}`;\n // Last wins for same range\n seen.set(key, edit);\n }\n return Array.from(seen.values());\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n",
|
|
918
918
|
"displayName": "Edit",
|
|
919
919
|
"properties": [
|
|
920
920
|
{
|
|
@@ -926,7 +926,7 @@
|
|
|
926
926
|
"indexKey": "",
|
|
927
927
|
"optional": false,
|
|
928
928
|
"description": "",
|
|
929
|
-
"line":
|
|
929
|
+
"line": 23,
|
|
930
930
|
"rawdescription": "\n"
|
|
931
931
|
},
|
|
932
932
|
{
|
|
@@ -938,7 +938,7 @@
|
|
|
938
938
|
"indexKey": "",
|
|
939
939
|
"optional": false,
|
|
940
940
|
"description": "",
|
|
941
|
-
"line":
|
|
941
|
+
"line": 24,
|
|
942
942
|
"rawdescription": "\n"
|
|
943
943
|
},
|
|
944
944
|
{
|
|
@@ -950,7 +950,7 @@
|
|
|
950
950
|
"indexKey": "",
|
|
951
951
|
"optional": false,
|
|
952
952
|
"description": "",
|
|
953
|
-
"line":
|
|
953
|
+
"line": 22,
|
|
954
954
|
"rawdescription": "\n"
|
|
955
955
|
}
|
|
956
956
|
],
|
|
@@ -1027,12 +1027,12 @@
|
|
|
1027
1027
|
},
|
|
1028
1028
|
{
|
|
1029
1029
|
"name": "Edit",
|
|
1030
|
-
"id": "interface-Edit-
|
|
1031
|
-
"file": "packages/core/schematics/migrate-
|
|
1030
|
+
"id": "interface-Edit-cac2ac952d8245647e8a48b450e8f127a36bcffb394cf2cd81fe9c0810ed02c19e5e1629dbf29a68a5c6f675aed99570e9ac04ae470e13315e20c92cc0c6c453-4",
|
|
1031
|
+
"file": "packages/core/schematics/migrate-to-signals/index.ts",
|
|
1032
1032
|
"deprecated": false,
|
|
1033
1033
|
"deprecationMessage": "",
|
|
1034
1034
|
"type": "interface",
|
|
1035
|
-
"sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\nconst OLD_TAG = 'eui-toolbar-menu';\nconst NEW_TAG = 'eui-toolbar-mega-menu';\nconst OLD_COMPONENT = 'EuiToolbarMenuComponent';\nconst NEW_COMPONENT = 'EuiToolbarMegaMenuComponent';\nconst OLD_INTERFACE = 'ToolbarItem';\nconst NEW_INTERFACE = 'EuiMenuItem';\nconst NEW_COMPONENT_PATH = '@eui/components/layout';\nconst NEW_INTERFACE_PATH = '@eui/core';\nconst REMOVED_OUTPUT = 'menuItemClick';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\ninterface Edit {\n start: number;\n end: number;\n replacement: string;\n}\n\nexport function migrateEuiToolbarMenu(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let fileCount = 0;\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(OLD_TAG) && !original.includes(OLD_COMPONENT) && !original.includes(OLD_INTERFACE)) return;\n\n let result: string;\n\n if (path.endsWith('.html')) {\n result = migrateTemplate(original, path, context);\n } else {\n result = migrateTypeScript(original, path, context);\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would migrate eui-toolbar-menu → eui-toolbar-mega-menu in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n fileCount++;\n }\n });\n\n context.logger.info(`Migrated eui-toolbar-menu → eui-toolbar-mega-menu in ${fileCount} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction migrateTemplate(source: string, filePath: string, context: SchematicContext): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: Edit[] = [];\n\n visitNodes(parsed.nodes, source, edits, filePath, context);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateTypeScript(source: string, filePath: string, context: SchematicContext): string {\n let result = migrateInlineTemplates(source, filePath, context);\n result = migrateImportsAndTypes(result, filePath, context);\n return result;\n}\n\nfunction migrateInlineTemplates(source: string, filePath: string, context: SchematicContext): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: Edit[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(OLD_TAG)) {\n ts.forEachChild(node, visit); return; \n}\n const migrated = migrateTemplate(rawTemplate, filePath, context);\n if (migrated !== rawTemplate) changes.push({ start, end, replacement: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n return applyEdits(source, changes);\n}\n\nfunction migrateImportsAndTypes(source: string, filePath: string, context: SchematicContext): string {\n if (!source.includes(OLD_COMPONENT) && !source.includes(OLD_INTERFACE)) return source;\n\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: Edit[] = [];\n\n // Track if EuiMenuItem is already imported from @eui/core\n let hasEuiMenuItemImport = false;\n\n // First pass: analyze imports\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const moduleSpec = (stmt.moduleSpecifier as ts.StringLiteral).text;\n const namedBindings = stmt.importClause?.namedBindings;\n if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;\n\n for (const specifier of namedBindings.elements) {\n if (specifier.name.text === NEW_INTERFACE && moduleSpec === NEW_INTERFACE_PATH) {\n hasEuiMenuItemImport = true;\n }\n }\n }\n\n // Second pass: collect edits for import declarations\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const namedBindings = stmt.importClause?.namedBindings;\n if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;\n\n const moduleSpec = stmt.moduleSpecifier as ts.StringLiteral;\n const specifiers = namedBindings.elements;\n const hasComponent = specifiers.some((s) => s.name.text === OLD_COMPONENT);\n const hasInterface = specifiers.some((s) => s.name.text === OLD_INTERFACE);\n\n if (hasComponent && hasInterface) {\n // Both are in the same import → must split into two different paths\n const others = specifiers.filter((s) => s.name.text !== OLD_COMPONENT && s.name.text !== OLD_INTERFACE);\n const lines: string[] = [];\n lines.push(`import { ${NEW_COMPONENT} } from '${NEW_COMPONENT_PATH}';`);\n if (!hasEuiMenuItemImport) {\n lines.push(`import { ${NEW_INTERFACE} } from '${NEW_INTERFACE_PATH}';`);\n }\n if (others.length > 0) {\n const otherNames = others.map((s) => s.name.text).join(', ');\n lines.push(`import { ${otherNames} } from '${moduleSpec.text}';`);\n }\n edits.push({\n start: stmt.getStart(sourceFile),\n end: stmt.getEnd(),\n replacement: lines.join('\\n'),\n });\n } else if (hasComponent) {\n edits.push({\n start: moduleSpec.getStart(sourceFile) + 1,\n end: moduleSpec.getEnd() - 1,\n replacement: NEW_COMPONENT_PATH,\n });\n for (const specifier of specifiers) {\n if (specifier.name.text === OLD_COMPONENT) {\n edits.push({\n start: specifier.name.getStart(sourceFile),\n end: specifier.name.getEnd(),\n replacement: NEW_COMPONENT,\n });\n }\n }\n } else if (hasInterface) {\n if (hasEuiMenuItemImport) {\n removeImportSpecifier(namedBindings, specifiers.find((s) => s.name.text === OLD_INTERFACE)!, sourceFile, edits);\n } else {\n edits.push({\n start: moduleSpec.getStart(sourceFile) + 1,\n end: moduleSpec.getEnd() - 1,\n replacement: NEW_INTERFACE_PATH,\n });\n for (const specifier of specifiers) {\n if (specifier.name.text === OLD_INTERFACE) {\n edits.push({\n start: specifier.name.getStart(sourceFile),\n end: specifier.name.getEnd(),\n replacement: NEW_INTERFACE,\n });\n }\n }\n }\n }\n }\n\n // Third pass: rename identifier references in non-import positions\n const visitRefs = (node: ts.Node): void => {\n if (ts.isImportDeclaration(node)) return; // skip imports (already handled)\n if (ts.isIdentifier(node)) {\n if (node.text === OLD_COMPONENT) {\n edits.push({ start: node.getStart(sourceFile), end: node.getEnd(), replacement: NEW_COMPONENT });\n }\n if (node.text === OLD_INTERFACE) {\n edits.push({ start: node.getStart(sourceFile), end: node.getEnd(), replacement: NEW_INTERFACE });\n }\n }\n ts.forEachChild(node, visitRefs);\n };\n\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) {\n visitRefs(stmt);\n }\n }\n\n // Warn about ToolbarItem-specific properties\n warnRemovedProperties(sourceFile, filePath, context);\n\n return applyEdits(source, edits);\n}\n\nfunction removeImportSpecifier(\n namedImports: ts.NamedImports,\n specifier: ts.ImportSpecifier,\n sourceFile: ts.SourceFile,\n edits: Edit[],\n): void {\n const elements = namedImports.elements;\n if (elements.length === 1) {\n // Remove the entire import declaration\n const importDecl = namedImports.parent.parent;\n edits.push({\n start: importDecl.getStart(sourceFile),\n end: importDecl.getEnd(),\n replacement: '',\n });\n } else {\n // Remove just this specifier with surrounding comma/whitespace\n const idx = elements.indexOf(specifier);\n let start: number;\n let end: number;\n if (idx < elements.length - 1) {\n start = specifier.getStart(sourceFile);\n end = elements[idx + 1].getStart(sourceFile);\n } else {\n start = elements[idx - 1].getEnd();\n end = specifier.getEnd();\n }\n edits.push({ start, end, replacement: '' });\n }\n}\n\nfunction warnRemovedProperties(sourceFile: ts.SourceFile, filePath: string, context: SchematicContext): void {\n const deprecated = ['isHome', 'isSeparator'];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && deprecated.includes(node.name.text)) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${filePath}:${line + 1} - \"${node.name.text}\" was part of ToolbarItem but does not exist on EuiMenuItem. Review manually.`,\n );\n }\n if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && deprecated.includes(node.name.text)) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${filePath}:${line + 1} - \"${node.name.text}\" was part of ToolbarItem but does not exist on EuiMenuItem. Review manually.`,\n );\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n}\n\nfunction visitNodes(nodes: TmplAstNode[], source: string, edits: Edit[], filePath: string, context: SchematicContext): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === OLD_TAG) {\n collectTagRenames(node, source, edits);\n collectOutputRemovals(node, source, edits, filePath, context);\n }\n visitNodes(node.children, source, edits, filePath, context);\n }\n }\n}\n\nfunction collectTagRenames(element: TmplAstElement, source: string, edits: Edit[]): void {\n // Rename opening tag\n const openStart = element.startSourceSpan.start.offset + 1; // skip '<'\n edits.push({ start: openStart, end: openStart + OLD_TAG.length, replacement: NEW_TAG });\n\n // Rename closing tag\n if (element.endSourceSpan) {\n const closeStart = element.endSourceSpan.start.offset + 2; // skip '</'\n edits.push({ start: closeStart, end: closeStart + OLD_TAG.length, replacement: NEW_TAG });\n }\n}\n\nfunction collectOutputRemovals(\n element: TmplAstElement,\n source: string,\n edits: Edit[],\n filePath: string,\n context: SchematicContext,\n): void {\n for (const output of element.outputs) {\n if (output.name === REMOVED_OUTPUT) {\n let start = output.sourceSpan.start.offset;\n // Remove leading whitespace\n while (start > 0 && (source[start - 1] === ' ' || source[start - 1] === '\\t')) {\n start--;\n }\n edits.push({ start, end: output.sourceSpan.end.offset, replacement: '' });\n\n const { line } = element.startSourceSpan.start;\n context.logger.warn(\n `${filePath}:${line + 1} - \"(menuItemClick)\" has been removed. There is no equivalent on eui-toolbar-mega-menu.`,\n );\n }\n }\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction applyEdits(source: string, edits: Edit[]): string {\n // Deduplicate edits at same position (e.g. module path edits when both Component and ToolbarItem are from same source)\n const unique = deduplicateEdits(edits);\n let result = source;\n for (const edit of unique.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n\nfunction deduplicateEdits(edits: Edit[]): Edit[] {\n const seen = new Map<string, Edit>();\n for (const edit of edits) {\n const key = `${edit.start}:${edit.end}`;\n // Last wins for same range\n seen.set(key, edit);\n }\n return Array.from(seen.values());\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n",
|
|
1035
|
+
"sourceCode": "import { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\nimport { EUI_SIGNAL_MAP, EUI_SELECTOR_TO_CLASS } from './signal-map';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\ninterface Edit {\n start: number;\n end: number;\n replacement: string;\n}\n\n/**\n * Migrates consumer code that accesses EUI component instances programmatically.\n *\n * When EUI migrated @Input() → input(), @ViewChild() → viewChild(), and\n * @ContentChild() → contentChild(), properties that were plain class fields\n * became signal getters. Consumers who access them via @ViewChild need to\n * change `comp.prop` to `comp.prop()`.\n *\n * This schematic:\n * 1. Finds @ViewChild / @ContentChild fields typed as EUI component classes\n * 2. Resolves untyped @ViewChild('refName') by reading the component template\n * and matching #refName to an EUI element tag/selector\n * 3. Tracks which local variable names hold EUI component references (incl. aliases)\n * 4. Rewrites property accesses on those references: `ref.prop` → `ref.prop()`\n * (only for properties listed in the signal map)\n * 5. Does NOT touch template bindings — those are unaffected by signal migrations\n */\nexport function migrateToSignals(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let fileCount = 0;\n let propCount = 0;\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n if (!path.endsWith('.ts')) return;\n // Skip spec files — test code may use different patterns\n if (path.endsWith('.spec.ts')) return;\n\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n\n // Fast bail: file must mention at least one EUI class name or have\n // ViewChild/ContentChild with string refs (which might reference EUI elements)\n if (!hasEuiReference(original) && !hasStringRefQuery(original)) return;\n\n const result = migrateTypeScript(original, path, tree, context); if (result.source !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would migrate ${result.editCount} signal property access(es) in ${path}`);\n } else {\n tree.overwrite(path, result.source);\n }\n fileCount++;\n propCount += result.editCount;\n }\n });\n\n context.logger.info(`Migrated ${propCount} property access(es) to signal getters in ${fileCount} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\n/** Quick check: does the file reference any EUI class name from the signal map? */\nfunction hasEuiReference(source: string): boolean {\n for (const className of Object.keys(EUI_SIGNAL_MAP)) {\n if (source.includes(className)) return true;\n }\n return false;\n}\n\n/** Quick check: does the file have @ViewChild('...') or @ContentChild('...') with string refs? */\nfunction hasStringRefQuery(source: string): boolean {\n return /(@ViewChild|@ContentChild|@ViewChildren|@ContentChildren)\\s*\\(\\s*['\"]/.test(source);\n}\n\ninterface MigrateResult {\n source: string;\n editCount: number;\n}\n\nfunction migrateTypeScript(source: string, filePath: string, tree: Tree, context: SchematicContext): MigrateResult {\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: Edit[] = [];\n\n // Phase 0: Build template ref map (#refName → EUI class name) from the component's template\n const templateRefMap = buildTemplateRefMap(sourceFile, source, filePath, tree);\n\n // Phase 1: Collect all variables/properties typed as EUI classes\n const euiRefs = collectEuiReferences(sourceFile, source, templateRefMap);\n\n if (euiRefs.size === 0) {\n return { source, editCount: 0 };\n }\n\n // Phase 2: Find property accesses on those references and rewrite signal props\n const visitNode = (node: ts.Node): void => {\n // Match: ref.prop (PropertyAccessExpression)\n if (ts.isPropertyAccessExpression(node)) {\n const rewrite = tryRewritePropertyAccess(node, euiRefs, sourceFile, source);\n if (rewrite) {\n edits.push(rewrite);\n }\n }\n ts.forEachChild(node, visitNode);\n };\n\n ts.forEachChild(sourceFile, visitNode);\n\n if (edits.length === 0) {\n return { source, editCount: 0 };\n }\n\n return {\n source: applyEdits(source, edits),\n editCount: edits.length,\n };\n}\n\n/**\n * Scans the source file for class properties or variables typed as EUI component classes.\n * Returns a map: localName → Set of signal property names.\n *\n * Handles:\n * @ViewChild(EuiXxxComponent) name: EuiXxxComponent;\n * @ViewChild(EuiXxxComponent) name!: EuiXxxComponent;\n * @ViewChild('ref') name: EuiXxxComponent;\n * @ContentChild(EuiXxxComponent) name: EuiXxxComponent;\n * @ViewChild('ref') name: ElementRef; (resolved via template #ref → EUI selector)\n * someVar: EuiXxxComponent; (typed class field, even without decorator)\n * const alias = this.viewChildRef; (alias tracking)\n */\nfunction collectEuiReferences(\n sourceFile: ts.SourceFile,\n source: string,\n templateRefMap: Map<string, string>,\n): Map<string, ReadonlySet<string>> {\n const refs = new Map<string, ReadonlySet<string>>();\n\n // Pass 1: collect directly-typed references\n const visitDirect = (node: ts.Node): void => {\n // Class property declarations\n if (ts.isPropertyDeclaration(node) && node.name && ts.isIdentifier(node.name)) {\n const euiClass = resolveEuiClassName(node, sourceFile, templateRefMap);\n if (euiClass && EUI_SIGNAL_MAP[euiClass]) {\n refs.set(node.name.text, EUI_SIGNAL_MAP[euiClass]);\n }\n }\n\n // Variable declarations (const x: EuiXxxComponent = ...)\n if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) {\n const typeName = getTypeAnnotationName(node.type);\n if (typeName && EUI_SIGNAL_MAP[typeName]) {\n refs.set(node.name.text, EUI_SIGNAL_MAP[typeName]);\n }\n }\n\n // Parameter declarations (method(ref: EuiXxxComponent))\n if (ts.isParameter(node) && ts.isIdentifier(node.name)) {\n const typeName = getTypeAnnotationName(node.type);\n if (typeName && EUI_SIGNAL_MAP[typeName]) {\n refs.set(node.name.text, EUI_SIGNAL_MAP[typeName]);\n }\n }\n\n ts.forEachChild(node, visitDirect);\n };\n\n ts.forEachChild(sourceFile, visitDirect);\n\n // Pass 2: track aliases — variables assigned from already-tracked references\n // e.g. const alias = this.viewChildRef; or const alias = someParam;\n const visitAlias = (node: ts.Node): void => {\n if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) {\n // Skip if already tracked (has explicit type annotation)\n if (refs.has(node.name.text)) {\n ts.forEachChild(node, visitAlias);\n return;\n }\n\n const aliasedRef = resolveRefName(node.initializer);\n if (aliasedRef && refs.has(aliasedRef)) {\n refs.set(node.name.text, refs.get(aliasedRef)!);\n }\n }\n\n ts.forEachChild(node, visitAlias);\n };\n\n ts.forEachChild(sourceFile, visitAlias);\n return refs;\n}\n\n/**\n * Resolves the EUI class name from a property declaration.\n * Checks: type annotation → decorator class argument → template ref map.\n */\nfunction resolveEuiClassName(\n node: ts.PropertyDeclaration,\n sourceFile: ts.SourceFile,\n templateRefMap: Map<string, string>,\n): string | null {\n // Check type annotation first\n const typeName = getTypeAnnotationName(node.type);\n if (typeName && EUI_SIGNAL_MAP[typeName]) {\n return typeName;\n }\n\n // Check generic type argument: ElementRef<EuiXxxComponent>\n if (node.type && ts.isTypeReferenceNode(node.type) && node.type.typeArguments?.length) {\n const genericArg = getTypeAnnotationName(node.type.typeArguments[0]);\n if (genericArg && EUI_SIGNAL_MAP[genericArg]) {\n return genericArg;\n }\n }\n\n // Check decorator arguments: @ViewChild(EuiXxxComponent) or @ViewChild('refName')\n if (node.modifiers) {\n for (const modifier of node.modifiers) {\n if (!ts.isDecorator(modifier)) continue;\n if (!ts.isCallExpression(modifier.expression)) continue;\n\n const decoratorName = getDecoratorName(modifier.expression);\n if (!decoratorName) continue;\n if (!['ViewChild', 'ViewChildren', 'ContentChild', 'ContentChildren'].includes(decoratorName)) continue;\n\n const args = modifier.expression.arguments;\n if (args.length > 0) {\n const firstArg = args[0];\n\n // @ViewChild(EuiXxxComponent) — class reference\n if (ts.isIdentifier(firstArg) && EUI_SIGNAL_MAP[firstArg.text]) {\n return firstArg.text;\n }\n\n // @ViewChild('refName') — string template ref, resolve via template\n if (ts.isStringLiteralLike(firstArg)) {\n const refName = firstArg.text;\n const euiClass = templateRefMap.get(refName);\n if (euiClass && EUI_SIGNAL_MAP[euiClass]) {\n return euiClass;\n }\n }\n }\n }\n }\n\n return null;\n}\n\nfunction getDecoratorName(callExpr: ts.CallExpression): string | null {\n if (ts.isIdentifier(callExpr.expression)) {\n return callExpr.expression.text;\n }\n return null;\n}\n\nfunction getTypeAnnotationName(typeNode: ts.TypeNode | undefined): string | null {\n if (!typeNode) return null;\n if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {\n return typeNode.typeName.text;\n }\n return null;\n}\n\n/**\n * Checks if a property access `ref.prop` should be rewritten to `ref.prop()`.\n *\n * We rewrite if:\n * - `ref` is tracked in euiRefs\n * - `prop` is in the signal set for that EUI class\n * - The access is NOT already a call expression (not `ref.prop()` or `ref.prop(args)`)\n * - The access is NOT a write target (not `ref.prop = value`)\n * - The access is NOT a template binding argument (those are unaffected)\n */\nfunction tryRewritePropertyAccess(\n node: ts.PropertyAccessExpression,\n euiRefs: Map<string, ReadonlySet<string>>,\n sourceFile: ts.SourceFile,\n source: string,\n): Edit | null {\n const propName = node.name.text;\n\n // Resolve the reference name — handle `this.ref.prop` and `ref.prop`\n const refName = resolveRefName(node.expression);\n if (!refName) return null;\n\n const signalProps = euiRefs.get(refName);\n if (!signalProps || !signalProps.has(propName)) return null;\n\n // Skip if parent is already a call expression on this property: ref.prop(...)\n if (ts.isCallExpression(node.parent) && node.parent.expression === node) {\n return null;\n }\n\n // Skip if this is the left side of an assignment: ref.prop = value\n if (ts.isBinaryExpression(node.parent) &&\n node.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken &&\n node.parent.left === node) {\n return null;\n }\n\n // Skip if this is a compound assignment: ref.prop += value, ref.prop &&= value, etc.\n if (ts.isBinaryExpression(node.parent) && isAssignmentOperator(node.parent.operatorToken.kind) && node.parent.left === node) {\n return null;\n }\n\n // Skip if this is used with delete: delete ref.prop\n if (ts.isDeleteExpression(node.parent) && node.parent.expression === node) {\n return null;\n }\n\n // Skip if inside a typeof: typeof ref.prop\n if (ts.isTypeOfExpression(node.parent) && node.parent.expression === node) {\n return null;\n }\n\n // Skip property access chains that go deeper: ref.prop.something → ref.prop().something\n // This IS correct — we still want to add ()\n\n // Rewrite: insert () after the property name\n const end = node.name.getEnd();\n return {\n start: end,\n end: end,\n replacement: '()',\n };\n}\n\nfunction isAssignmentOperator(kind: ts.SyntaxKind): boolean {\n return kind >= ts.SyntaxKind.FirstAssignment && kind <= ts.SyntaxKind.LastAssignment;\n}\n\n/**\n * Resolves a reference name from an expression.\n * Handles: `ref`, `this.ref`, `this._ref`\n */\nfunction resolveRefName(expr: ts.Expression): string | null {\n // Direct identifier: ref.prop\n if (ts.isIdentifier(expr)) {\n return expr.text;\n }\n\n // this.ref.prop\n if (ts.isPropertyAccessExpression(expr) && expr.expression.kind === ts.SyntaxKind.ThisKeyword) {\n return expr.name.text;\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Template ref resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Builds a map of template reference variable names (#ref) to EUI class names.\n *\n * Parses the component's template (inline or external) and finds elements like:\n * <eui-alert #myAlert> → myAlert → EuiAlertComponent\n * <input euiAutocomplete #ac> → ac → EuiAutocompleteComponent\n *\n * Returns Map<refName, euiClassName>.\n */\nfunction buildTemplateRefMap(\n sourceFile: ts.SourceFile,\n source: string,\n filePath: string,\n tree: Tree,\n): Map<string, string> {\n const refMap = new Map<string, string>();\n\n // Find @Component decorator\n const componentMeta = findComponentMetadata(sourceFile, source);\n if (!componentMeta) return refMap;\n\n let templateSource: string | null = null;\n\n if (componentMeta.inlineTemplate !== null) {\n templateSource = componentMeta.inlineTemplate;\n } else if (componentMeta.templateUrl) {\n // Resolve templateUrl relative to the TS file\n const dir = filePath.substring(0, filePath.lastIndexOf('/'));\n const templatePath = `${dir}/${componentMeta.templateUrl}`;\n const buffer = tree.read(templatePath);\n if (buffer) {\n templateSource = buffer.toString('utf-8');\n }\n }\n\n if (!templateSource) return refMap;\n\n // Parse the template and collect #refs\n try {\n const parsed = parseTemplate(templateSource, '', { preserveWhitespaces: true });\n collectTemplateRefs(parsed.nodes, refMap);\n } catch {\n // Template parse failure — silently skip (might be malformed or use third-party syntax)\n }\n\n return refMap;\n}\n\n/**\n * Extracts @Component metadata: templateUrl or inline template content.\n */\nfunction findComponentMetadata(\n sourceFile: ts.SourceFile,\n source: string,\n): { templateUrl: string | null; inlineTemplate: string | null } | null {\n let result: { templateUrl: string | null; inlineTemplate: string | null } | null = null;\n\n const visit = (node: ts.Node): void => {\n if (result) return;\n\n if (ts.isDecorator(node) && ts.isCallExpression(node.expression)) {\n const name = getDecoratorName(node.expression);\n if (name !== 'Component') {\n ts.forEachChild(node, visit);\n return;\n }\n\n const args = node.expression.arguments;\n if (args.length === 0 || !ts.isObjectLiteralExpression(args[0])) {\n ts.forEachChild(node, visit);\n return;\n }\n\n const meta: { templateUrl: string | null; inlineTemplate: string | null } = {\n templateUrl: null,\n inlineTemplate: null,\n };\n\n for (const prop of args[0].properties) {\n if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) continue;\n\n if (prop.name.text === 'templateUrl') {\n const init = unwrapExpression(prop.initializer);\n if (ts.isStringLiteralLike(init)) {\n meta.templateUrl = init.text;\n }\n }\n\n if (prop.name.text === 'template') {\n const init = unwrapExpression(prop.initializer);\n if (ts.isStringLiteralLike(init)) {\n meta.inlineTemplate = init.text;\n } else if (ts.isNoSubstitutionTemplateLiteral(init)) {\n meta.inlineTemplate = init.text;\n }\n }\n }\n\n result = meta;\n return;\n }\n\n ts.forEachChild(node, visit);\n };\n\n ts.forEachChild(sourceFile, visit);\n return result;\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\n/**\n * Recursively walks template AST nodes, collecting #refName → EUI class name mappings.\n *\n * For each element with a template reference (#ref), we try to resolve its EUI class:\n * 1. Match element tag name against EUI_SELECTOR_TO_CLASS (e.g. 'eui-alert' → EuiAlertComponent)\n * 2. Match element attributes against attribute selectors (e.g. [euiAutocomplete] → EuiAutocompleteComponent)\n */\nfunction collectTemplateRefs(nodes: TmplAstNode[], refMap: Map<string, string>): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.references && node.references.length > 0) {\n const euiClass = resolveElementToEuiClass(node);\n if (euiClass) {\n for (const ref of node.references) {\n refMap.set(ref.name, euiClass);\n }\n }\n }\n collectTemplateRefs(node.children, refMap);\n }\n }\n}\n\n/**\n * Given a template element, resolves it to an EUI class name by checking:\n * 1. Tag name: <eui-alert> → look up 'eui-alert' in EUI_SELECTOR_TO_CLASS\n * 2. Attribute selectors: <input euiAutocomplete> → look up 'input[euiAutocomplete]' or '[euiAutocomplete]'\n */\nfunction resolveElementToEuiClass(element: TmplAstElement): string | null {\n const tag = element.name;\n\n // Direct tag match: eui-alert, eui-dialog, etc.\n if (EUI_SELECTOR_TO_CLASS[tag]) {\n return EUI_SELECTOR_TO_CLASS[tag];\n }\n\n // Attribute selector match: check all attributes against known patterns\n const allAttrNames = [\n ...element.attributes.map((a) => a.name),\n ...element.inputs.map((i) => i.name),\n ];\n\n for (const attrName of allAttrNames) {\n // Try tag[attr] pattern first (e.g. 'button[euiButton]', 'input[euiAutocomplete]')\n const tagAttrSelector = `${tag}[${attrName}]`;\n if (EUI_SELECTOR_TO_CLASS[tagAttrSelector]) {\n return EUI_SELECTOR_TO_CLASS[tagAttrSelector];\n }\n\n // Try [attr] pattern (e.g. '[euiTooltip]', '[euiLoading]')\n const attrSelector = `[${attrName}]`;\n if (EUI_SELECTOR_TO_CLASS[attrSelector]) {\n return EUI_SELECTOR_TO_CLASS[attrSelector];\n }\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Edit helpers\n// ---------------------------------------------------------------------------\n\nfunction applyEdits(source: string, edits: Edit[]): string {\n const unique = deduplicateEdits(edits);\n let result = source;\n for (const edit of unique.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n\nfunction deduplicateEdits(edits: Edit[]): Edit[] {\n const seen = new Map<string, Edit>();\n for (const edit of edits) {\n const key = `${edit.start}:${edit.end}`;\n seen.set(key, edit);\n }\n return Array.from(seen.values());\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n",
|
|
1036
1036
|
"displayName": "Edit",
|
|
1037
1037
|
"properties": [
|
|
1038
1038
|
{
|
|
@@ -1044,7 +1044,7 @@
|
|
|
1044
1044
|
"indexKey": "",
|
|
1045
1045
|
"optional": false,
|
|
1046
1046
|
"description": "",
|
|
1047
|
-
"line":
|
|
1047
|
+
"line": 14,
|
|
1048
1048
|
"rawdescription": "\n"
|
|
1049
1049
|
},
|
|
1050
1050
|
{
|
|
@@ -1056,7 +1056,7 @@
|
|
|
1056
1056
|
"indexKey": "",
|
|
1057
1057
|
"optional": false,
|
|
1058
1058
|
"description": "",
|
|
1059
|
-
"line":
|
|
1059
|
+
"line": 15,
|
|
1060
1060
|
"rawdescription": "\n"
|
|
1061
1061
|
},
|
|
1062
1062
|
{
|
|
@@ -1068,7 +1068,7 @@
|
|
|
1068
1068
|
"indexKey": "",
|
|
1069
1069
|
"optional": false,
|
|
1070
1070
|
"description": "",
|
|
1071
|
-
"line":
|
|
1071
|
+
"line": 13,
|
|
1072
1072
|
"rawdescription": "\n"
|
|
1073
1073
|
}
|
|
1074
1074
|
],
|
|
@@ -2139,7 +2139,63 @@
|
|
|
2139
2139
|
},
|
|
2140
2140
|
{
|
|
2141
2141
|
"name": "Schema",
|
|
2142
|
-
"id": "interface-Schema-
|
|
2142
|
+
"id": "interface-Schema-9c5e016857e1416ac7bbb881973e644c0f578a53bd432bb951c1676ac3f2a6631bfe3344860346a081b841217278723e0bb0bf2fa35fb869de67cb0cc8d99849",
|
|
2143
|
+
"file": "packages/core/schematics/add-eui-imports/index.ts",
|
|
2144
|
+
"deprecated": false,
|
|
2145
|
+
"deprecationMessage": "",
|
|
2146
|
+
"type": "interface",
|
|
2147
|
+
"sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode, TmplAstTemplate } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\nimport { SELECTOR_MAP, SelectorEntry, getClassNamesForArray } from './selector-map';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n useClassArray?: boolean;\n}\n\ninterface ImportToAdd {\n /** The symbol to add to imports array (class name or array name for spread) */\n symbol: string;\n /** Whether this should be spread (...EUI_BUTTON) */\n isSpread: boolean;\n /** ES import path */\n importPath: string;\n}\n\nexport function addEuiImports(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n const useClassArray = options.useClassArray ?? false;\n let filesUpdated = 0;\n\n // Index NgModules for standalone:false support\n const ngModuleIndex = buildNgModuleIndex(tree, tree.getDir(scanPath || '/'));\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n if (!path.endsWith('.ts') || path.endsWith('.spec.ts')) return;\n\n const buffer = tree.read(path);\n if (!buffer) return;\n const source = buffer.toString('utf-8');\n\n const sourceFile = ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true);\n const components = findComponentDecorators(sourceFile);\n if (components.length === 0) return;\n\n let modified = false;\n\n for (const { decorator, className: componentClassName, isNonStandalone } of components) {\n const templateHtml = getTemplateContent(tree, path, decorator, source);\n if (!templateHtml) continue;\n\n const matched = matchSelectorsInTemplate(templateHtml);\n if (matched.length === 0) continue;\n\n const importsToAdd = resolveImports(matched, useClassArray);\n if (importsToAdd.length === 0) continue;\n\n if (isNonStandalone) {\n // Find the NgModule that declares this component and add imports there\n const moduleInfo = findDeclaringModule(ngModuleIndex, componentClassName);\n if (!moduleInfo) {\n context.logger.warn(`⚠ Could not find declaring NgModule for ${componentClassName} in ${path}`);\n continue;\n }\n const moduleBuffer = tree.read(moduleInfo.path);\n if (!moduleBuffer) continue;\n const moduleSource = moduleBuffer.toString('utf-8');\n const result = addImportsToFile(moduleSource, moduleInfo.path, moduleInfo.decoratorPos, importsToAdd, useClassArray);\n if (result !== moduleSource) {\n if (options.dryRun) {\n logDryRun(context, `Would add EUI imports to NgModule in ${moduleInfo.path} for component ${componentClassName}`);\n } else {\n tree.overwrite(moduleInfo.path, result);\n }\n modified = true;\n }\n } else {\n // Standalone component — add imports directly\n const currentSource = tree.read(path)!.toString('utf-8');\n const result = addImportsToFile(currentSource, path, decorator.getStart(), importsToAdd, useClassArray);\n if (result !== currentSource) {\n if (options.dryRun) {\n logDryRun(context, `Would add EUI imports to ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n modified = true;\n }\n }\n }\n\n if (modified) filesUpdated++;\n });\n\n context.logger.info(`add-eui-imports: ${filesUpdated} file(s) updated.`);\n if (options.dryRun) logDryRunNote(context);\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\n// --- Selector Matching ---\n\nfunction matchSelectorsInTemplate(html: string): SelectorEntry[] {\n const parsed = parseTemplate(html, '', { preserveWhitespaces: true });\n if (parsed.errors?.length) return [];\n\n const matched: SelectorEntry[] = [];\n visitTemplateNodes(parsed.nodes, matched);\n return matched;\n}\n\nfunction visitTemplateNodes(nodes: TmplAstNode[], matched: SelectorEntry[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n matchElement(node, matched);\n visitTemplateNodes(node.children, matched);\n } else if (node instanceof TmplAstTemplate) {\n visitTemplateNodes(node.children, matched);\n }\n }\n}\n\nfunction matchElement(element: TmplAstElement, matched: SelectorEntry[]): void {\n const tagName = element.name;\n const attrNames = new Set([\n ...element.attributes.map(a => a.name),\n ...element.inputs.map(i => i.name),\n ]);\n\n for (const entry of SELECTOR_MAP) {\n if (entry.element && entry.element !== tagName) continue;\n if (!entry.element && entry.attributes.length === 0) continue;\n if (!entry.attributes.every(attr => attrNames.has(attr))) continue;\n // If no element specified, at least one attribute must match on this element\n if (!entry.element && entry.attributes.length > 0 && !entry.attributes.some(attr => attrNames.has(attr))) continue;\n matched.push(entry);\n }\n}\n\n// --- Import Resolution ---\n\nfunction resolveImports(matched: SelectorEntry[], useClassArray: boolean): ImportToAdd[] {\n const seen = new Set<string>();\n const result: ImportToAdd[] = [];\n\n for (const entry of matched) {\n if (useClassArray && entry.classArray) {\n if (seen.has(entry.classArray)) continue;\n seen.add(entry.classArray);\n result.push({ symbol: entry.classArray, isSpread: true, importPath: entry.importPath });\n } else {\n if (seen.has(entry.className)) continue;\n seen.add(entry.className);\n result.push({ symbol: entry.className, isSpread: false, importPath: entry.importPath });\n }\n }\n\n return result;\n}\n\n// --- Template Extraction ---\n\nfunction getTemplateContent(tree: Tree, tsPath: string, decorator: ts.Decorator, source: string): string | null {\n const call = decorator.expression as ts.CallExpression;\n if (!call.arguments[0] || !ts.isObjectLiteralExpression(call.arguments[0])) return null;\n const metadata = call.arguments[0];\n\n for (const prop of metadata.properties) {\n if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) continue;\n if (prop.name.text === 'template') {\n const init = prop.initializer;\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n return init.text;\n }\n }\n if (prop.name.text === 'templateUrl') {\n if (ts.isStringLiteral(prop.initializer)) {\n const dir = tsPath.substring(0, tsPath.lastIndexOf('/'));\n const templateBuffer = tree.read(`${dir}/${prop.initializer.text}`);\n if (templateBuffer) return templateBuffer.toString('utf-8');\n }\n }\n }\n return null;\n}\n\n// --- Component Decorator Detection ---\n\ninterface ComponentInfo {\n decorator: ts.Decorator;\n className: string;\n isNonStandalone: boolean;\n}\n\nfunction findComponentDecorators(sourceFile: ts.SourceFile): ComponentInfo[] {\n const results: ComponentInfo[] = [];\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node) && node.name) {\n const decs = ts.getDecorators(node);\n if (decs) {\n for (const dec of decs) {\n if (ts.isCallExpression(dec.expression) && ts.isIdentifier(dec.expression.expression) && dec.expression.expression.text === 'Component') {\n const isNonStandalone = hasStandaloneFalse(dec);\n results.push({ decorator: dec, className: node.name.text, isNonStandalone });\n }\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n return results;\n}\n\nfunction hasStandaloneFalse(decorator: ts.Decorator): boolean {\n const call = decorator.expression as ts.CallExpression;\n if (!call.arguments[0] || !ts.isObjectLiteralExpression(call.arguments[0])) return false;\n for (const prop of call.arguments[0].properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'standalone') {\n return prop.initializer.kind === ts.SyntaxKind.FalseKeyword;\n }\n }\n return false;\n}\n\n// --- NgModule Index ---\n\ninterface NgModuleInfo {\n path: string;\n declarations: string[];\n decoratorPos: number;\n}\n\nfunction buildNgModuleIndex(tree: Tree, dir: DirEntry): NgModuleInfo[] {\n const modules: NgModuleInfo[] = [];\n\n visitDir(dir, (path) => {\n if (!path.endsWith('.ts') || path.endsWith('.spec.ts')) return;\n\n const buffer = tree.read(path);\n if (!buffer) return;\n const source = buffer.toString('utf-8');\n if (!source.includes('NgModule')) return;\n\n const sf = ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true);\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node)) {\n const decs = ts.getDecorators(node);\n if (decs) {\n for (const dec of decs) {\n if (ts.isCallExpression(dec.expression) && ts.isIdentifier(dec.expression.expression) && dec.expression.expression.text === 'NgModule') {\n const declarations = extractArrayProperty(dec, 'declarations', source);\n modules.push({ path, declarations, decoratorPos: dec.getStart() });\n }\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sf);\n });\n\n return modules;\n}\n\nfunction extractArrayProperty(decorator: ts.Decorator, propName: string, source: string): string[] {\n const call = decorator.expression as ts.CallExpression;\n if (!call.arguments[0] || !ts.isObjectLiteralExpression(call.arguments[0])) return [];\n for (const prop of call.arguments[0].properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === propName) {\n if (ts.isArrayLiteralExpression(prop.initializer)) {\n return prop.initializer.elements\n .filter(ts.isIdentifier)\n .map(id => id.text);\n }\n }\n }\n return [];\n}\n\nfunction findDeclaringModule(modules: NgModuleInfo[], componentClassName: string): NgModuleInfo | undefined {\n return modules.find(m => m.declarations.includes(componentClassName));\n}\n\n// --- Import Addition ---\n\nfunction addImportsToFile(source: string, filePath: string, decoratorStartHint: number, imports: ImportToAdd[], useClassArray: boolean): string {\n const sf = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);\n\n // Find the imports array in the decorator closest to decoratorStartHint\n const importsArrayInfo = findDecoratorImportsArray(sf, source, decoratorStartHint);\n if (!importsArrayInfo) return source;\n\n const { arrayNode, decoratorType } = importsArrayInfo;\n\n // Determine what's already in the imports array\n const existingSymbols = new Set<string>();\n const existingSpreads = new Set<string>();\n for (const el of arrayNode.elements) {\n if (ts.isSpreadElement(el) && ts.isIdentifier(el.expression)) {\n existingSpreads.add(el.expression.text);\n } else if (ts.isIdentifier(el)) {\n existingSymbols.add(el.text);\n }\n }\n\n // Filter out already-present imports and compute what to add/remove\n const toAdd: ImportToAdd[] = [];\n const toRemoveFromArray: string[] = []; // individual class names to consolidate\n\n for (const imp of imports) {\n if (imp.isSpread) {\n if (existingSpreads.has(imp.symbol)) continue; // Already has ...EUI_X\n toAdd.push(imp);\n // Consolidate: remove individual class names covered by this array\n if (useClassArray) {\n const coveredClasses = getClassNamesForArray(imp.symbol);\n for (const cls of coveredClasses) {\n if (existingSymbols.has(cls)) toRemoveFromArray.push(cls);\n }\n }\n } else {\n if (existingSymbols.has(imp.symbol)) continue;\n // Also skip if a spread already covers this class\n const coveringArray = imports.find(i => i.isSpread && getClassNamesForArray(i.symbol).includes(imp.symbol));\n if (coveringArray && (existingSpreads.has(coveringArray.symbol) || toAdd.some(a => a.symbol === coveringArray.symbol))) continue;\n toAdd.push(imp);\n }\n }\n\n if (toAdd.length === 0 && toRemoveFromArray.length === 0) return source;\n\n // Build new array content\n let result = source;\n result = updateDecoratorImportsArray(result, filePath, arrayNode, toAdd, toRemoveFromArray);\n\n // Add ES imports\n result = addEsImports(result, filePath, toAdd);\n\n // Remove consolidated class names from ES imports\n if (toRemoveFromArray.length > 0) {\n result = removeFromEsImports(result, filePath, toRemoveFromArray);\n }\n\n return result;\n}\n\ninterface ImportsArrayInfo {\n arrayNode: ts.ArrayLiteralExpression;\n decoratorType: 'Component' | 'NgModule';\n}\n\nfunction findDecoratorImportsArray(sf: ts.SourceFile, source: string, decoratorStartHint: number): ImportsArrayInfo | null {\n let found: ImportsArrayInfo | null = null;\n\n const visit = (node: ts.Node): void => {\n if (found) return;\n if (ts.isClassDeclaration(node)) {\n const decs = ts.getDecorators(node);\n if (!decs) return;\n for (const dec of decs) {\n if (!ts.isCallExpression(dec.expression)) continue;\n if (!ts.isIdentifier(dec.expression.expression)) continue;\n const decName = dec.expression.expression.text;\n if (decName !== 'Component' && decName !== 'NgModule') continue;\n if (Math.abs(dec.getStart() - decoratorStartHint) > 5) continue; // Match by position\n\n const metadata = dec.expression.arguments[0];\n if (!ts.isObjectLiteralExpression(metadata)) continue;\n\n for (const prop of metadata.properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'imports') {\n if (ts.isArrayLiteralExpression(prop.initializer)) {\n found = { arrayNode: prop.initializer, decoratorType: decName as 'Component' | 'NgModule' };\n return;\n }\n }\n }\n\n // No imports array found — create one\n if (!found && decName === 'Component') {\n // We need to add `imports: []` to the decorator\n // Insert after the last property\n const lastProp = metadata.properties[metadata.properties.length - 1];\n if (lastProp) {\n const insertPos = lastProp.getEnd();\n const indent = detectIndent(source, metadata.getStart());\n const insertion = `,\\n${indent} imports: []`;\n const newSource = source.slice(0, insertPos) + insertion + source.slice(insertPos);\n // Re-parse to get the array node\n const newSf = ts.createSourceFile('', newSource, ts.ScriptTarget.Latest, true);\n const newArray = findImportsArrayInSource(newSf);\n if (newArray) {\n // We can't return a node from a different source file in the general case.\n // Instead, we'll handle the \"no imports array\" case by adding it inline.\n found = null; // Will be handled separately\n }\n }\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sf);\n return found;\n}\n\nfunction findImportsArrayInSource(sf: ts.SourceFile): ts.ArrayLiteralExpression | null {\n let found: ts.ArrayLiteralExpression | null = null;\n const visit = (node: ts.Node): void => {\n if (found) return;\n if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && node.name.text === 'imports' && ts.isArrayLiteralExpression(node.initializer)) {\n found = node.initializer;\n }\n ts.forEachChild(node, visit);\n };\n visit(sf);\n return found;\n}\n\nfunction updateDecoratorImportsArray(source: string, filePath: string, arrayNode: ts.ArrayLiteralExpression, toAdd: ImportToAdd[], toRemove: string[]): string {\n const sf = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);\n\n // Rebuild the array content\n const existingElements: string[] = [];\n for (const el of arrayNode.elements) {\n const text = source.slice(el.getStart(sf), el.getEnd()).trim();\n // Check if this element should be removed (consolidation)\n if (ts.isIdentifier(el) && toRemove.includes(el.text)) continue;\n existingElements.push(text);\n }\n\n // Add new entries\n for (const imp of toAdd) {\n const entry = imp.isSpread ? `...${imp.symbol}` : imp.symbol;\n if (!existingElements.includes(entry)) {\n existingElements.push(entry);\n }\n }\n\n // Determine formatting\n const arrayStart = arrayNode.getStart(sf);\n const arrayEnd = arrayNode.getEnd();\n const originalText = source.slice(arrayStart, arrayEnd);\n const isMultiline = originalText.includes('\\n');\n\n let newArrayText: string;\n if (isMultiline || existingElements.length > 3) {\n const indent = detectIndent(source, arrayStart);\n const itemIndent = indent + ' ';\n newArrayText = `[\\n${existingElements.map(e => `${itemIndent}${e},`).join('\\n')}\\n${indent}]`;\n } else {\n newArrayText = `[${existingElements.join(', ')}]`;\n }\n\n return source.slice(0, arrayStart) + newArrayText + source.slice(arrayEnd);\n}\n\nfunction addEsImports(source: string, filePath: string, imports: ImportToAdd[]): string {\n let result = source;\n\n // Group by import path\n const byPath = new Map<string, string[]>();\n for (const imp of imports) {\n const existing = byPath.get(imp.importPath) || [];\n existing.push(imp.symbol);\n byPath.set(imp.importPath, existing);\n }\n\n for (const [importPath, symbols] of byPath) {\n const sf = ts.createSourceFile(filePath, result, ts.ScriptTarget.Latest, true);\n\n // Check if there's already an import from this path\n const existingImport = sf.statements.find(\n (s): s is ts.ImportDeclaration =>\n ts.isImportDeclaration(s) && ts.isStringLiteral(s.moduleSpecifier) && s.moduleSpecifier.text === importPath,\n );\n\n if (existingImport?.importClause?.namedBindings && ts.isNamedImports(existingImport.importClause.namedBindings)) {\n // Extend existing import\n const namedBindings = existingImport.importClause.namedBindings;\n const existingNames = namedBindings.elements.map(el => el.name.text);\n const newNames = symbols.filter(s => !existingNames.includes(s));\n if (newNames.length === 0) continue;\n\n const allNames = [...existingNames, ...newNames].sort();\n const newClause = `{ ${allNames.join(', ')} }`;\n result = result.slice(0, namedBindings.getStart(sf)) + newClause + result.slice(namedBindings.getEnd());\n } else {\n // Add new import statement\n const sortedSymbols = [...symbols].sort();\n const newImport = `import { ${sortedSymbols.join(', ')} } from '${importPath}';\\n`;\n\n // Insert after the last existing import\n const lastImport = [...sf.statements].reverse().find(ts.isImportDeclaration);\n if (lastImport) {\n const pos = lastImport.getEnd();\n result = result.slice(0, pos) + '\\n' + newImport.trimEnd() + result.slice(pos);\n } else {\n result = newImport + result;\n }\n }\n }\n\n return result;\n}\n\nfunction detectIndent(source: string, pos: number): string {\n const lineStart = source.lastIndexOf('\\n', pos - 1) + 1;\n const match = source.slice(lineStart, pos).match(/^(\\s*)/);\n return match ? match[1] : '';\n}\n\nfunction removeFromEsImports(source: string, filePath: string, symbolsToRemove: string[]): string {\n let result = source;\n const sf = ts.createSourceFile(filePath, result, ts.ScriptTarget.Latest, true);\n\n for (const stmt of sf.statements) {\n if (!ts.isImportDeclaration(stmt) || !stmt.importClause?.namedBindings || !ts.isNamedImports(stmt.importClause.namedBindings)) continue;\n const namedBindings = stmt.importClause.namedBindings;\n const existingNames = namedBindings.elements.map(el => el.name.text);\n const remaining = existingNames.filter(n => !symbolsToRemove.includes(n));\n\n if (remaining.length === existingNames.length) continue; // Nothing to remove from this import\n\n if (remaining.length === 0) {\n // Remove the entire import statement\n result = result.slice(0, stmt.getStart(sf)) + result.slice(stmt.getEnd()).replace(/^\\r?\\n/, '');\n } else {\n const newClause = `{ ${remaining.join(', ')} }`;\n result = result.slice(0, namedBindings.getStart(sf)) + newClause + result.slice(namedBindings.getEnd());\n }\n break; // Only process the first matching import for the consolidated symbols\n }\n\n return result;\n}\n",
|
|
2148
|
+
"displayName": "Schema",
|
|
2149
|
+
"properties": [
|
|
2150
|
+
{
|
|
2151
|
+
"name": "dryRun",
|
|
2152
|
+
"coverageIgnore": false,
|
|
2153
|
+
"deprecated": false,
|
|
2154
|
+
"deprecationMessage": "",
|
|
2155
|
+
"type": "boolean",
|
|
2156
|
+
"indexKey": "",
|
|
2157
|
+
"optional": true,
|
|
2158
|
+
"description": "",
|
|
2159
|
+
"line": 9,
|
|
2160
|
+
"rawdescription": "\n"
|
|
2161
|
+
},
|
|
2162
|
+
{
|
|
2163
|
+
"name": "path",
|
|
2164
|
+
"coverageIgnore": false,
|
|
2165
|
+
"deprecated": false,
|
|
2166
|
+
"deprecationMessage": "",
|
|
2167
|
+
"type": "string",
|
|
2168
|
+
"indexKey": "",
|
|
2169
|
+
"optional": true,
|
|
2170
|
+
"description": "",
|
|
2171
|
+
"line": 8,
|
|
2172
|
+
"rawdescription": "\n"
|
|
2173
|
+
},
|
|
2174
|
+
{
|
|
2175
|
+
"name": "useClassArray",
|
|
2176
|
+
"coverageIgnore": false,
|
|
2177
|
+
"deprecated": false,
|
|
2178
|
+
"deprecationMessage": "",
|
|
2179
|
+
"type": "boolean",
|
|
2180
|
+
"indexKey": "",
|
|
2181
|
+
"optional": true,
|
|
2182
|
+
"description": "",
|
|
2183
|
+
"line": 10,
|
|
2184
|
+
"rawdescription": "\n"
|
|
2185
|
+
}
|
|
2186
|
+
],
|
|
2187
|
+
"indexSignatures": [],
|
|
2188
|
+
"kind": 172,
|
|
2189
|
+
"methods": [],
|
|
2190
|
+
"extends": [],
|
|
2191
|
+
"relationships": {
|
|
2192
|
+
"incoming": [],
|
|
2193
|
+
"outgoing": []
|
|
2194
|
+
}
|
|
2195
|
+
},
|
|
2196
|
+
{
|
|
2197
|
+
"name": "Schema",
|
|
2198
|
+
"id": "interface-Schema-4fe31ff3e9f1d34845a6b865d605e215f33552094b88c3d0eab0b180187fe64ce4d68d687516cb3d62c57d2678a103969b2dacbb18a49b26060f78096678fcce-1",
|
|
2143
2199
|
"file": "packages/core/schematics/fix-no-multiple-empty-lines/index.ts",
|
|
2144
2200
|
"deprecated": false,
|
|
2145
2201
|
"deprecationMessage": "",
|
|
@@ -2176,6 +2232,9 @@
|
|
|
2176
2232
|
"kind": 172,
|
|
2177
2233
|
"methods": [],
|
|
2178
2234
|
"extends": [],
|
|
2235
|
+
"isDuplicate": true,
|
|
2236
|
+
"duplicateId": 1,
|
|
2237
|
+
"duplicateName": "Schema-1",
|
|
2179
2238
|
"relationships": {
|
|
2180
2239
|
"incoming": [],
|
|
2181
2240
|
"outgoing": []
|
|
@@ -2183,7 +2242,7 @@
|
|
|
2183
2242
|
},
|
|
2184
2243
|
{
|
|
2185
2244
|
"name": "Schema",
|
|
2186
|
-
"id": "interface-Schema-5cd6db1920bd5b70a44c0b8a7f7e30f600bfd16a9950f218e6d451a1755ced95b462ef9a62ee87e39bcb8c392981b8d2597895bf0a272fd8aea71f03429ed976-
|
|
2245
|
+
"id": "interface-Schema-5cd6db1920bd5b70a44c0b8a7f7e30f600bfd16a9950f218e6d451a1755ced95b462ef9a62ee87e39bcb8c392981b8d2597895bf0a272fd8aea71f03429ed976-2",
|
|
2187
2246
|
"file": "packages/core/schematics/icon-migrate/schema.ts",
|
|
2188
2247
|
"deprecated": false,
|
|
2189
2248
|
"deprecationMessage": "",
|
|
@@ -2221,8 +2280,8 @@
|
|
|
2221
2280
|
"methods": [],
|
|
2222
2281
|
"extends": [],
|
|
2223
2282
|
"isDuplicate": true,
|
|
2224
|
-
"duplicateId":
|
|
2225
|
-
"duplicateName": "Schema-
|
|
2283
|
+
"duplicateId": 2,
|
|
2284
|
+
"duplicateName": "Schema-2",
|
|
2226
2285
|
"relationships": {
|
|
2227
2286
|
"incoming": [],
|
|
2228
2287
|
"outgoing": []
|
|
@@ -2230,7 +2289,7 @@
|
|
|
2230
2289
|
},
|
|
2231
2290
|
{
|
|
2232
2291
|
"name": "Schema",
|
|
2233
|
-
"id": "interface-Schema-56b9fe60701ca349dc90e152829b0a18bb7a8a9bb303c4bac05f9764cd2e933cce0879775ca17168b4c881e202d0258af3668a2a3a1b940e561f7e4b2349b5cc-
|
|
2292
|
+
"id": "interface-Schema-56b9fe60701ca349dc90e152829b0a18bb7a8a9bb303c4bac05f9764cd2e933cce0879775ca17168b4c881e202d0258af3668a2a3a1b940e561f7e4b2349b5cc-3",
|
|
2234
2293
|
"file": "packages/core/schematics/migrate/schema.ts",
|
|
2235
2294
|
"deprecated": false,
|
|
2236
2295
|
"deprecationMessage": "",
|
|
@@ -2280,8 +2339,8 @@
|
|
|
2280
2339
|
"methods": [],
|
|
2281
2340
|
"extends": [],
|
|
2282
2341
|
"isDuplicate": true,
|
|
2283
|
-
"duplicateId":
|
|
2284
|
-
"duplicateName": "Schema-
|
|
2342
|
+
"duplicateId": 3,
|
|
2343
|
+
"duplicateName": "Schema-3",
|
|
2285
2344
|
"relationships": {
|
|
2286
2345
|
"incoming": [],
|
|
2287
2346
|
"outgoing": []
|
|
@@ -2289,7 +2348,7 @@
|
|
|
2289
2348
|
},
|
|
2290
2349
|
{
|
|
2291
2350
|
"name": "Schema",
|
|
2292
|
-
"id": "interface-Schema-e96d29c1785a16147de773cebe7ab5d4ed82aa23c6b1f7737113252a14d156e236f46f7a16340432dec900b664c8005be0aef5d0a628c2f5b8dd2ec7e41edf56-
|
|
2351
|
+
"id": "interface-Schema-e96d29c1785a16147de773cebe7ab5d4ed82aa23c6b1f7737113252a14d156e236f46f7a16340432dec900b664c8005be0aef5d0a628c2f5b8dd2ec7e41edf56-4",
|
|
2293
2352
|
"file": "packages/core/schematics/migrate-eui-accent/index.ts",
|
|
2294
2353
|
"deprecated": false,
|
|
2295
2354
|
"deprecationMessage": "",
|
|
@@ -2327,8 +2386,8 @@
|
|
|
2327
2386
|
"methods": [],
|
|
2328
2387
|
"extends": [],
|
|
2329
2388
|
"isDuplicate": true,
|
|
2330
|
-
"duplicateId":
|
|
2331
|
-
"duplicateName": "Schema-
|
|
2389
|
+
"duplicateId": 4,
|
|
2390
|
+
"duplicateName": "Schema-4",
|
|
2332
2391
|
"relationships": {
|
|
2333
2392
|
"incoming": [],
|
|
2334
2393
|
"outgoing": []
|
|
@@ -2336,7 +2395,7 @@
|
|
|
2336
2395
|
},
|
|
2337
2396
|
{
|
|
2338
2397
|
"name": "Schema",
|
|
2339
|
-
"id": "interface-Schema-585b211d989563bbec5bd67bcdb58d5f264396adbe0ed2e51d0b4a0ecaf8b3ec36d821647c170d616bb409fc603c5a4c0e699eb7511e02c6add8d8670fc9cb9f-
|
|
2398
|
+
"id": "interface-Schema-585b211d989563bbec5bd67bcdb58d5f264396adbe0ed2e51d0b4a0ecaf8b3ec36d821647c170d616bb409fc603c5a4c0e699eb7511e02c6add8d8670fc9cb9f-5",
|
|
2340
2399
|
"file": "packages/core/schematics/migrate-eui-alert/index.ts",
|
|
2341
2400
|
"deprecated": false,
|
|
2342
2401
|
"deprecationMessage": "",
|
|
@@ -2374,8 +2433,8 @@
|
|
|
2374
2433
|
"methods": [],
|
|
2375
2434
|
"extends": [],
|
|
2376
2435
|
"isDuplicate": true,
|
|
2377
|
-
"duplicateId":
|
|
2378
|
-
"duplicateName": "Schema-
|
|
2436
|
+
"duplicateId": 5,
|
|
2437
|
+
"duplicateName": "Schema-5",
|
|
2379
2438
|
"relationships": {
|
|
2380
2439
|
"incoming": [],
|
|
2381
2440
|
"outgoing": []
|
|
@@ -2383,7 +2442,7 @@
|
|
|
2383
2442
|
},
|
|
2384
2443
|
{
|
|
2385
2444
|
"name": "Schema",
|
|
2386
|
-
"id": "interface-Schema-c7e2cc0add09c97d83ec2675d59c86951681f8e2d49f133fb2f216ca98d0da1fdf892d45af31f19ecbbfd0d32f4ad70dd99e862f75725d3812f66a73ec34362c-
|
|
2445
|
+
"id": "interface-Schema-c7e2cc0add09c97d83ec2675d59c86951681f8e2d49f133fb2f216ca98d0da1fdf892d45af31f19ecbbfd0d32f4ad70dd99e862f75725d3812f66a73ec34362c-6",
|
|
2387
2446
|
"file": "packages/core/schematics/migrate-eui-avatar/index.ts",
|
|
2388
2447
|
"deprecated": false,
|
|
2389
2448
|
"deprecationMessage": "",
|
|
@@ -2421,8 +2480,8 @@
|
|
|
2421
2480
|
"methods": [],
|
|
2422
2481
|
"extends": [],
|
|
2423
2482
|
"isDuplicate": true,
|
|
2424
|
-
"duplicateId":
|
|
2425
|
-
"duplicateName": "Schema-
|
|
2483
|
+
"duplicateId": 6,
|
|
2484
|
+
"duplicateName": "Schema-6",
|
|
2426
2485
|
"relationships": {
|
|
2427
2486
|
"incoming": [],
|
|
2428
2487
|
"outgoing": []
|
|
@@ -2430,12 +2489,12 @@
|
|
|
2430
2489
|
},
|
|
2431
2490
|
{
|
|
2432
2491
|
"name": "Schema",
|
|
2433
|
-
"id": "interface-Schema-
|
|
2434
|
-
"file": "packages/core/schematics/
|
|
2492
|
+
"id": "interface-Schema-c0c08f5e83da9afa13ce8375f982fb280b9bde854e00c914f41b04d44deecd531496c2cee264b50314e92a6260c26cebe35741a2cf5ec068d8b6da5c5267539e-7",
|
|
2493
|
+
"file": "packages/core/schematics/migrate-eui-button/index.ts",
|
|
2435
2494
|
"deprecated": false,
|
|
2436
2495
|
"deprecationMessage": "",
|
|
2437
2496
|
"type": "interface",
|
|
2438
|
-
"sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode, TmplAstTemplate } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\nimport { SELECTOR_MAP, SelectorEntry, getClassNamesForArray } from './selector-map';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n useClassArray?: boolean;\n}\n\ninterface ImportToAdd {\n /** The symbol to add to imports array (class name or array name for spread) */\n symbol: string;\n /** Whether this should be spread (...EUI_BUTTON) */\n isSpread: boolean;\n /** ES import path */\n importPath: string;\n}\n\nexport function addEuiImports(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n const useClassArray = options.useClassArray ?? false;\n let filesUpdated = 0;\n\n // Index NgModules for standalone:false support\n const ngModuleIndex = buildNgModuleIndex(tree, tree.getDir(scanPath || '/'));\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n if (!path.endsWith('.ts') || path.endsWith('.spec.ts')) return;\n\n const buffer = tree.read(path);\n if (!buffer) return;\n const source = buffer.toString('utf-8');\n\n const sourceFile = ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true);\n const components = findComponentDecorators(sourceFile);\n if (components.length === 0) return;\n\n let modified = false;\n\n for (const { decorator, className: componentClassName, isNonStandalone } of components) {\n const templateHtml = getTemplateContent(tree, path, decorator, source);\n if (!templateHtml) continue;\n\n const matched = matchSelectorsInTemplate(templateHtml);\n if (matched.length === 0) continue;\n\n const importsToAdd = resolveImports(matched, useClassArray);\n if (importsToAdd.length === 0) continue;\n\n if (isNonStandalone) {\n // Find the NgModule that declares this component and add imports there\n const moduleInfo = findDeclaringModule(ngModuleIndex, componentClassName);\n if (!moduleInfo) {\n context.logger.warn(`⚠ Could not find declaring NgModule for ${componentClassName} in ${path}`);\n continue;\n }\n const moduleBuffer = tree.read(moduleInfo.path);\n if (!moduleBuffer) continue;\n const moduleSource = moduleBuffer.toString('utf-8');\n const result = addImportsToFile(moduleSource, moduleInfo.path, moduleInfo.decoratorPos, importsToAdd, useClassArray);\n if (result !== moduleSource) {\n if (options.dryRun) {\n logDryRun(context, `Would add EUI imports to NgModule in ${moduleInfo.path} for component ${componentClassName}`);\n } else {\n tree.overwrite(moduleInfo.path, result);\n }\n modified = true;\n }\n } else {\n // Standalone component — add imports directly\n const currentSource = tree.read(path)!.toString('utf-8');\n const result = addImportsToFile(currentSource, path, decorator.getStart(), importsToAdd, useClassArray);\n if (result !== currentSource) {\n if (options.dryRun) {\n logDryRun(context, `Would add EUI imports to ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n modified = true;\n }\n }\n }\n\n if (modified) filesUpdated++;\n });\n\n context.logger.info(`add-eui-imports: ${filesUpdated} file(s) updated.`);\n if (options.dryRun) logDryRunNote(context);\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\n// --- Selector Matching ---\n\nfunction matchSelectorsInTemplate(html: string): SelectorEntry[] {\n const parsed = parseTemplate(html, '', { preserveWhitespaces: true });\n if (parsed.errors?.length) return [];\n\n const matched: SelectorEntry[] = [];\n visitTemplateNodes(parsed.nodes, matched);\n return matched;\n}\n\nfunction visitTemplateNodes(nodes: TmplAstNode[], matched: SelectorEntry[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n matchElement(node, matched);\n visitTemplateNodes(node.children, matched);\n } else if (node instanceof TmplAstTemplate) {\n visitTemplateNodes(node.children, matched);\n }\n }\n}\n\nfunction matchElement(element: TmplAstElement, matched: SelectorEntry[]): void {\n const tagName = element.name;\n const attrNames = new Set([\n ...element.attributes.map(a => a.name),\n ...element.inputs.map(i => i.name),\n ]);\n\n for (const entry of SELECTOR_MAP) {\n if (entry.element && entry.element !== tagName) continue;\n if (!entry.element && entry.attributes.length === 0) continue;\n if (!entry.attributes.every(attr => attrNames.has(attr))) continue;\n // If no element specified, at least one attribute must match on this element\n if (!entry.element && entry.attributes.length > 0 && !entry.attributes.some(attr => attrNames.has(attr))) continue;\n matched.push(entry);\n }\n}\n\n// --- Import Resolution ---\n\nfunction resolveImports(matched: SelectorEntry[], useClassArray: boolean): ImportToAdd[] {\n const seen = new Set<string>();\n const result: ImportToAdd[] = [];\n\n for (const entry of matched) {\n if (useClassArray && entry.classArray) {\n if (seen.has(entry.classArray)) continue;\n seen.add(entry.classArray);\n result.push({ symbol: entry.classArray, isSpread: true, importPath: entry.importPath });\n } else {\n if (seen.has(entry.className)) continue;\n seen.add(entry.className);\n result.push({ symbol: entry.className, isSpread: false, importPath: entry.importPath });\n }\n }\n\n return result;\n}\n\n// --- Template Extraction ---\n\nfunction getTemplateContent(tree: Tree, tsPath: string, decorator: ts.Decorator, source: string): string | null {\n const call = decorator.expression as ts.CallExpression;\n if (!call.arguments[0] || !ts.isObjectLiteralExpression(call.arguments[0])) return null;\n const metadata = call.arguments[0];\n\n for (const prop of metadata.properties) {\n if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) continue;\n if (prop.name.text === 'template') {\n const init = prop.initializer;\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n return init.text;\n }\n }\n if (prop.name.text === 'templateUrl') {\n if (ts.isStringLiteral(prop.initializer)) {\n const dir = tsPath.substring(0, tsPath.lastIndexOf('/'));\n const templateBuffer = tree.read(`${dir}/${prop.initializer.text}`);\n if (templateBuffer) return templateBuffer.toString('utf-8');\n }\n }\n }\n return null;\n}\n\n// --- Component Decorator Detection ---\n\ninterface ComponentInfo {\n decorator: ts.Decorator;\n className: string;\n isNonStandalone: boolean;\n}\n\nfunction findComponentDecorators(sourceFile: ts.SourceFile): ComponentInfo[] {\n const results: ComponentInfo[] = [];\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node) && node.name) {\n const decs = ts.getDecorators(node);\n if (decs) {\n for (const dec of decs) {\n if (ts.isCallExpression(dec.expression) && ts.isIdentifier(dec.expression.expression) && dec.expression.expression.text === 'Component') {\n const isNonStandalone = hasStandaloneFalse(dec);\n results.push({ decorator: dec, className: node.name.text, isNonStandalone });\n }\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n return results;\n}\n\nfunction hasStandaloneFalse(decorator: ts.Decorator): boolean {\n const call = decorator.expression as ts.CallExpression;\n if (!call.arguments[0] || !ts.isObjectLiteralExpression(call.arguments[0])) return false;\n for (const prop of call.arguments[0].properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'standalone') {\n return prop.initializer.kind === ts.SyntaxKind.FalseKeyword;\n }\n }\n return false;\n}\n\n// --- NgModule Index ---\n\ninterface NgModuleInfo {\n path: string;\n declarations: string[];\n decoratorPos: number;\n}\n\nfunction buildNgModuleIndex(tree: Tree, dir: DirEntry): NgModuleInfo[] {\n const modules: NgModuleInfo[] = [];\n\n visitDir(dir, (path) => {\n if (!path.endsWith('.ts') || path.endsWith('.spec.ts')) return;\n\n const buffer = tree.read(path);\n if (!buffer) return;\n const source = buffer.toString('utf-8');\n if (!source.includes('NgModule')) return;\n\n const sf = ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true);\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node)) {\n const decs = ts.getDecorators(node);\n if (decs) {\n for (const dec of decs) {\n if (ts.isCallExpression(dec.expression) && ts.isIdentifier(dec.expression.expression) && dec.expression.expression.text === 'NgModule') {\n const declarations = extractArrayProperty(dec, 'declarations', source);\n modules.push({ path, declarations, decoratorPos: dec.getStart() });\n }\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sf);\n });\n\n return modules;\n}\n\nfunction extractArrayProperty(decorator: ts.Decorator, propName: string, source: string): string[] {\n const call = decorator.expression as ts.CallExpression;\n if (!call.arguments[0] || !ts.isObjectLiteralExpression(call.arguments[0])) return [];\n for (const prop of call.arguments[0].properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === propName) {\n if (ts.isArrayLiteralExpression(prop.initializer)) {\n return prop.initializer.elements\n .filter(ts.isIdentifier)\n .map(id => id.text);\n }\n }\n }\n return [];\n}\n\nfunction findDeclaringModule(modules: NgModuleInfo[], componentClassName: string): NgModuleInfo | undefined {\n return modules.find(m => m.declarations.includes(componentClassName));\n}\n\n// --- Import Addition ---\n\nfunction addImportsToFile(source: string, filePath: string, decoratorStartHint: number, imports: ImportToAdd[], useClassArray: boolean): string {\n const sf = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);\n\n // Find the imports array in the decorator closest to decoratorStartHint\n const importsArrayInfo = findDecoratorImportsArray(sf, source, decoratorStartHint);\n if (!importsArrayInfo) return source;\n\n const { arrayNode, decoratorType } = importsArrayInfo;\n\n // Determine what's already in the imports array\n const existingSymbols = new Set<string>();\n const existingSpreads = new Set<string>();\n for (const el of arrayNode.elements) {\n if (ts.isSpreadElement(el) && ts.isIdentifier(el.expression)) {\n existingSpreads.add(el.expression.text);\n } else if (ts.isIdentifier(el)) {\n existingSymbols.add(el.text);\n }\n }\n\n // Filter out already-present imports and compute what to add/remove\n const toAdd: ImportToAdd[] = [];\n const toRemoveFromArray: string[] = []; // individual class names to consolidate\n\n for (const imp of imports) {\n if (imp.isSpread) {\n if (existingSpreads.has(imp.symbol)) continue; // Already has ...EUI_X\n toAdd.push(imp);\n // Consolidate: remove individual class names covered by this array\n if (useClassArray) {\n const coveredClasses = getClassNamesForArray(imp.symbol);\n for (const cls of coveredClasses) {\n if (existingSymbols.has(cls)) toRemoveFromArray.push(cls);\n }\n }\n } else {\n if (existingSymbols.has(imp.symbol)) continue;\n // Also skip if a spread already covers this class\n const coveringArray = imports.find(i => i.isSpread && getClassNamesForArray(i.symbol).includes(imp.symbol));\n if (coveringArray && (existingSpreads.has(coveringArray.symbol) || toAdd.some(a => a.symbol === coveringArray.symbol))) continue;\n toAdd.push(imp);\n }\n }\n\n if (toAdd.length === 0 && toRemoveFromArray.length === 0) return source;\n\n // Build new array content\n let result = source;\n result = updateDecoratorImportsArray(result, filePath, arrayNode, toAdd, toRemoveFromArray);\n\n // Add ES imports\n result = addEsImports(result, filePath, toAdd);\n\n // Remove consolidated class names from ES imports\n if (toRemoveFromArray.length > 0) {\n result = removeFromEsImports(result, filePath, toRemoveFromArray);\n }\n\n return result;\n}\n\ninterface ImportsArrayInfo {\n arrayNode: ts.ArrayLiteralExpression;\n decoratorType: 'Component' | 'NgModule';\n}\n\nfunction findDecoratorImportsArray(sf: ts.SourceFile, source: string, decoratorStartHint: number): ImportsArrayInfo | null {\n let found: ImportsArrayInfo | null = null;\n\n const visit = (node: ts.Node): void => {\n if (found) return;\n if (ts.isClassDeclaration(node)) {\n const decs = ts.getDecorators(node);\n if (!decs) return;\n for (const dec of decs) {\n if (!ts.isCallExpression(dec.expression)) continue;\n if (!ts.isIdentifier(dec.expression.expression)) continue;\n const decName = dec.expression.expression.text;\n if (decName !== 'Component' && decName !== 'NgModule') continue;\n if (Math.abs(dec.getStart() - decoratorStartHint) > 5) continue; // Match by position\n\n const metadata = dec.expression.arguments[0];\n if (!ts.isObjectLiteralExpression(metadata)) continue;\n\n for (const prop of metadata.properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'imports') {\n if (ts.isArrayLiteralExpression(prop.initializer)) {\n found = { arrayNode: prop.initializer, decoratorType: decName as 'Component' | 'NgModule' };\n return;\n }\n }\n }\n\n // No imports array found — create one\n if (!found && decName === 'Component') {\n // We need to add `imports: []` to the decorator\n // Insert after the last property\n const lastProp = metadata.properties[metadata.properties.length - 1];\n if (lastProp) {\n const insertPos = lastProp.getEnd();\n const indent = detectIndent(source, metadata.getStart());\n const insertion = `,\\n${indent} imports: []`;\n const newSource = source.slice(0, insertPos) + insertion + source.slice(insertPos);\n // Re-parse to get the array node\n const newSf = ts.createSourceFile('', newSource, ts.ScriptTarget.Latest, true);\n const newArray = findImportsArrayInSource(newSf);\n if (newArray) {\n // We can't return a node from a different source file in the general case.\n // Instead, we'll handle the \"no imports array\" case by adding it inline.\n found = null; // Will be handled separately\n }\n }\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sf);\n return found;\n}\n\nfunction findImportsArrayInSource(sf: ts.SourceFile): ts.ArrayLiteralExpression | null {\n let found: ts.ArrayLiteralExpression | null = null;\n const visit = (node: ts.Node): void => {\n if (found) return;\n if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && node.name.text === 'imports' && ts.isArrayLiteralExpression(node.initializer)) {\n found = node.initializer;\n }\n ts.forEachChild(node, visit);\n };\n visit(sf);\n return found;\n}\n\nfunction updateDecoratorImportsArray(source: string, filePath: string, arrayNode: ts.ArrayLiteralExpression, toAdd: ImportToAdd[], toRemove: string[]): string {\n const sf = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);\n\n // Rebuild the array content\n const existingElements: string[] = [];\n for (const el of arrayNode.elements) {\n const text = source.slice(el.getStart(sf), el.getEnd()).trim();\n // Check if this element should be removed (consolidation)\n if (ts.isIdentifier(el) && toRemove.includes(el.text)) continue;\n existingElements.push(text);\n }\n\n // Add new entries\n for (const imp of toAdd) {\n const entry = imp.isSpread ? `...${imp.symbol}` : imp.symbol;\n if (!existingElements.includes(entry)) {\n existingElements.push(entry);\n }\n }\n\n // Determine formatting\n const arrayStart = arrayNode.getStart(sf);\n const arrayEnd = arrayNode.getEnd();\n const originalText = source.slice(arrayStart, arrayEnd);\n const isMultiline = originalText.includes('\\n');\n\n let newArrayText: string;\n if (isMultiline || existingElements.length > 3) {\n const indent = detectIndent(source, arrayStart);\n const itemIndent = indent + ' ';\n newArrayText = `[\\n${existingElements.map(e => `${itemIndent}${e},`).join('\\n')}\\n${indent}]`;\n } else {\n newArrayText = `[${existingElements.join(', ')}]`;\n }\n\n return source.slice(0, arrayStart) + newArrayText + source.slice(arrayEnd);\n}\n\nfunction addEsImports(source: string, filePath: string, imports: ImportToAdd[]): string {\n let result = source;\n\n // Group by import path\n const byPath = new Map<string, string[]>();\n for (const imp of imports) {\n const existing = byPath.get(imp.importPath) || [];\n existing.push(imp.symbol);\n byPath.set(imp.importPath, existing);\n }\n\n for (const [importPath, symbols] of byPath) {\n const sf = ts.createSourceFile(filePath, result, ts.ScriptTarget.Latest, true);\n\n // Check if there's already an import from this path\n const existingImport = sf.statements.find(\n (s): s is ts.ImportDeclaration =>\n ts.isImportDeclaration(s) && ts.isStringLiteral(s.moduleSpecifier) && s.moduleSpecifier.text === importPath,\n );\n\n if (existingImport?.importClause?.namedBindings && ts.isNamedImports(existingImport.importClause.namedBindings)) {\n // Extend existing import\n const namedBindings = existingImport.importClause.namedBindings;\n const existingNames = namedBindings.elements.map(el => el.name.text);\n const newNames = symbols.filter(s => !existingNames.includes(s));\n if (newNames.length === 0) continue;\n\n const allNames = [...existingNames, ...newNames].sort();\n const newClause = `{ ${allNames.join(', ')} }`;\n result = result.slice(0, namedBindings.getStart(sf)) + newClause + result.slice(namedBindings.getEnd());\n } else {\n // Add new import statement\n const sortedSymbols = [...symbols].sort();\n const newImport = `import { ${sortedSymbols.join(', ')} } from '${importPath}';\\n`;\n\n // Insert after the last existing import\n const lastImport = [...sf.statements].reverse().find(ts.isImportDeclaration);\n if (lastImport) {\n const pos = lastImport.getEnd();\n result = result.slice(0, pos) + '\\n' + newImport.trimEnd() + result.slice(pos);\n } else {\n result = newImport + result;\n }\n }\n }\n\n return result;\n}\n\nfunction detectIndent(source: string, pos: number): string {\n const lineStart = source.lastIndexOf('\\n', pos - 1) + 1;\n const match = source.slice(lineStart, pos).match(/^(\\s*)/);\n return match ? match[1] : '';\n}\n\nfunction removeFromEsImports(source: string, filePath: string, symbolsToRemove: string[]): string {\n let result = source;\n const sf = ts.createSourceFile(filePath, result, ts.ScriptTarget.Latest, true);\n\n for (const stmt of sf.statements) {\n if (!ts.isImportDeclaration(stmt) || !stmt.importClause?.namedBindings || !ts.isNamedImports(stmt.importClause.namedBindings)) continue;\n const namedBindings = stmt.importClause.namedBindings;\n const existingNames = namedBindings.elements.map(el => el.name.text);\n const remaining = existingNames.filter(n => !symbolsToRemove.includes(n));\n\n if (remaining.length === existingNames.length) continue; // Nothing to remove from this import\n\n if (remaining.length === 0) {\n // Remove the entire import statement\n result = result.slice(0, stmt.getStart(sf)) + result.slice(stmt.getEnd()).replace(/^\\r?\\n/, '');\n } else {\n const newClause = `{ ${remaining.join(', ')} }`;\n result = result.slice(0, namedBindings.getStart(sf)) + newClause + result.slice(namedBindings.getEnd());\n }\n break; // Only process the first matching import for the consolidated symbols\n }\n\n return result;\n}\n",
|
|
2497
|
+
"sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst OLD_NAME = 'euiButtonCall';\nconst NEW_NAME = 'euiCTAButton';\n\nexport function migrateEuiButton(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(OLD_NAME)) return;\n\n let result: string;\n\n if (path.endsWith('.html')) {\n result = migrateTemplate(original);\n } else {\n result = migrateInlineTemplates(original);\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would rename '${OLD_NAME}' → '${NEW_NAME}' in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n\n // Warn about TS property access usages\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts')) {\n if (!original.includes(OLD_NAME)) return;\n\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === OLD_NAME) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(`${path}:${line + 1} - \"${OLD_NAME}\" has been renamed to \"${NEW_NAME}\". Update this reference manually.`);\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n });\n\n context.logger.info(`Renamed '${OLD_NAME}' → '${NEW_NAME}' on elements with euiButton in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: { start: number; end: number; replacement: string }[] = [];\n\n visitNodes(parsed.nodes, edits);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: { start: number; end: number; text: string }[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(OLD_NAME)) {\n ts.forEachChild(node, visit); return; \n}\n const migrated = migrateTemplate(rawTemplate);\n if (migrated !== rawTemplate) changes.push({ start, end, text: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n let result = source;\n for (const change of changes.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, change.start) + change.text + result.slice(change.end);\n }\n return result;\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction hasEuiButtonAttribute(element: TmplAstElement): boolean {\n return element.attributes.some((a) => a.name === 'euiButton') ||\n element.inputs.some((i) => i.name === 'euiButton');\n}\n\nfunction visitNodes(nodes: TmplAstNode[], edits: { start: number; end: number; replacement: string }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (hasEuiButtonAttribute(node)) collectRenames(node, edits);\n visitNodes(node.children, edits);\n }\n }\n}\n\nfunction collectRenames(element: TmplAstElement, edits: { start: number; end: number; replacement: string }[]): void {\n for (const attr of element.attributes) {\n if (attr.name === OLD_NAME) {\n edits.push({ start: attr.keySpan!.start.offset, end: attr.keySpan!.end.offset, replacement: NEW_NAME });\n }\n }\n for (const input of element.inputs) {\n if (input.name === OLD_NAME) {\n edits.push({ start: input.keySpan!.start.offset, end: input.keySpan!.end.offset, replacement: NEW_NAME });\n }\n }\n}\n\nfunction applyEdits(source: string, edits: { start: number; end: number; replacement: string }[]): string {\n let result = source;\n for (const edit of edits.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n",
|
|
2439
2498
|
"displayName": "Schema",
|
|
2440
2499
|
"properties": [
|
|
2441
2500
|
{
|
|
@@ -2447,7 +2506,7 @@
|
|
|
2447
2506
|
"indexKey": "",
|
|
2448
2507
|
"optional": true,
|
|
2449
2508
|
"description": "",
|
|
2450
|
-
"line":
|
|
2509
|
+
"line": 8,
|
|
2451
2510
|
"rawdescription": "\n"
|
|
2452
2511
|
},
|
|
2453
2512
|
{
|
|
@@ -2459,11 +2518,81 @@
|
|
|
2459
2518
|
"indexKey": "",
|
|
2460
2519
|
"optional": true,
|
|
2461
2520
|
"description": "",
|
|
2521
|
+
"line": 7,
|
|
2522
|
+
"rawdescription": "\n"
|
|
2523
|
+
}
|
|
2524
|
+
],
|
|
2525
|
+
"indexSignatures": [],
|
|
2526
|
+
"kind": 172,
|
|
2527
|
+
"methods": [],
|
|
2528
|
+
"extends": [],
|
|
2529
|
+
"isDuplicate": true,
|
|
2530
|
+
"duplicateId": 7,
|
|
2531
|
+
"duplicateName": "Schema-7",
|
|
2532
|
+
"relationships": {
|
|
2533
|
+
"incoming": [],
|
|
2534
|
+
"outgoing": []
|
|
2535
|
+
}
|
|
2536
|
+
},
|
|
2537
|
+
{
|
|
2538
|
+
"name": "Schema",
|
|
2539
|
+
"id": "interface-Schema-39d3d52e788050bebc4e0b99ed0756f7e9727556cb090ae8796bd4261a56463fe1f479c322b23028cd4134d71e4d1dc44cd248ac62a9a148b5fc46a665466f7c-8",
|
|
2540
|
+
"file": "packages/core/schematics/migrate-eui-chip/index.ts",
|
|
2541
|
+
"deprecated": false,
|
|
2542
|
+
"deprecationMessage": "",
|
|
2543
|
+
"type": "interface",
|
|
2544
|
+
"sourceCode": "import { parseTemplate, TmplAstBoundAttribute, TmplAstElement, TmplAstNode, TmplAstTextAttribute } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst REMOVED_INPUTS = new Set(['isSquared']);\n\nexport function migrateEuiChip(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes('eui-chip') && !original.includes('euiChip')) return;\n\n const result = path.endsWith('.html')\n ? migrateTemplate(original)\n : migrateInlineTemplates(original);\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would remove 'isSquared' input in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n\n // Warn about TS usages inline\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts') && original.includes('isSquared')) {\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === 'isSquared') {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(`${path}:${line + 1} - \"isSquared\" is no longer a valid input on eui-chip. Remove this assignment.`);\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n });\n\n context.logger.info(`Removed deprecated eui-chip 'isSquared' input from ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const removals: { start: number; end: number }[] = [];\n\n visitNodes(parsed.nodes, removals);\n\n let result = source;\n for (const { start, end } of removals.sort((a, b) => b.start - a.start)) {\n let adjustedStart = start;\n while (adjustedStart > 0 && (result[adjustedStart - 1] === ' ' || result[adjustedStart - 1] === '\\t')) {\n adjustedStart--;\n }\n result = result.slice(0, adjustedStart) + result.slice(end);\n }\n\n return result;\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const templateRegex = /template\\s*:\\s*`([^`]*)`/gs;\n return source.replace(templateRegex, (match, templateContent: string) => {\n if (!templateContent.includes('eui-chip') && !templateContent.includes('euiChip')) return match;\n const migrated = migrateTemplate(templateContent);\n if (migrated === templateContent) return match;\n return match.replace(templateContent, migrated);\n });\n}\n\nfunction isChipElement(element: TmplAstElement): boolean {\n if (element.name === 'eui-chip') return true;\n return element.attributes.some((a) => a.name === 'euiChip');\n}\n\nfunction visitNodes(nodes: TmplAstNode[], removals: { start: number; end: number }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (isChipElement(node)) collectRemovals(node, removals);\n visitNodes(node.children, removals);\n }\n }\n}\n\nfunction collectRemovals(element: TmplAstElement, removals: { start: number; end: number }[]): void {\n for (const attr of element.attributes) {\n if (REMOVED_INPUTS.has(attr.name)) {\n removals.push(getAttributeSpan(attr));\n }\n }\n for (const input of element.inputs) {\n if (REMOVED_INPUTS.has(input.name)) {\n removals.push(getAttributeSpan(input));\n }\n }\n}\n\nfunction getAttributeSpan(attr: TmplAstTextAttribute | TmplAstBoundAttribute): { start: number; end: number } {\n return { start: attr.sourceSpan.start.offset, end: attr.sourceSpan.end.offset };\n}\n",
|
|
2545
|
+
"displayName": "Schema",
|
|
2546
|
+
"properties": [
|
|
2547
|
+
{
|
|
2548
|
+
"name": "dryRun",
|
|
2549
|
+
"coverageIgnore": false,
|
|
2550
|
+
"deprecated": false,
|
|
2551
|
+
"deprecationMessage": "",
|
|
2552
|
+
"type": "boolean",
|
|
2553
|
+
"indexKey": "",
|
|
2554
|
+
"optional": true,
|
|
2555
|
+
"description": "",
|
|
2462
2556
|
"line": 8,
|
|
2463
2557
|
"rawdescription": "\n"
|
|
2464
2558
|
},
|
|
2465
2559
|
{
|
|
2466
|
-
"name": "
|
|
2560
|
+
"name": "path",
|
|
2561
|
+
"coverageIgnore": false,
|
|
2562
|
+
"deprecated": false,
|
|
2563
|
+
"deprecationMessage": "",
|
|
2564
|
+
"type": "string",
|
|
2565
|
+
"indexKey": "",
|
|
2566
|
+
"optional": true,
|
|
2567
|
+
"description": "",
|
|
2568
|
+
"line": 7,
|
|
2569
|
+
"rawdescription": "\n"
|
|
2570
|
+
}
|
|
2571
|
+
],
|
|
2572
|
+
"indexSignatures": [],
|
|
2573
|
+
"kind": 172,
|
|
2574
|
+
"methods": [],
|
|
2575
|
+
"extends": [],
|
|
2576
|
+
"isDuplicate": true,
|
|
2577
|
+
"duplicateId": 8,
|
|
2578
|
+
"duplicateName": "Schema-8",
|
|
2579
|
+
"relationships": {
|
|
2580
|
+
"incoming": [],
|
|
2581
|
+
"outgoing": []
|
|
2582
|
+
}
|
|
2583
|
+
},
|
|
2584
|
+
{
|
|
2585
|
+
"name": "Schema",
|
|
2586
|
+
"id": "interface-Schema-311bef7d5e4b40b356b0fdb5e35dd89e48a5c5beec29eeb865c22c2ca1c4c4fb1fd0bb4391ed0b33825a6d1d744bc3a2e9822b347fbc686a537e15b170c279dd-9",
|
|
2587
|
+
"file": "packages/core/schematics/migrate-eui-chip-list/index.ts",
|
|
2588
|
+
"deprecated": false,
|
|
2589
|
+
"deprecationMessage": "",
|
|
2590
|
+
"type": "interface",
|
|
2591
|
+
"sourceCode": "import { parseTemplate, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstElement, TmplAstNode, TmplAstTextAttribute } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\nconst CHIP_LIST_TAG = 'eui-chip-list';\nconst CHIP_LIST_ATTR = 'euiChipList';\nconst CHIP_TAG = 'eui-chip';\nconst CHIP_ATTR = 'euiChip';\n\nconst PROPAGATED_INPUTS = new Set([\n 'euiPrimary', 'euiSecondary', 'euiSuccess', 'euiInfo', 'euiWarning',\n 'euiDanger', 'euiAccent', 'euiVariant', 'euiSizeS', 'euiSizeVariant',\n 'euiOutline', 'euiDisabled',\n]);\n\nconst WARN_PROPERTIES = new Set([...PROPAGATED_INPUTS, 'chipRemove', 'isChipsRemovable', 'chipsLabelTruncateCount',\n 'maxVisibleChipsCount', 'isMaxVisibleChipsOpened', 'toggleLinkMoreLabel', 'toggleLinkLessLabel',\n 'isChipsSorted', 'chipsSortOrder']);\n\nconst REMOVED_INPUTS = new Set(['maxVisibleChipsCount', 'isMaxVisibleChipsOpened', 'toggleLinkMoreLabel', 'toggleLinkLessLabel', 'isChipsSorted', 'chipsSortOrder']);\n\nconst TRUNCATE_PIPE_IMPORT = 'EuiTruncatePipe';\nconst TRUNCATE_PIPE_PATH = '@eui/components/pipes';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\ninterface Edit {\n start: number;\n end: number;\n replacement: string;\n}\n\nexport function migrateEuiChipList(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n const filesNeedingTruncateImport = new Set<string>();\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(CHIP_LIST_TAG) && !original.includes(CHIP_LIST_ATTR)) return;\n\n let result: string;\n let addedTruncate = false;\n\n if (path.endsWith('.html')) {\n result = migrateTemplate(original);\n if (result !== original && result.includes('euiTruncate') && !original.includes('euiTruncate')) {\n // Find the associated .ts file\n const tsPath = path.replace(/\\.html$/, '.ts');\n if (tree.exists(tsPath)) {\n filesNeedingTruncateImport.add(tsPath);\n } else {\n // Try component naming convention\n const componentTsPath = path.replace(/\\.html$/, '.component.ts');\n if (tree.exists(componentTsPath)) {\n filesNeedingTruncateImport.add(componentTsPath);\n }\n }\n }\n } else {\n result = migrateInlineTemplates(original);\n if (result !== original && result.includes('euiTruncate') && !original.includes('euiTruncate')) {\n addedTruncate = true;\n }\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would move variant/size/outline inputs to child eui-chip in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n\n if (addedTruncate) {\n filesNeedingTruncateImport.add(path);\n }\n\n // Warn about TS usages of removed properties\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts')) {\n if ([...WARN_PROPERTIES].some((p) => original.includes(p))) {\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && WARN_PROPERTIES.has(node.name.text)) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${path}:${line + 1} - \"${node.name.text}\" has been removed from eui-chip-list. Move it to individual eui-chip elements.`,\n );\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n }\n });\n\n // Add EuiTruncatePipe import to component files that need it\n for (const tsPath of filesNeedingTruncateImport) {\n const buffer = tree.read(tsPath);\n if (!buffer) continue;\n const source = buffer.toString('utf-8');\n if (source.includes(TRUNCATE_PIPE_IMPORT)) continue;\n const result = addTruncatePipeImport(source, tsPath);\n if (result !== source) {\n if (!options.dryRun) {\n tree.overwrite(tsPath, result);\n }\n }\n }\n\n context.logger.info(`Migrated eui-chip-list inputs/outputs to child eui-chip in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction addTruncatePipeImport(source: string, filePath: string): string {\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);\n const edits: Edit[] = [];\n\n // 1. Add ES import statement for EuiTruncatePipe\n let hasEsImport = false;\n let lastImportEnd = 0;\n\n for (const stmt of sourceFile.statements) {\n if (ts.isImportDeclaration(stmt)) {\n lastImportEnd = stmt.getEnd();\n const moduleSpec = (stmt.moduleSpecifier as ts.StringLiteral).text;\n if (moduleSpec === TRUNCATE_PIPE_PATH) {\n const namedBindings = stmt.importClause?.namedBindings;\n if (namedBindings && ts.isNamedImports(namedBindings)) {\n if (namedBindings.elements.some((el) => el.name.text === TRUNCATE_PIPE_IMPORT)) {\n hasEsImport = true;\n } else {\n // Add to existing import from same path\n const lastEl = namedBindings.elements[namedBindings.elements.length - 1];\n edits.push({ start: lastEl.getEnd(), end: lastEl.getEnd(), replacement: `, ${TRUNCATE_PIPE_IMPORT}` });\n hasEsImport = true;\n }\n }\n }\n }\n }\n\n if (!hasEsImport) {\n const importStatement = `\\nimport { ${TRUNCATE_PIPE_IMPORT} } from '${TRUNCATE_PIPE_PATH}';`;\n edits.push({ start: lastImportEnd, end: lastImportEnd, replacement: importStatement });\n }\n\n // 2. Add EuiTruncatePipe to @Component imports array\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node)) {\n const decs = ts.getDecorators(node);\n if (!decs) return;\n for (const dec of decs) {\n if (!ts.isCallExpression(dec.expression) || !ts.isIdentifier(dec.expression.expression) || dec.expression.expression.text !== 'Component') continue;\n const metadata = dec.expression.arguments[0];\n if (!ts.isObjectLiteralExpression(metadata)) continue;\n for (const prop of metadata.properties) {\n if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name) || prop.name.text !== 'imports') continue;\n if (!ts.isArrayLiteralExpression(prop.initializer)) continue;\n const arr = prop.initializer;\n const arrText = source.slice(arr.getStart(sourceFile), arr.getEnd());\n if (arrText.includes(TRUNCATE_PIPE_IMPORT)) continue;\n if (arr.elements.length > 0) {\n const lastElement = arr.elements[arr.elements.length - 1];\n edits.push({ start: lastElement.getEnd(), end: lastElement.getEnd(), replacement: `,\\n ${TRUNCATE_PIPE_IMPORT}` });\n } else {\n edits.push({ start: arr.getStart(sourceFile) + 1, end: arr.getEnd() - 1, replacement: TRUNCATE_PIPE_IMPORT });\n }\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: Edit[] = [];\n\n visitNodes(parsed.nodes, source, edits);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: Edit[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(CHIP_LIST_TAG) && !rawTemplate.includes(CHIP_LIST_ATTR)) {\n ts.forEachChild(node, visit);\n return;\n }\n const migrated = migrateTemplate(rawTemplate);\n if (migrated !== rawTemplate) changes.push({ start, end, replacement: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n return applyEdits(source, changes);\n}\n\nfunction visitNodes(nodes: TmplAstNode[], source: string, edits: Edit[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (isChipListElement(node)) {\n migrateChipListElement(node, source, edits);\n }\n visitNodes(node.children, source, edits);\n }\n }\n}\n\nfunction isChipListElement(element: TmplAstElement): boolean {\n if (element.name === CHIP_LIST_TAG) return true;\n return element.attributes.some((a) => a.name === CHIP_LIST_ATTR);\n}\n\nfunction isChipElement(element: TmplAstElement): boolean {\n if (element.name === CHIP_TAG) return true;\n return element.attributes.some((a) => a.name === CHIP_ATTR);\n}\n\nfunction findChildChips(nodes: TmplAstNode[]): TmplAstElement[] {\n const chips: TmplAstElement[] = [];\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (isChipElement(node)) {\n chips.push(node);\n } else {\n chips.push(...findChildChips(node.children));\n }\n }\n }\n return chips;\n}\n\nfunction migrateChipListElement(element: TmplAstElement, source: string, edits: Edit[]): void {\n const childChips = findChildChips(element.children);\n const insertions: string[] = [];\n let truncateValue: string | null = null;\n\n // Collect and remove propagated static attributes\n for (const attr of element.attributes) {\n if (PROPAGATED_INPUTS.has(attr.name)) {\n edits.push(removalEdit(attr, source));\n insertions.push(attr.value ? `${attr.name}=\"${attr.value}\"` : attr.name);\n }\n if (attr.name === 'isChipsRemovable') {\n edits.push(removalEdit(attr, source));\n insertions.push('isChipRemovable');\n }\n if (attr.name === 'chipsLabelTruncateCount') {\n edits.push(removalEdit(attr, source));\n truncateValue = attr.value || null;\n }\n if (REMOVED_INPUTS.has(attr.name)) {\n edits.push(removalEdit(attr, source));\n }\n }\n\n // Collect and remove propagated bound inputs\n for (const input of element.inputs) {\n if (PROPAGATED_INPUTS.has(input.name)) {\n edits.push(removalEdit(input, source));\n const raw = source.slice(input.sourceSpan.start.offset, input.sourceSpan.end.offset);\n insertions.push(raw);\n }\n if (input.name === 'isChipsRemovable') {\n edits.push(removalEdit(input, source));\n const valueText = extractBindingValue(input, source);\n insertions.push(`[isChipRemovable]=\"${valueText}\"`);\n }\n if (input.name === 'chipsLabelTruncateCount') {\n edits.push(removalEdit(input, source));\n truncateValue = extractBindingValue(input, source);\n }\n if (REMOVED_INPUTS.has(input.name)) {\n edits.push(removalEdit(input, source));\n }\n }\n\n // Collect and remove (chipRemove) output\n let chipRemoveHandler: string | null = null;\n for (const output of element.outputs) {\n if (output.name === 'chipRemove') {\n edits.push(removalEdit(output, source));\n chipRemoveHandler = extractHandlerExpression(output, source);\n }\n }\n\n if (chipRemoveHandler) {\n insertions.push(`(remove)=\"${chipRemoveHandler}\"`);\n }\n\n // Add collected attributes to each child eui-chip\n if (insertions.length > 0) {\n for (const chip of childChips) {\n const existingNames = getExistingAttrNames(chip);\n const toInsert = insertions.filter((ins) => {\n const name = extractAttrName(ins);\n return !existingNames.has(name);\n });\n if (toInsert.length > 0) {\n const insertPos = chip.startSourceSpan.end.offset - 1;\n edits.push({ start: insertPos, end: insertPos, replacement: ' ' + toInsert.join(' ') });\n }\n }\n }\n\n // Add euiTruncate pipe to chip label content\n if (truncateValue) {\n for (const chip of childChips) {\n const labelEdit = buildTruncatePipeEdit(chip, source, truncateValue);\n if (labelEdit) edits.push(labelEdit);\n }\n }\n}\n\nfunction buildTruncatePipeEdit(chip: TmplAstElement, source: string, truncateValue: string): Edit | null {\n // Find <span euiLabel>...</span> inside the chip\n const labelEl = findLabelElement(chip.children);\n if (labelEl && labelEl.endSourceSpan) {\n const contentStart = labelEl.startSourceSpan.end.offset;\n const contentEnd = labelEl.endSourceSpan.start.offset;\n const content = source.slice(contentStart, contentEnd);\n if (content && !content.includes('euiTruncate')) {\n const trimmed = content.trim();\n const interpMatch = trimmed.match(/^\\{\\{\\s*(.+?)\\s*\\}\\}$/);\n if (interpMatch) {\n return { start: contentStart, end: contentEnd, replacement: `{{ ${interpMatch[1]} | euiTruncate: ${truncateValue} }}` };\n }\n return { start: contentStart, end: contentEnd, replacement: `{{ '${trimmed}' | euiTruncate: ${truncateValue} }}` };\n }\n }\n\n // Check direct text content inside chip (no label element)\n if (chip.endSourceSpan) {\n const chipContentStart = chip.startSourceSpan.end.offset;\n const chipContentEnd = chip.endSourceSpan.start.offset;\n const chipContent = source.slice(chipContentStart, chipContentEnd);\n const trimmed = chipContent.trim();\n const interpMatch = trimmed.match(/^\\{\\{\\s*(.+?)\\s*\\}\\}$/);\n if (interpMatch && !chipContent.includes('euiTruncate')) {\n return { start: chipContentStart, end: chipContentEnd, replacement: `{{ ${interpMatch[1]} | euiTruncate: ${truncateValue} }}` };\n }\n }\n return null;\n}\n\nfunction findLabelElement(nodes: TmplAstNode[]): TmplAstElement | null {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.attributes.some((a) => a.name === 'euiLabel')) return node;\n const nested = findLabelElement(node.children);\n if (nested) return nested;\n }\n }\n return null;\n}\n\nfunction getExistingAttrNames(element: TmplAstElement): Set<string> {\n const names = new Set<string>();\n for (const attr of element.attributes) names.add(attr.name);\n for (const input of element.inputs) names.add(input.name);\n for (const output of element.outputs) names.add(output.name);\n return names;\n}\n\nfunction extractAttrName(insertion: string): string {\n const outputMatch = insertion.match(/^\\(([^)]+)\\)/);\n if (outputMatch) return outputMatch[1];\n const inputMatch = insertion.match(/^\\[([^\\]]+)\\]/);\n if (inputMatch) return inputMatch[1];\n return insertion.split('=')[0];\n}\n\nfunction removalEdit(node: TmplAstTextAttribute | TmplAstBoundAttribute | TmplAstBoundEvent, source: string): Edit {\n let start = node.sourceSpan.start.offset;\n while (start > 0 && (source[start - 1] === ' ' || source[start - 1] === '\\t')) {\n start--;\n }\n return { start, end: node.sourceSpan.end.offset, replacement: '' };\n}\n\nfunction extractBindingValue(input: TmplAstBoundAttribute, source: string): string {\n const raw = source.slice(input.sourceSpan.start.offset, input.sourceSpan.end.offset);\n const match = raw.match(/=[\"']([^\"']*)[\"']/);\n return match ? match[1] : 'true';\n}\n\nfunction extractHandlerExpression(output: TmplAstBoundEvent, source: string): string {\n const raw = source.slice(output.sourceSpan.start.offset, output.sourceSpan.end.offset);\n const match = raw.match(/=[\"']([^\"']*)[\"']/);\n return match ? match[1] : '';\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction applyEdits(source: string, edits: Edit[]): string {\n const unique = deduplicateEdits(edits);\n let result = source;\n for (const edit of unique.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n\nfunction deduplicateEdits(edits: Edit[]): Edit[] {\n const seen = new Map<string, Edit>();\n for (const edit of edits) {\n seen.set(`${edit.start}:${edit.end}`, edit);\n }\n return Array.from(seen.values());\n}\n",
|
|
2592
|
+
"displayName": "Schema",
|
|
2593
|
+
"properties": [
|
|
2594
|
+
{
|
|
2595
|
+
"name": "dryRun",
|
|
2467
2596
|
"coverageIgnore": false,
|
|
2468
2597
|
"deprecated": false,
|
|
2469
2598
|
"deprecationMessage": "",
|
|
@@ -2471,7 +2600,19 @@
|
|
|
2471
2600
|
"indexKey": "",
|
|
2472
2601
|
"optional": true,
|
|
2473
2602
|
"description": "",
|
|
2474
|
-
"line":
|
|
2603
|
+
"line": 28,
|
|
2604
|
+
"rawdescription": "\n"
|
|
2605
|
+
},
|
|
2606
|
+
{
|
|
2607
|
+
"name": "path",
|
|
2608
|
+
"coverageIgnore": false,
|
|
2609
|
+
"deprecated": false,
|
|
2610
|
+
"deprecationMessage": "",
|
|
2611
|
+
"type": "string",
|
|
2612
|
+
"indexKey": "",
|
|
2613
|
+
"optional": true,
|
|
2614
|
+
"description": "",
|
|
2615
|
+
"line": 27,
|
|
2475
2616
|
"rawdescription": "\n"
|
|
2476
2617
|
}
|
|
2477
2618
|
],
|
|
@@ -2480,8 +2621,8 @@
|
|
|
2480
2621
|
"methods": [],
|
|
2481
2622
|
"extends": [],
|
|
2482
2623
|
"isDuplicate": true,
|
|
2483
|
-
"duplicateId":
|
|
2484
|
-
"duplicateName": "Schema-
|
|
2624
|
+
"duplicateId": 9,
|
|
2625
|
+
"duplicateName": "Schema-9",
|
|
2485
2626
|
"relationships": {
|
|
2486
2627
|
"incoming": [],
|
|
2487
2628
|
"outgoing": []
|
|
@@ -2489,12 +2630,12 @@
|
|
|
2489
2630
|
},
|
|
2490
2631
|
{
|
|
2491
2632
|
"name": "Schema",
|
|
2492
|
-
"id": "interface-Schema-
|
|
2493
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
2633
|
+
"id": "interface-Schema-869dfc324e9111966817cbebb3553eabfe200acfe33bb77efa71a6c46e1cba0ff5852de7b9ade3060f87264035b99591361331a9e0622daaac22b8d59146c761-10",
|
|
2634
|
+
"file": "packages/core/schematics/migrate-eui-discussion-thread/index.ts",
|
|
2494
2635
|
"deprecated": false,
|
|
2495
2636
|
"deprecationMessage": "",
|
|
2496
2637
|
"type": "interface",
|
|
2497
|
-
"sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst
|
|
2638
|
+
"sourceCode": "import { parseTemplate, TmplAstBoundAttribute, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst COMPONENT_TAG = 'eui-discussion-thread';\n\nexport function migrateEuiDiscussionThread(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(COMPONENT_TAG)) return;\n\n const result = path.endsWith('.html')\n ? migrateTemplate(original)\n : migrateInlineTemplates(original);\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would remove [trackBy] binding in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n\n // Warn about TS usages inline\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts') && original.includes('trackByFn')) {\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === 'trackByFn') {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(`${path}:${line + 1} - \"trackByFn\" has been removed from ${COMPONENT_TAG}. Remove this reference manually.`);\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n });\n\n context.logger.info(`Removed trackByFn-based [trackBy] bindings from ${COMPONENT_TAG} in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const removals: { start: number; end: number }[] = [];\n\n visitNodes(parsed.nodes, source, removals);\n\n let result = source;\n for (const { start, end } of removals.sort((a, b) => b.start - a.start)) {\n let adjustedStart = start;\n while (adjustedStart > 0 && (result[adjustedStart - 1] === ' ' || result[adjustedStart - 1] === '\\t')) {\n adjustedStart--;\n }\n result = result.slice(0, adjustedStart) + result.slice(end);\n }\n\n return result;\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const templateRegex = /template\\s*:\\s*`([^`]*)`/gs;\n return source.replace(templateRegex, (match, templateContent: string) => {\n if (!templateContent.includes(COMPONENT_TAG)) return match;\n const migrated = migrateTemplate(templateContent);\n if (migrated === templateContent) return match;\n return match.replace(templateContent, migrated);\n });\n}\n\nfunction visitNodes(nodes: TmplAstNode[], source: string, removals: { start: number; end: number }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === COMPONENT_TAG) collectRemovals(node, source, removals);\n visitNodes(node.children, source, removals);\n }\n }\n}\n\nfunction collectRemovals(element: TmplAstElement, source: string, removals: { start: number; end: number }[]): void {\n for (const input of element.inputs) {\n if (input.name === 'trackBy') {\n const valueSource = source.slice(input.sourceSpan.start.offset, input.sourceSpan.end.offset);\n if (valueSource.includes('trackByFn')) {\n removals.push({ start: input.sourceSpan.start.offset, end: input.sourceSpan.end.offset });\n }\n }\n }\n}\n",
|
|
2498
2639
|
"displayName": "Schema",
|
|
2499
2640
|
"properties": [
|
|
2500
2641
|
{
|
|
@@ -2527,8 +2668,8 @@
|
|
|
2527
2668
|
"methods": [],
|
|
2528
2669
|
"extends": [],
|
|
2529
2670
|
"isDuplicate": true,
|
|
2530
|
-
"duplicateId":
|
|
2531
|
-
"duplicateName": "Schema-
|
|
2671
|
+
"duplicateId": 10,
|
|
2672
|
+
"duplicateName": "Schema-10",
|
|
2532
2673
|
"relationships": {
|
|
2533
2674
|
"incoming": [],
|
|
2534
2675
|
"outgoing": []
|
|
@@ -2536,200 +2677,59 @@
|
|
|
2536
2677
|
},
|
|
2537
2678
|
{
|
|
2538
2679
|
"name": "Schema",
|
|
2539
|
-
"id": "interface-Schema-
|
|
2540
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
2680
|
+
"id": "interface-Schema-375dc0924084a2acbafe4a6a32577d59f631c9a386d151180d8fb1c89e7e7cd23da9fd459e597592ed823692adb6ad2633c50baf16621f003246e8c9bb1c6ce0-11",
|
|
2681
|
+
"file": "packages/core/schematics/migrate-eui-editor/index.ts",
|
|
2541
2682
|
"deprecated": false,
|
|
2542
2683
|
"deprecationMessage": "",
|
|
2543
2684
|
"type": "interface",
|
|
2544
|
-
"sourceCode": "import { parseTemplate,
|
|
2545
|
-
"displayName": "Schema",
|
|
2546
|
-
"properties": [
|
|
2547
|
-
{
|
|
2548
|
-
"name": "dryRun",
|
|
2549
|
-
"coverageIgnore": false,
|
|
2550
|
-
"deprecated": false,
|
|
2551
|
-
"deprecationMessage": "",
|
|
2552
|
-
"type": "boolean",
|
|
2553
|
-
"indexKey": "",
|
|
2554
|
-
"optional": true,
|
|
2555
|
-
"description": "",
|
|
2556
|
-
"line":
|
|
2557
|
-
"rawdescription": "\n"
|
|
2558
|
-
},
|
|
2559
|
-
{
|
|
2560
|
-
"name": "path",
|
|
2561
|
-
"coverageIgnore": false,
|
|
2562
|
-
"deprecated": false,
|
|
2563
|
-
"deprecationMessage": "",
|
|
2564
|
-
"type": "string",
|
|
2565
|
-
"indexKey": "",
|
|
2566
|
-
"optional": true,
|
|
2567
|
-
"description": "",
|
|
2568
|
-
"line":
|
|
2569
|
-
"rawdescription": "\n"
|
|
2570
|
-
}
|
|
2571
|
-
],
|
|
2572
|
-
"indexSignatures": [],
|
|
2573
|
-
"kind": 172,
|
|
2574
|
-
"methods": [],
|
|
2575
|
-
"extends": [],
|
|
2576
|
-
"isDuplicate": true,
|
|
2577
|
-
"duplicateId":
|
|
2578
|
-
"duplicateName": "Schema-
|
|
2579
|
-
"relationships": {
|
|
2580
|
-
"incoming": [],
|
|
2581
|
-
"outgoing": []
|
|
2582
|
-
}
|
|
2583
|
-
},
|
|
2584
|
-
{
|
|
2585
|
-
"name": "Schema",
|
|
2586
|
-
"id": "interface-Schema-
|
|
2587
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
2588
|
-
"deprecated": false,
|
|
2589
|
-
"deprecationMessage": "",
|
|
2590
|
-
"type": "interface",
|
|
2591
|
-
"sourceCode": "import { parseTemplate, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstElement, TmplAstNode, TmplAstTextAttribute } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\nconst CHIP_LIST_TAG = 'eui-chip-list';\nconst CHIP_LIST_ATTR = 'euiChipList';\nconst CHIP_TAG = 'eui-chip';\nconst CHIP_ATTR = 'euiChip';\n\nconst PROPAGATED_INPUTS = new Set([\n 'euiPrimary', 'euiSecondary', 'euiSuccess', 'euiInfo', 'euiWarning',\n 'euiDanger', 'euiAccent', 'euiVariant', 'euiSizeS', 'euiSizeVariant',\n 'euiOutline', 'euiDisabled',\n]);\n\nconst WARN_PROPERTIES = new Set([...PROPAGATED_INPUTS, 'chipRemove', 'isChipsRemovable', 'chipsLabelTruncateCount',\n 'maxVisibleChipsCount', 'isMaxVisibleChipsOpened', 'toggleLinkMoreLabel', 'toggleLinkLessLabel',\n 'isChipsSorted', 'chipsSortOrder']);\n\nconst REMOVED_INPUTS = new Set(['maxVisibleChipsCount', 'isMaxVisibleChipsOpened', 'toggleLinkMoreLabel', 'toggleLinkLessLabel', 'isChipsSorted', 'chipsSortOrder']);\n\nconst TRUNCATE_PIPE_IMPORT = 'EuiTruncatePipe';\nconst TRUNCATE_PIPE_PATH = '@eui/components/pipes';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\ninterface Edit {\n start: number;\n end: number;\n replacement: string;\n}\n\nexport function migrateEuiChipList(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n const filesNeedingTruncateImport = new Set<string>();\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(CHIP_LIST_TAG) && !original.includes(CHIP_LIST_ATTR)) return;\n\n let result: string;\n let addedTruncate = false;\n\n if (path.endsWith('.html')) {\n result = migrateTemplate(original);\n if (result !== original && result.includes('euiTruncate') && !original.includes('euiTruncate')) {\n // Find the associated .ts file\n const tsPath = path.replace(/\\.html$/, '.ts');\n if (tree.exists(tsPath)) {\n filesNeedingTruncateImport.add(tsPath);\n } else {\n // Try component naming convention\n const componentTsPath = path.replace(/\\.html$/, '.component.ts');\n if (tree.exists(componentTsPath)) {\n filesNeedingTruncateImport.add(componentTsPath);\n }\n }\n }\n } else {\n result = migrateInlineTemplates(original);\n if (result !== original && result.includes('euiTruncate') && !original.includes('euiTruncate')) {\n addedTruncate = true;\n }\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would move variant/size/outline inputs to child eui-chip in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n\n if (addedTruncate) {\n filesNeedingTruncateImport.add(path);\n }\n\n // Warn about TS usages of removed properties\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts')) {\n if ([...WARN_PROPERTIES].some((p) => original.includes(p))) {\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && WARN_PROPERTIES.has(node.name.text)) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${path}:${line + 1} - \"${node.name.text}\" has been removed from eui-chip-list. Move it to individual eui-chip elements.`,\n );\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n }\n });\n\n // Add EuiTruncatePipe import to component files that need it\n for (const tsPath of filesNeedingTruncateImport) {\n const buffer = tree.read(tsPath);\n if (!buffer) continue;\n const source = buffer.toString('utf-8');\n if (source.includes(TRUNCATE_PIPE_IMPORT)) continue;\n const result = addTruncatePipeImport(source, tsPath);\n if (result !== source) {\n if (!options.dryRun) {\n tree.overwrite(tsPath, result);\n }\n }\n }\n\n context.logger.info(`Migrated eui-chip-list inputs/outputs to child eui-chip in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction addTruncatePipeImport(source: string, filePath: string): string {\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);\n const edits: Edit[] = [];\n\n // 1. Add ES import statement for EuiTruncatePipe\n let hasEsImport = false;\n let lastImportEnd = 0;\n\n for (const stmt of sourceFile.statements) {\n if (ts.isImportDeclaration(stmt)) {\n lastImportEnd = stmt.getEnd();\n const moduleSpec = (stmt.moduleSpecifier as ts.StringLiteral).text;\n if (moduleSpec === TRUNCATE_PIPE_PATH) {\n const namedBindings = stmt.importClause?.namedBindings;\n if (namedBindings && ts.isNamedImports(namedBindings)) {\n if (namedBindings.elements.some((el) => el.name.text === TRUNCATE_PIPE_IMPORT)) {\n hasEsImport = true;\n } else {\n // Add to existing import from same path\n const lastEl = namedBindings.elements[namedBindings.elements.length - 1];\n edits.push({ start: lastEl.getEnd(), end: lastEl.getEnd(), replacement: `, ${TRUNCATE_PIPE_IMPORT}` });\n hasEsImport = true;\n }\n }\n }\n }\n }\n\n if (!hasEsImport) {\n const importStatement = `\\nimport { ${TRUNCATE_PIPE_IMPORT} } from '${TRUNCATE_PIPE_PATH}';`;\n edits.push({ start: lastImportEnd, end: lastImportEnd, replacement: importStatement });\n }\n\n // 2. Add EuiTruncatePipe to @Component imports array\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node)) {\n const decs = ts.getDecorators(node);\n if (!decs) return;\n for (const dec of decs) {\n if (!ts.isCallExpression(dec.expression) || !ts.isIdentifier(dec.expression.expression) || dec.expression.expression.text !== 'Component') continue;\n const metadata = dec.expression.arguments[0];\n if (!ts.isObjectLiteralExpression(metadata)) continue;\n for (const prop of metadata.properties) {\n if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name) || prop.name.text !== 'imports') continue;\n if (!ts.isArrayLiteralExpression(prop.initializer)) continue;\n const arr = prop.initializer;\n const arrText = source.slice(arr.getStart(sourceFile), arr.getEnd());\n if (arrText.includes(TRUNCATE_PIPE_IMPORT)) continue;\n if (arr.elements.length > 0) {\n const lastElement = arr.elements[arr.elements.length - 1];\n edits.push({ start: lastElement.getEnd(), end: lastElement.getEnd(), replacement: `,\\n ${TRUNCATE_PIPE_IMPORT}` });\n } else {\n edits.push({ start: arr.getStart(sourceFile) + 1, end: arr.getEnd() - 1, replacement: TRUNCATE_PIPE_IMPORT });\n }\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: Edit[] = [];\n\n visitNodes(parsed.nodes, source, edits);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: Edit[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(CHIP_LIST_TAG) && !rawTemplate.includes(CHIP_LIST_ATTR)) {\n ts.forEachChild(node, visit);\n return;\n }\n const migrated = migrateTemplate(rawTemplate);\n if (migrated !== rawTemplate) changes.push({ start, end, replacement: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n return applyEdits(source, changes);\n}\n\nfunction visitNodes(nodes: TmplAstNode[], source: string, edits: Edit[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (isChipListElement(node)) {\n migrateChipListElement(node, source, edits);\n }\n visitNodes(node.children, source, edits);\n }\n }\n}\n\nfunction isChipListElement(element: TmplAstElement): boolean {\n if (element.name === CHIP_LIST_TAG) return true;\n return element.attributes.some((a) => a.name === CHIP_LIST_ATTR);\n}\n\nfunction isChipElement(element: TmplAstElement): boolean {\n if (element.name === CHIP_TAG) return true;\n return element.attributes.some((a) => a.name === CHIP_ATTR);\n}\n\nfunction findChildChips(nodes: TmplAstNode[]): TmplAstElement[] {\n const chips: TmplAstElement[] = [];\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (isChipElement(node)) {\n chips.push(node);\n } else {\n chips.push(...findChildChips(node.children));\n }\n }\n }\n return chips;\n}\n\nfunction migrateChipListElement(element: TmplAstElement, source: string, edits: Edit[]): void {\n const childChips = findChildChips(element.children);\n const insertions: string[] = [];\n let truncateValue: string | null = null;\n\n // Collect and remove propagated static attributes\n for (const attr of element.attributes) {\n if (PROPAGATED_INPUTS.has(attr.name)) {\n edits.push(removalEdit(attr, source));\n insertions.push(attr.value ? `${attr.name}=\"${attr.value}\"` : attr.name);\n }\n if (attr.name === 'isChipsRemovable') {\n edits.push(removalEdit(attr, source));\n insertions.push('isChipRemovable');\n }\n if (attr.name === 'chipsLabelTruncateCount') {\n edits.push(removalEdit(attr, source));\n truncateValue = attr.value || null;\n }\n if (REMOVED_INPUTS.has(attr.name)) {\n edits.push(removalEdit(attr, source));\n }\n }\n\n // Collect and remove propagated bound inputs\n for (const input of element.inputs) {\n if (PROPAGATED_INPUTS.has(input.name)) {\n edits.push(removalEdit(input, source));\n const raw = source.slice(input.sourceSpan.start.offset, input.sourceSpan.end.offset);\n insertions.push(raw);\n }\n if (input.name === 'isChipsRemovable') {\n edits.push(removalEdit(input, source));\n const valueText = extractBindingValue(input, source);\n insertions.push(`[isChipRemovable]=\"${valueText}\"`);\n }\n if (input.name === 'chipsLabelTruncateCount') {\n edits.push(removalEdit(input, source));\n truncateValue = extractBindingValue(input, source);\n }\n if (REMOVED_INPUTS.has(input.name)) {\n edits.push(removalEdit(input, source));\n }\n }\n\n // Collect and remove (chipRemove) output\n let chipRemoveHandler: string | null = null;\n for (const output of element.outputs) {\n if (output.name === 'chipRemove') {\n edits.push(removalEdit(output, source));\n chipRemoveHandler = extractHandlerExpression(output, source);\n }\n }\n\n if (chipRemoveHandler) {\n insertions.push(`(remove)=\"${chipRemoveHandler}\"`);\n }\n\n // Add collected attributes to each child eui-chip\n if (insertions.length > 0) {\n for (const chip of childChips) {\n const existingNames = getExistingAttrNames(chip);\n const toInsert = insertions.filter((ins) => {\n const name = extractAttrName(ins);\n return !existingNames.has(name);\n });\n if (toInsert.length > 0) {\n const insertPos = chip.startSourceSpan.end.offset - 1;\n edits.push({ start: insertPos, end: insertPos, replacement: ' ' + toInsert.join(' ') });\n }\n }\n }\n\n // Add euiTruncate pipe to chip label content\n if (truncateValue) {\n for (const chip of childChips) {\n const labelEdit = buildTruncatePipeEdit(chip, source, truncateValue);\n if (labelEdit) edits.push(labelEdit);\n }\n }\n}\n\nfunction buildTruncatePipeEdit(chip: TmplAstElement, source: string, truncateValue: string): Edit | null {\n // Find <span euiLabel>...</span> inside the chip\n const labelEl = findLabelElement(chip.children);\n if (labelEl && labelEl.endSourceSpan) {\n const contentStart = labelEl.startSourceSpan.end.offset;\n const contentEnd = labelEl.endSourceSpan.start.offset;\n const content = source.slice(contentStart, contentEnd);\n if (content && !content.includes('euiTruncate')) {\n const trimmed = content.trim();\n const interpMatch = trimmed.match(/^\\{\\{\\s*(.+?)\\s*\\}\\}$/);\n if (interpMatch) {\n return { start: contentStart, end: contentEnd, replacement: `{{ ${interpMatch[1]} | euiTruncate: ${truncateValue} }}` };\n }\n return { start: contentStart, end: contentEnd, replacement: `{{ '${trimmed}' | euiTruncate: ${truncateValue} }}` };\n }\n }\n\n // Check direct text content inside chip (no label element)\n if (chip.endSourceSpan) {\n const chipContentStart = chip.startSourceSpan.end.offset;\n const chipContentEnd = chip.endSourceSpan.start.offset;\n const chipContent = source.slice(chipContentStart, chipContentEnd);\n const trimmed = chipContent.trim();\n const interpMatch = trimmed.match(/^\\{\\{\\s*(.+?)\\s*\\}\\}$/);\n if (interpMatch && !chipContent.includes('euiTruncate')) {\n return { start: chipContentStart, end: chipContentEnd, replacement: `{{ ${interpMatch[1]} | euiTruncate: ${truncateValue} }}` };\n }\n }\n return null;\n}\n\nfunction findLabelElement(nodes: TmplAstNode[]): TmplAstElement | null {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.attributes.some((a) => a.name === 'euiLabel')) return node;\n const nested = findLabelElement(node.children);\n if (nested) return nested;\n }\n }\n return null;\n}\n\nfunction getExistingAttrNames(element: TmplAstElement): Set<string> {\n const names = new Set<string>();\n for (const attr of element.attributes) names.add(attr.name);\n for (const input of element.inputs) names.add(input.name);\n for (const output of element.outputs) names.add(output.name);\n return names;\n}\n\nfunction extractAttrName(insertion: string): string {\n const outputMatch = insertion.match(/^\\(([^)]+)\\)/);\n if (outputMatch) return outputMatch[1];\n const inputMatch = insertion.match(/^\\[([^\\]]+)\\]/);\n if (inputMatch) return inputMatch[1];\n return insertion.split('=')[0];\n}\n\nfunction removalEdit(node: TmplAstTextAttribute | TmplAstBoundAttribute | TmplAstBoundEvent, source: string): Edit {\n let start = node.sourceSpan.start.offset;\n while (start > 0 && (source[start - 1] === ' ' || source[start - 1] === '\\t')) {\n start--;\n }\n return { start, end: node.sourceSpan.end.offset, replacement: '' };\n}\n\nfunction extractBindingValue(input: TmplAstBoundAttribute, source: string): string {\n const raw = source.slice(input.sourceSpan.start.offset, input.sourceSpan.end.offset);\n const match = raw.match(/=[\"']([^\"']*)[\"']/);\n return match ? match[1] : 'true';\n}\n\nfunction extractHandlerExpression(output: TmplAstBoundEvent, source: string): string {\n const raw = source.slice(output.sourceSpan.start.offset, output.sourceSpan.end.offset);\n const match = raw.match(/=[\"']([^\"']*)[\"']/);\n return match ? match[1] : '';\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction applyEdits(source: string, edits: Edit[]): string {\n const unique = deduplicateEdits(edits);\n let result = source;\n for (const edit of unique.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n\nfunction deduplicateEdits(edits: Edit[]): Edit[] {\n const seen = new Map<string, Edit>();\n for (const edit of edits) {\n seen.set(`${edit.start}:${edit.end}`, edit);\n }\n return Array.from(seen.values());\n}\n",
|
|
2592
|
-
"displayName": "Schema",
|
|
2593
|
-
"properties": [
|
|
2594
|
-
{
|
|
2595
|
-
"name": "dryRun",
|
|
2596
|
-
"coverageIgnore": false,
|
|
2597
|
-
"deprecated": false,
|
|
2598
|
-
"deprecationMessage": "",
|
|
2599
|
-
"type": "boolean",
|
|
2600
|
-
"indexKey": "",
|
|
2601
|
-
"optional": true,
|
|
2602
|
-
"description": "",
|
|
2603
|
-
"line": 28,
|
|
2604
|
-
"rawdescription": "\n"
|
|
2605
|
-
},
|
|
2606
|
-
{
|
|
2607
|
-
"name": "path",
|
|
2608
|
-
"coverageIgnore": false,
|
|
2609
|
-
"deprecated": false,
|
|
2610
|
-
"deprecationMessage": "",
|
|
2611
|
-
"type": "string",
|
|
2612
|
-
"indexKey": "",
|
|
2613
|
-
"optional": true,
|
|
2614
|
-
"description": "",
|
|
2615
|
-
"line": 27,
|
|
2616
|
-
"rawdescription": "\n"
|
|
2617
|
-
}
|
|
2618
|
-
],
|
|
2619
|
-
"indexSignatures": [],
|
|
2620
|
-
"kind": 172,
|
|
2621
|
-
"methods": [],
|
|
2622
|
-
"extends": [],
|
|
2623
|
-
"isDuplicate": true,
|
|
2624
|
-
"duplicateId": 9,
|
|
2625
|
-
"duplicateName": "Schema-9",
|
|
2626
|
-
"relationships": {
|
|
2627
|
-
"incoming": [],
|
|
2628
|
-
"outgoing": []
|
|
2629
|
-
}
|
|
2630
|
-
},
|
|
2631
|
-
{
|
|
2632
|
-
"name": "Schema",
|
|
2633
|
-
"id": "interface-Schema-869dfc324e9111966817cbebb3553eabfe200acfe33bb77efa71a6c46e1cba0ff5852de7b9ade3060f87264035b99591361331a9e0622daaac22b8d59146c761-10",
|
|
2634
|
-
"file": "packages/core/schematics/migrate-eui-discussion-thread/index.ts",
|
|
2635
|
-
"deprecated": false,
|
|
2636
|
-
"deprecationMessage": "",
|
|
2637
|
-
"type": "interface",
|
|
2638
|
-
"sourceCode": "import { parseTemplate, TmplAstBoundAttribute, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst COMPONENT_TAG = 'eui-discussion-thread';\n\nexport function migrateEuiDiscussionThread(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(COMPONENT_TAG)) return;\n\n const result = path.endsWith('.html')\n ? migrateTemplate(original)\n : migrateInlineTemplates(original);\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would remove [trackBy] binding in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n\n // Warn about TS usages inline\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts') && original.includes('trackByFn')) {\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === 'trackByFn') {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(`${path}:${line + 1} - \"trackByFn\" has been removed from ${COMPONENT_TAG}. Remove this reference manually.`);\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n });\n\n context.logger.info(`Removed trackByFn-based [trackBy] bindings from ${COMPONENT_TAG} in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const removals: { start: number; end: number }[] = [];\n\n visitNodes(parsed.nodes, source, removals);\n\n let result = source;\n for (const { start, end } of removals.sort((a, b) => b.start - a.start)) {\n let adjustedStart = start;\n while (adjustedStart > 0 && (result[adjustedStart - 1] === ' ' || result[adjustedStart - 1] === '\\t')) {\n adjustedStart--;\n }\n result = result.slice(0, adjustedStart) + result.slice(end);\n }\n\n return result;\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const templateRegex = /template\\s*:\\s*`([^`]*)`/gs;\n return source.replace(templateRegex, (match, templateContent: string) => {\n if (!templateContent.includes(COMPONENT_TAG)) return match;\n const migrated = migrateTemplate(templateContent);\n if (migrated === templateContent) return match;\n return match.replace(templateContent, migrated);\n });\n}\n\nfunction visitNodes(nodes: TmplAstNode[], source: string, removals: { start: number; end: number }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === COMPONENT_TAG) collectRemovals(node, source, removals);\n visitNodes(node.children, source, removals);\n }\n }\n}\n\nfunction collectRemovals(element: TmplAstElement, source: string, removals: { start: number; end: number }[]): void {\n for (const input of element.inputs) {\n if (input.name === 'trackBy') {\n const valueSource = source.slice(input.sourceSpan.start.offset, input.sourceSpan.end.offset);\n if (valueSource.includes('trackByFn')) {\n removals.push({ start: input.sourceSpan.start.offset, end: input.sourceSpan.end.offset });\n }\n }\n }\n}\n",
|
|
2639
|
-
"displayName": "Schema",
|
|
2640
|
-
"properties": [
|
|
2641
|
-
{
|
|
2642
|
-
"name": "dryRun",
|
|
2643
|
-
"coverageIgnore": false,
|
|
2644
|
-
"deprecated": false,
|
|
2645
|
-
"deprecationMessage": "",
|
|
2646
|
-
"type": "boolean",
|
|
2647
|
-
"indexKey": "",
|
|
2648
|
-
"optional": true,
|
|
2649
|
-
"description": "",
|
|
2650
|
-
"line": 8,
|
|
2651
|
-
"rawdescription": "\n"
|
|
2652
|
-
},
|
|
2653
|
-
{
|
|
2654
|
-
"name": "path",
|
|
2655
|
-
"coverageIgnore": false,
|
|
2656
|
-
"deprecated": false,
|
|
2657
|
-
"deprecationMessage": "",
|
|
2658
|
-
"type": "string",
|
|
2659
|
-
"indexKey": "",
|
|
2660
|
-
"optional": true,
|
|
2661
|
-
"description": "",
|
|
2662
|
-
"line": 7,
|
|
2663
|
-
"rawdescription": "\n"
|
|
2664
|
-
}
|
|
2665
|
-
],
|
|
2666
|
-
"indexSignatures": [],
|
|
2667
|
-
"kind": 172,
|
|
2668
|
-
"methods": [],
|
|
2669
|
-
"extends": [],
|
|
2670
|
-
"isDuplicate": true,
|
|
2671
|
-
"duplicateId": 10,
|
|
2672
|
-
"duplicateName": "Schema-10",
|
|
2673
|
-
"relationships": {
|
|
2674
|
-
"incoming": [],
|
|
2675
|
-
"outgoing": []
|
|
2676
|
-
}
|
|
2677
|
-
},
|
|
2678
|
-
{
|
|
2679
|
-
"name": "Schema",
|
|
2680
|
-
"id": "interface-Schema-375dc0924084a2acbafe4a6a32577d59f631c9a386d151180d8fb1c89e7e7cd23da9fd459e597592ed823692adb6ad2633c50baf16621f003246e8c9bb1c6ce0-11",
|
|
2681
|
-
"file": "packages/core/schematics/migrate-eui-editor/index.ts",
|
|
2682
|
-
"deprecated": false,
|
|
2683
|
-
"deprecationMessage": "",
|
|
2684
|
-
"type": "interface",
|
|
2685
|
-
"sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\nconst COMPONENT_TAG = 'eui-editor';\nconst OLD_NAME = 'onEditorChanged';\nconst NEW_NAME = 'contentChange';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nexport function migrateEuiEditor(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n\n if (path.endsWith('.html')) {\n if (!original.includes(COMPONENT_TAG)) return;\n const result = migrateTemplate(original);\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would rename '${OLD_NAME}' → '${NEW_NAME}' in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n return;\n }\n\n // .ts file — handle both migration and warnings in one pass\n const hasTag = original.includes(COMPONENT_TAG);\n const hasOldName = original.includes(OLD_NAME);\n if (!hasTag && !hasOldName) return;\n\n if (hasTag) {\n const result = migrateInlineTemplates(original);\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would rename '${OLD_NAME}' → '${NEW_NAME}' in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n }\n\n // Warn about TS property access usages (skip spec files)\n if (hasOldName && !path.endsWith('.spec.ts')) {\n warnPropertyAccesses(path, original, context);\n }\n });\n\n context.logger.info(`Renamed '(${OLD_NAME})' → '(${NEW_NAME})' on ${COMPONENT_TAG} in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: { start: number; end: number; replacement: string }[] = [];\n\n visitNodes(parsed.nodes, edits);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: { start: number; end: number; text: string }[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(COMPONENT_TAG)) {\n ts.forEachChild(node, visit); return;\n}\n const migrated = migrateTemplate(rawTemplate);\n if (migrated !== rawTemplate) changes.push({ start, end, text: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n let result = source;\n for (const change of changes.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, change.start) + change.text + result.slice(change.end);\n }\n return result;\n}\n\nfunction warnPropertyAccesses(path: string, source: string, context: SchematicContext): void {\n const sourceFile = ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === OLD_NAME) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(`${path}:${line + 1} - \"${OLD_NAME}\" has been renamed to \"${NEW_NAME}\" on ${COMPONENT_TAG}. Update this reference manually.`);\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction visitNodes(nodes: TmplAstNode[], edits: { start: number; end: number; replacement: string }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === COMPONENT_TAG) collectRenames(node, edits);\n visitNodes(node.children, edits);\n }\n }\n}\n\nfunction collectRenames(element: TmplAstElement, edits: { start: number; end: number; replacement: string }[]): void {\n for (const output of element.outputs) {\n if (output.name === OLD_NAME) {\n edits.push({ start: output.keySpan!.start.offset, end: output.keySpan!.end.offset, replacement: NEW_NAME });\n }\n }\n}\n\nfunction applyEdits(source: string, edits: { start: number; end: number; replacement: string }[]): string {\n let result = source;\n for (const edit of edits.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n",
|
|
2686
|
-
"displayName": "Schema",
|
|
2687
|
-
"properties": [
|
|
2688
|
-
{
|
|
2689
|
-
"name": "dryRun",
|
|
2690
|
-
"coverageIgnore": false,
|
|
2691
|
-
"deprecated": false,
|
|
2692
|
-
"deprecationMessage": "",
|
|
2693
|
-
"type": "boolean",
|
|
2694
|
-
"indexKey": "",
|
|
2695
|
-
"optional": true,
|
|
2696
|
-
"description": "",
|
|
2697
|
-
"line": 12,
|
|
2698
|
-
"rawdescription": "\n"
|
|
2699
|
-
},
|
|
2700
|
-
{
|
|
2701
|
-
"name": "path",
|
|
2702
|
-
"coverageIgnore": false,
|
|
2703
|
-
"deprecated": false,
|
|
2704
|
-
"deprecationMessage": "",
|
|
2705
|
-
"type": "string",
|
|
2706
|
-
"indexKey": "",
|
|
2707
|
-
"optional": true,
|
|
2708
|
-
"description": "",
|
|
2709
|
-
"line": 11,
|
|
2710
|
-
"rawdescription": "\n"
|
|
2711
|
-
}
|
|
2712
|
-
],
|
|
2713
|
-
"indexSignatures": [],
|
|
2714
|
-
"kind": 172,
|
|
2715
|
-
"methods": [],
|
|
2716
|
-
"extends": [],
|
|
2717
|
-
"isDuplicate": true,
|
|
2718
|
-
"duplicateId": 11,
|
|
2719
|
-
"duplicateName": "Schema-11",
|
|
2720
|
-
"relationships": {
|
|
2721
|
-
"incoming": [],
|
|
2722
|
-
"outgoing": []
|
|
2723
|
-
}
|
|
2724
|
-
},
|
|
2725
|
-
{
|
|
2726
|
-
"name": "Schema",
|
|
2727
|
-
"id": "interface-Schema-0ee574e11dd651dd971057cb66220845e5b87b2949c69c623c8ddf7355af92a396c58c237a52a05f18c22a5332a2695b717bcfeb8ea840c4e7df8d0084c2b49c-12",
|
|
2728
|
-
"file": "packages/core/schematics/migrate-eui-fieldset/index.ts",
|
|
2729
|
-
"deprecated": false,
|
|
2730
|
-
"deprecationMessage": "",
|
|
2731
|
-
"type": "interface",
|
|
2732
|
-
"sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst COMPONENT_TAG = 'eui-fieldset';\nconst OLD_NAME = 'iconSvgType';\nconst NEW_NAME = 'iconSvgName';\n\nexport function migrateEuiFieldset(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(COMPONENT_TAG)) return;\n\n let result: string;\n\n if (path.endsWith('.html')) {\n result = migrateTemplate(original);\n } else {\n result = migrateInlineTemplates(original);\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would rename '${OLD_NAME}' → '${NEW_NAME}' in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n\n // Warn about TS property access usages (merged from warnTsUsages)\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts') && original.includes(OLD_NAME)) {\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === OLD_NAME) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(`${path}:${line + 1} - \"${OLD_NAME}\" has been renamed to \"${NEW_NAME}\" on ${COMPONENT_TAG}. Update this reference manually.`);\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n });\n\n context.logger.info(`Renamed '${OLD_NAME}' → '${NEW_NAME}' on ${COMPONENT_TAG} in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: { start: number; end: number; replacement: string }[] = [];\n\n visitNodes(parsed.nodes, edits);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: { start: number; end: number; text: string }[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(COMPONENT_TAG)) {\n ts.forEachChild(node, visit); return; \n}\n const migrated = migrateTemplate(rawTemplate);\n if (migrated !== rawTemplate) changes.push({ start, end, text: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n let result = source;\n for (const change of changes.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, change.start) + change.text + result.slice(change.end);\n }\n return result;\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction visitNodes(nodes: TmplAstNode[], edits: { start: number; end: number; replacement: string }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === COMPONENT_TAG) collectRenames(node, edits);\n visitNodes(node.children, edits);\n }\n }\n}\n\nfunction collectRenames(element: TmplAstElement, edits: { start: number; end: number; replacement: string }[]): void {\n for (const attr of element.attributes) {\n if (attr.name === OLD_NAME) {\n edits.push({ start: attr.keySpan!.start.offset, end: attr.keySpan!.end.offset, replacement: NEW_NAME });\n }\n }\n for (const input of element.inputs) {\n if (input.name === OLD_NAME) {\n edits.push({ start: input.keySpan!.start.offset, end: input.keySpan!.end.offset, replacement: NEW_NAME });\n }\n }\n}\n\nfunction applyEdits(source: string, edits: { start: number; end: number; replacement: string }[]): string {\n let result = source;\n for (const edit of edits.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n",
|
|
2685
|
+
"sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\nconst COMPONENT_TAG = 'eui-editor';\nconst OLD_NAME = 'onEditorChanged';\nconst NEW_NAME = 'contentChange';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nexport function migrateEuiEditor(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n\n if (path.endsWith('.html')) {\n if (!original.includes(COMPONENT_TAG)) return;\n const result = migrateTemplate(original);\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would rename '${OLD_NAME}' → '${NEW_NAME}' in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n return;\n }\n\n // .ts file — handle both migration and warnings in one pass\n const hasTag = original.includes(COMPONENT_TAG);\n const hasOldName = original.includes(OLD_NAME);\n if (!hasTag && !hasOldName) return;\n\n if (hasTag) {\n const result = migrateInlineTemplates(original);\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would rename '${OLD_NAME}' → '${NEW_NAME}' in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n }\n\n // Warn about TS property access usages (skip spec files)\n if (hasOldName && !path.endsWith('.spec.ts')) {\n warnPropertyAccesses(path, original, context);\n }\n });\n\n context.logger.info(`Renamed '(${OLD_NAME})' → '(${NEW_NAME})' on ${COMPONENT_TAG} in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: { start: number; end: number; replacement: string }[] = [];\n\n visitNodes(parsed.nodes, edits);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: { start: number; end: number; text: string }[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(COMPONENT_TAG)) {\n ts.forEachChild(node, visit); return;\n}\n const migrated = migrateTemplate(rawTemplate);\n if (migrated !== rawTemplate) changes.push({ start, end, text: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n let result = source;\n for (const change of changes.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, change.start) + change.text + result.slice(change.end);\n }\n return result;\n}\n\nfunction warnPropertyAccesses(path: string, source: string, context: SchematicContext): void {\n const sourceFile = ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === OLD_NAME) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(`${path}:${line + 1} - \"${OLD_NAME}\" has been renamed to \"${NEW_NAME}\" on ${COMPONENT_TAG}. Update this reference manually.`);\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction visitNodes(nodes: TmplAstNode[], edits: { start: number; end: number; replacement: string }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === COMPONENT_TAG) collectRenames(node, edits);\n visitNodes(node.children, edits);\n }\n }\n}\n\nfunction collectRenames(element: TmplAstElement, edits: { start: number; end: number; replacement: string }[]): void {\n for (const output of element.outputs) {\n if (output.name === OLD_NAME) {\n edits.push({ start: output.keySpan!.start.offset, end: output.keySpan!.end.offset, replacement: NEW_NAME });\n }\n }\n}\n\nfunction applyEdits(source: string, edits: { start: number; end: number; replacement: string }[]): string {\n let result = source;\n for (const edit of edits.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n",
|
|
2686
|
+
"displayName": "Schema",
|
|
2687
|
+
"properties": [
|
|
2688
|
+
{
|
|
2689
|
+
"name": "dryRun",
|
|
2690
|
+
"coverageIgnore": false,
|
|
2691
|
+
"deprecated": false,
|
|
2692
|
+
"deprecationMessage": "",
|
|
2693
|
+
"type": "boolean",
|
|
2694
|
+
"indexKey": "",
|
|
2695
|
+
"optional": true,
|
|
2696
|
+
"description": "",
|
|
2697
|
+
"line": 12,
|
|
2698
|
+
"rawdescription": "\n"
|
|
2699
|
+
},
|
|
2700
|
+
{
|
|
2701
|
+
"name": "path",
|
|
2702
|
+
"coverageIgnore": false,
|
|
2703
|
+
"deprecated": false,
|
|
2704
|
+
"deprecationMessage": "",
|
|
2705
|
+
"type": "string",
|
|
2706
|
+
"indexKey": "",
|
|
2707
|
+
"optional": true,
|
|
2708
|
+
"description": "",
|
|
2709
|
+
"line": 11,
|
|
2710
|
+
"rawdescription": "\n"
|
|
2711
|
+
}
|
|
2712
|
+
],
|
|
2713
|
+
"indexSignatures": [],
|
|
2714
|
+
"kind": 172,
|
|
2715
|
+
"methods": [],
|
|
2716
|
+
"extends": [],
|
|
2717
|
+
"isDuplicate": true,
|
|
2718
|
+
"duplicateId": 11,
|
|
2719
|
+
"duplicateName": "Schema-11",
|
|
2720
|
+
"relationships": {
|
|
2721
|
+
"incoming": [],
|
|
2722
|
+
"outgoing": []
|
|
2723
|
+
}
|
|
2724
|
+
},
|
|
2725
|
+
{
|
|
2726
|
+
"name": "Schema",
|
|
2727
|
+
"id": "interface-Schema-0ee574e11dd651dd971057cb66220845e5b87b2949c69c623c8ddf7355af92a396c58c237a52a05f18c22a5332a2695b717bcfeb8ea840c4e7df8d0084c2b49c-12",
|
|
2728
|
+
"file": "packages/core/schematics/migrate-eui-fieldset/index.ts",
|
|
2729
|
+
"deprecated": false,
|
|
2730
|
+
"deprecationMessage": "",
|
|
2731
|
+
"type": "interface",
|
|
2732
|
+
"sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst COMPONENT_TAG = 'eui-fieldset';\nconst OLD_NAME = 'iconSvgType';\nconst NEW_NAME = 'iconSvgName';\n\nexport function migrateEuiFieldset(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(COMPONENT_TAG)) return;\n\n let result: string;\n\n if (path.endsWith('.html')) {\n result = migrateTemplate(original);\n } else {\n result = migrateInlineTemplates(original);\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would rename '${OLD_NAME}' → '${NEW_NAME}' in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n\n // Warn about TS property access usages (merged from warnTsUsages)\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts') && original.includes(OLD_NAME)) {\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === OLD_NAME) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(`${path}:${line + 1} - \"${OLD_NAME}\" has been renamed to \"${NEW_NAME}\" on ${COMPONENT_TAG}. Update this reference manually.`);\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n });\n\n context.logger.info(`Renamed '${OLD_NAME}' → '${NEW_NAME}' on ${COMPONENT_TAG} in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: { start: number; end: number; replacement: string }[] = [];\n\n visitNodes(parsed.nodes, edits);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: { start: number; end: number; text: string }[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(COMPONENT_TAG)) {\n ts.forEachChild(node, visit); return; \n}\n const migrated = migrateTemplate(rawTemplate);\n if (migrated !== rawTemplate) changes.push({ start, end, text: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n let result = source;\n for (const change of changes.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, change.start) + change.text + result.slice(change.end);\n }\n return result;\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction visitNodes(nodes: TmplAstNode[], edits: { start: number; end: number; replacement: string }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === COMPONENT_TAG) collectRenames(node, edits);\n visitNodes(node.children, edits);\n }\n }\n}\n\nfunction collectRenames(element: TmplAstElement, edits: { start: number; end: number; replacement: string }[]): void {\n for (const attr of element.attributes) {\n if (attr.name === OLD_NAME) {\n edits.push({ start: attr.keySpan!.start.offset, end: attr.keySpan!.end.offset, replacement: NEW_NAME });\n }\n }\n for (const input of element.inputs) {\n if (input.name === OLD_NAME) {\n edits.push({ start: input.keySpan!.start.offset, end: input.keySpan!.end.offset, replacement: NEW_NAME });\n }\n }\n}\n\nfunction applyEdits(source: string, edits: { start: number; end: number; replacement: string }[]): string {\n let result = source;\n for (const edit of edits.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n",
|
|
2733
2733
|
"displayName": "Schema",
|
|
2734
2734
|
"properties": [
|
|
2735
2735
|
{
|
|
@@ -2865,12 +2865,12 @@
|
|
|
2865
2865
|
},
|
|
2866
2866
|
{
|
|
2867
2867
|
"name": "Schema",
|
|
2868
|
-
"id": "interface-Schema-
|
|
2869
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
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
|
|
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-
|
|
2916
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
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":
|
|
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":
|
|
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-
|
|
2963
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
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":
|
|
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":
|
|
2991
|
+
"line": 48,
|
|
2992
2992
|
"rawdescription": "\n"
|
|
2993
2993
|
}
|
|
2994
2994
|
],
|
|
@@ -3053,12 +3053,12 @@
|
|
|
3053
3053
|
},
|
|
3054
3054
|
{
|
|
3055
3055
|
"name": "Schema",
|
|
3056
|
-
"id": "interface-Schema-
|
|
3057
|
-
"file": "packages/core/schematics/migrate-
|
|
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 { 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",
|
|
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":
|
|
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":
|
|
3085
|
+
"line": 17,
|
|
3086
3086
|
"rawdescription": "\n"
|
|
3087
3087
|
}
|
|
3088
3088
|
],
|
|
@@ -3100,12 +3100,12 @@
|
|
|
3100
3100
|
},
|
|
3101
3101
|
{
|
|
3102
3102
|
"name": "Schema",
|
|
3103
|
-
"id": "interface-Schema-
|
|
3104
|
-
"file": "packages/core/schematics/migrate-
|
|
3103
|
+
"id": "interface-Schema-d36032102ed30a7ada1e3d36bb9ca41b7234b855760cac9783a25818f7ffe2097e1ebd8e808b574ddec0760f827272f6cd561f45f3eb1578f87f39ee2633730a-20",
|
|
3104
|
+
"file": "packages/core/schematics/migrate-eui-tooltip/index.ts",
|
|
3105
3105
|
"deprecated": false,
|
|
3106
3106
|
"deprecationMessage": "",
|
|
3107
3107
|
"type": "interface",
|
|
3108
|
-
"sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface ModuleMapping {\n importPath: string;\n selectors: Record<string, string>;\n}\n\nconst MODULE_MAPPINGS: Record<string, ModuleMapping> = {\n EuiAccordionModule: {\n importPath: '@eui/components/eui-accordion',\n selectors: {\n 'eui-accordion': 'EuiAccordionComponent',\n 'eui-accordion-item': 'EuiAccordionItemComponent',\n euiAccordionItemHeader: 'EuiAccordionItemHeaderDirective',\n },\n },\n EuiAlertModule: {\n importPath: '@eui/components/eui-alert',\n selectors: {\n 'eui-alert': 'EuiAlertComponent',\n euiAlert: 'EuiAlertComponent',\n 'eui-alert-title': 'EuiAlertTitleComponent',\n },\n },\n EuiAutocompleteModule: {\n importPath: '@eui/components/eui-autocomplete',\n selectors: {\n 'eui-autocomplete': 'EuiAutocompleteComponent',\n euiAutocomplete: 'EuiAutocompleteComponent',\n 'eui-autocomplete-option': 'EuiAutocompleteOptionComponent',\n 'eui-autocomplete-option-group': 'EuiAutocompleteOptionGroupComponent',\n 'eui-autocomplete-panel': 'EuiAutocompletePanelComponent',\n },\n },\n EuiAvatarModule: {\n importPath: '@eui/components/eui-avatar',\n selectors: {\n 'eui-avatar': 'EuiAvatarComponent',\n euiAvatar: 'EuiAvatarComponent',\n },\n },\n EuiBadgeModule: {\n importPath: '@eui/components/eui-badge',\n selectors: {\n 'eui-badge': 'EuiBadgeComponent',\n euiBadge: 'EuiBadgeComponent',\n },\n },\n EuiBlockContentModule: {\n importPath: '@eui/components/eui-block-content',\n selectors: {\n 'eui-block-content': 'EuiBlockContentComponent',\n },\n },\n EuiBreadcrumbModule: {\n importPath: '@eui/components/eui-breadcrumb',\n selectors: {\n 'eui-breadcrumb': 'EuiBreadcrumbComponent',\n },\n },\n EuiButtonModule: {\n importPath: '@eui/components/eui-button',\n selectors: {\n euiButton: 'EuiButtonComponent',\n },\n },\n EuiButtonGroupModule: {\n importPath: '@eui/components/eui-button-group',\n selectors: {\n 'eui-button-group': 'EuiButtonGroupComponent',\n },\n },\n EuiCardModule: {\n importPath: '@eui/components/eui-card',\n selectors: {\n 'eui-card': 'EuiCardComponent',\n 'eui-card-header': 'EuiCardHeaderComponent',\n 'eui-card-header-title': 'EuiCardHeaderTitleComponent',\n 'eui-card-content': 'EuiCardContentComponent',\n 'eui-card-footer': 'EuiCardFooterComponent',\n 'eui-card-media': 'EuiCardMediaComponent',\n },\n },\n EuiChipModule: {\n importPath: '@eui/components/eui-chip',\n selectors: {\n 'eui-chip': 'EuiChipComponent',\n euiChip: 'EuiChipComponent',\n },\n },\n EuiChipListModule: {\n importPath: '@eui/components/eui-chip-list',\n selectors: {\n 'eui-chip-list': 'EuiChipListComponent',\n },\n },\n EuiChipGroupModule: {\n importPath: '@eui/components/eui-chip-group',\n selectors: {\n 'eui-chip-group': 'EuiChipGroupComponent',\n },\n },\n EuiDashboardCardModule: {\n importPath: '@eui/components/eui-dashboard-card',\n selectors: {\n 'eui-dashboard-card': 'EuiDashboardCardComponent',\n 'eui-dashboard-card-content': 'EuiDashboardCardContentComponent',\n 'eui-dashboard-card-content-header': 'EuiDashboardCardContentHeaderComponent',\n 'eui-dashboard-card-content-body': 'EuiDashboardCardContentBodyComponent',\n 'eui-dashboard-card-content-footer': 'EuiDashboardCardContentFooterComponent',\n },\n },\n EuiDashboardButtonModule: {\n importPath: '@eui/components/eui-dashboard-card',\n selectors: {\n 'eui-dashboard-card': 'EuiDashboardCardComponent',\n 'eui-dashboard-card-content': 'EuiDashboardCardContentComponent',\n 'eui-dashboard-card-content-header': 'EuiDashboardCardContentHeaderComponent',\n 'eui-dashboard-card-content-body': 'EuiDashboardCardContentBodyComponent',\n 'eui-dashboard-card-content-footer': 'EuiDashboardCardContentFooterComponent',\n },\n },\n EuiDatepickerModule: {\n importPath: '@eui/components/eui-datepicker',\n selectors: {\n 'eui-datepicker': 'EuiDatepickerComponent',\n },\n },\n EuiDateRangeSelectorModule: {\n importPath: '@eui/components/eui-date-range-selector',\n selectors: {\n 'eui-date-range-selector': 'EuiDateRangeSelectorComponent',\n },\n },\n EuiDialogModule: {\n importPath: '@eui/components/eui-dialog',\n selectors: {\n 'eui-dialog': 'EuiDialogComponent',\n 'eui-dialog-header': 'EuiDialogHeaderDirective',\n 'eui-dialog-footer': 'EuiDialogFooterDirective',\n 'eui-dialog-container': 'EuiDialogContainerComponent',\n },\n },\n EuiDisableContentModule: {\n importPath: '@eui/components/eui-disable-content',\n selectors: {\n 'eui-disable-content': 'EuiDisableContentComponent',\n },\n },\n EuiDiscussionThreadModule: {\n importPath: '@eui/components/eui-discussion-thread',\n selectors: {\n 'eui-discussion-thread': 'EuiDiscussionThreadComponent',\n 'eui-discussion-thread-item': 'EuiDiscussionThreadItemComponent',\n },\n },\n EuiDropdownModule: {\n importPath: '@eui/components/eui-dropdown',\n selectors: {\n 'eui-dropdown': 'EuiDropdownComponent',\n },\n },\n EuiFeedbackMessageModule: {\n importPath: '@eui/components/eui-feedback-message',\n selectors: {\n 'eui-feedback-message': 'EuiFeedbackMessageComponent',\n },\n },\n EuiFieldsetModule: {\n importPath: '@eui/components/eui-fieldset',\n selectors: {\n 'eui-fieldset': 'EuiFieldsetComponent',\n euiFieldsetLabelRightContent: 'EuiFieldsetLabelRightContentTagDirective',\n euiFieldsetLabelExtraContent: 'EuiFieldsetLabelExtraContentTagDirective',\n },\n },\n EuiFileUploadModule: {\n importPath: '@eui/components/eui-file-upload',\n selectors: {\n 'eui-file-upload': 'EuiFileUploadComponent',\n },\n },\n EuiGrowlModule: {\n importPath: '@eui/components/eui-growl',\n selectors: {\n 'eui-growl': 'EuiGrowlComponent',\n },\n },\n EuiIconModule: {\n importPath: '@eui/components/eui-icon',\n selectors: {\n 'eui-icon-svg': 'EuiIconSvgComponent',\n euiIconSvg: 'EuiIconSvgComponent',\n },\n },\n EuiIconButtonModule: {\n importPath: '@eui/components/eui-icon-button',\n selectors: {\n 'eui-icon-button': 'EuiIconButtonComponent',\n },\n },\n EuiIconToggleModule: {\n importPath: '@eui/components/eui-icon-toggle',\n selectors: {\n 'eui-icon-toggle': 'EuiIconToggleComponent',\n },\n },\n EuiInputCheckboxModule: {\n importPath: '@eui/components/eui-input-checkbox',\n selectors: {\n euiInputCheckBox: 'EuiInputCheckboxComponent',\n },\n },\n EuiInputGroupModule: {\n importPath: '@eui/components/eui-input-group',\n selectors: {\n euiInputGroup: 'EuiInputGroupComponent',\n 'eui-input-group-addon': 'EuiInputGroupAddOnComponent',\n euiInputGroupAddOn: 'EuiInputGroupAddOnComponent',\n 'eui-input-group-addon-item': 'EuiInputGroupAddOnItemComponent',\n euiInputGroupAddOnItem: 'EuiInputGroupAddOnItemComponent',\n },\n },\n EuiInputNumberModule: {\n importPath: '@eui/components/eui-input-number',\n selectors: {\n euiInputNumber: 'EuiInputNumberComponent',\n },\n },\n EuiInputRadioModule: {\n importPath: '@eui/components/eui-input-radio',\n selectors: {\n euiInputRadio: 'EuiInputRadioComponent',\n },\n },\n EuiInputTextModule: {\n importPath: '@eui/components/eui-input-text',\n selectors: {\n euiInputText: 'EuiInputTextComponent',\n },\n },\n EuiLabelModule: {\n importPath: '@eui/components/eui-label',\n selectors: {\n 'eui-label': 'EuiLabelComponent',\n euiLabel: 'EuiLabelComponent',\n },\n },\n EuiListModule: {\n importPath: '@eui/components/eui-list',\n selectors: {\n 'eui-list': 'EuiListComponent',\n euiList: 'EuiListComponent',\n 'eui-list-item': 'EuiListItemComponent',\n euiListItem: 'EuiListItemComponent',\n },\n },\n EuiMenuModule: {\n importPath: '@eui/components/eui-menu',\n selectors: {\n 'eui-menu': 'EuiMenuComponent',\n 'eui-menu-item': 'EuiMenuItemComponent',\n },\n },\n EuiMessageBoxModule: {\n importPath: '@eui/components/eui-message-box',\n selectors: {\n 'eui-message-box': 'EuiMessageBoxComponent',\n 'eui-message-box-footer': 'EuiMessageBoxFooterDirective',\n },\n },\n EuiOverlayModule: {\n importPath: '@eui/components/eui-overlay',\n selectors: {\n 'eui-overlay': 'EuiOverlayComponent',\n },\n },\n EuiPageModule: {\n importPath: '@eui/components/eui-page',\n selectors: {\n 'eui-page': 'EuiPageComponent',\n },\n },\n EuiPaginatorModule: {\n importPath: '@eui/components/eui-paginator',\n selectors: {\n 'eui-paginator': 'EuiPaginatorComponent',\n },\n },\n EuiPopoverModule: {\n importPath: '@eui/components/eui-popover',\n selectors: {\n 'eui-popover': 'EuiPopoverComponent',\n },\n },\n EuiProgressBarModule: {\n importPath: '@eui/components/eui-progress-bar',\n selectors: {\n 'eui-progress-bar': 'EuiProgressBarComponent',\n },\n },\n EuiProgressCircleModule: {\n importPath: '@eui/components/eui-progress-circle',\n selectors: {\n 'eui-progress-circle': 'EuiProgressCircleComponent',\n },\n },\n EuiSelectModule: {\n importPath: '@eui/components/eui-select',\n selectors: {\n euiSelect: 'EuiSelectComponent',\n },\n },\n EuiSidebarMenuModule: {\n importPath: '@eui/components/eui-sidebar-menu',\n selectors: {\n 'eui-sidebar-menu': 'EuiSidebarMenuComponent',\n },\n },\n EuiSkeletonModule: {\n importPath: '@eui/components/eui-skeleton',\n selectors: {\n 'eui-skeleton': 'EuiSkeletonComponent',\n },\n },\n EuiSlideToggleModule: {\n importPath: '@eui/components/eui-slide-toggle',\n selectors: {\n 'eui-slide-toggle': 'EuiSlideToggleComponent',\n },\n },\n EuiTableModule: {\n importPath: '@eui/components/eui-table',\n selectors: {\n 'eui-table': 'EuiTableComponent',\n euiTable: 'EuiTableComponent',\n 'eui-table-filter': 'EuiTableFilterComponent',\n isSortable: 'EuiTableSortableColComponent',\n isStickyCol: 'EuiTableStickyColDirective',\n isHeaderSelectable: 'EuiTableSelectableHeaderComponent',\n isDataSelectable: 'EuiTableSelectableRowComponent',\n isExpandableRow: 'EuiTableExpandableRowDirective',\n },\n },\n EuiTableV2Module: {\n importPath: '@eui/components/eui-table',\n selectors: {\n 'eui-table': 'EuiTableComponent',\n euiTable: 'EuiTableComponent',\n 'eui-table-filter': 'EuiTableFilterComponent',\n isSortable: 'EuiTableSortableColComponent',\n isStickyCol: 'EuiTableStickyColDirective',\n isHeaderSelectable: 'EuiTableSelectableHeaderComponent',\n isDataSelectable: 'EuiTableSelectableRowComponent',\n isExpandableRow: 'EuiTableExpandableRowDirective',\n },\n },\n EuiTabsModule: {\n importPath: '@eui/components/eui-tabs',\n selectors: {\n 'eui-tabs': 'EuiTabsComponent',\n 'eui-tab': 'EuiTabComponent',\n 'eui-tab-header': 'EuiTabHeaderComponent',\n 'eui-tab-body': 'EuiTabBodyComponent',\n },\n },\n EuiTextAreaModule: {\n importPath: '@eui/components/eui-textarea',\n selectors: {\n euiTextArea: 'EuiTextareaComponent',\n },\n },\n EuiTimelineModule: {\n importPath: '@eui/components/eui-timeline',\n selectors: {\n 'eui-timeline': 'EuiTimelineComponent',\n 'eui-timeline-item': 'EuiTimelineItemComponent',\n },\n },\n EuiTimepickerModule: {\n importPath: '@eui/components/eui-timepicker',\n selectors: {\n 'eui-timepicker': 'EuiTimepickerComponent',\n },\n },\n EuiTreeModule: {\n importPath: '@eui/components/eui-tree',\n selectors: {\n 'eui-tree': 'EuiTreeComponent',\n },\n },\n EuiTreeListModule: {\n importPath: '@eui/components/eui-tree-list',\n selectors: {\n 'eui-tree-list': 'EuiTreeListComponent',\n 'eui-tree-list-item': 'EuiTreeListItemComponent',\n },\n },\n EuiUserProfileModule: {\n importPath: '@eui/components/eui-user-profile',\n selectors: {\n 'eui-user-profile': 'EuiUserProfileComponent',\n },\n },\n EuiWizardModule: {\n importPath: '@eui/components/eui-wizard',\n selectors: {\n 'eui-wizard': 'EuiWizardComponent',\n 'eui-wizard-step': 'EuiWizardStepComponent',\n },\n },\n EuiTooltipDirectiveModule: {\n importPath: '@eui/components/directives',\n selectors: {\n euiTooltip: 'EuiTooltipDirective',\n },\n },\n EuiTemplateDirectiveModule: {\n importPath: '@eui/components/directives',\n selectors: {\n euiTemplate: 'EuiTemplateDirective',\n },\n },\n EuiResizableDirectiveModule: {\n importPath: '@eui/components/directives',\n selectors: {\n euiResizable: 'EuiResizableDirective',\n 'eui-resizable': 'EuiResizableComponent',\n },\n },\n EuiMaxLengthDirectiveModule: {\n importPath: '@eui/components/directives',\n selectors: {\n euiEditorMaxlength: 'EuiMaxLengthDirective',\n },\n },\n EuiTruncatePipeModule: {\n importPath: '@eui/components/pipes',\n selectors: {\n euiTruncate: 'EuiTruncatePipe',\n },\n },\n EuiLayoutModule: {\n importPath: '@eui/components/layout',\n selectors: {\n 'eui-app': 'EuiAppComponent',\n 'eui-header': 'EuiHeaderComponent',\n 'eui-footer': 'EuiFooterComponent',\n 'eui-toolbar': 'EuiToolbarComponent',\n 'eui-sidebar-toggle': 'EuiSidebarToggleComponent',\n },\n },\n};\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nexport function migrateToStandalone(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n const allModuleNames = Object.keys(MODULE_MAPPINGS);\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const source = buffer.toString('utf-8');\n if (!allModuleNames.some((m) => source.includes(m))) return;\n\n const sourceFile = ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true);\n const componentDecorators = findComponentDecorators(sourceFile);\n\n for (const decorator of componentDecorators) {\n const importsNode = findImportsArrayNode(decorator);\n if (!importsNode) continue;\n\n const currentSource = tree.read(path)!.toString('utf-8');\n const modulesInArray = allModuleNames.filter((m) => hasModuleInArray(importsNode, m, source));\n if (modulesInArray.length === 0) continue;\n\n const templateSelectors = getTemplateSelectors(tree, path, decorator, source);\n const replacements = buildReplacements(modulesInArray, templateSelectors);\n if (replacements.size === 0) continue;\n\n const result = applyReplacements(currentSource, path, replacements);\n if (options.dryRun) {\n logDryRun(context, `Would replace module imports with standalone imports in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n }\n });\n\n context.logger.info('Migration to standalone imports complete.');\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction buildReplacements(moduleNames: string[], templateSelectors: Set<string>): Map<string, { components: string[]; importPath: string }> {\n const map = new Map<string, { components: string[]; importPath: string }>();\n\n for (const moduleName of moduleNames) {\n const mapping = MODULE_MAPPINGS[moduleName];\n const components: string[] = [];\n\n for (const [selector, component] of Object.entries(mapping.selectors)) {\n if (templateSelectors.has(selector)) {\n components.push(component);\n }\n }\n\n if (components.length > 0) {\n map.set(moduleName, { components: [...new Set(components)].sort(), importPath: mapping.importPath });\n }\n }\n\n return map;\n}\n\nfunction findComponentDecorators(sourceFile: ts.SourceFile): ts.Decorator[] {\n const decorators: ts.Decorator[] = [];\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node)) {\n const decs = ts.getDecorators(node);\n if (decs) {\n for (const dec of decs) {\n if (ts.isCallExpression(dec.expression) && ts.isIdentifier(dec.expression.expression) && dec.expression.expression.text === 'Component') {\n decorators.push(dec);\n }\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n return decorators;\n}\n\nfunction findImportsArrayNode(decorator: ts.Decorator): ts.ArrayLiteralExpression | undefined {\n const call = decorator.expression as ts.CallExpression;\n const metadata = call.arguments[0];\n if (!ts.isObjectLiteralExpression(metadata)) return undefined;\n\n for (const prop of metadata.properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'imports') {\n if (ts.isArrayLiteralExpression(prop.initializer)) {\n return prop.initializer;\n }\n }\n }\n return undefined;\n}\n\nfunction hasModuleInArray(array: ts.ArrayLiteralExpression, moduleName: string, source: string): boolean {\n return array.elements.some((el) => source.slice(el.getStart(), el.getEnd()).trim() === moduleName);\n}\n\nfunction getTemplateSelectors(tree: Tree, tsPath: string, decorator: ts.Decorator, source: string): Set<string> {\n const call = decorator.expression as ts.CallExpression;\n const metadata = call.arguments[0] as ts.ObjectLiteralExpression;\n const allSelectors = Object.values(MODULE_MAPPINGS).flatMap((m) => Object.keys(m.selectors));\n\n for (const prop of metadata.properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'template') {\n const init = prop.initializer;\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n return findSelectorsInTemplate(init.text, allSelectors);\n }\n }\n }\n\n for (const prop of metadata.properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'templateUrl') {\n if (ts.isStringLiteral(prop.initializer)) {\n const dir = tsPath.substring(0, tsPath.lastIndexOf('/'));\n const templateBuffer = tree.read(`${dir}/${prop.initializer.text}`);\n if (templateBuffer) {\n return findSelectorsInTemplate(templateBuffer.toString('utf-8'), allSelectors);\n }\n }\n }\n }\n\n return new Set();\n}\n\nfunction findSelectorsInTemplate(html: string, knownSelectors: string[]): Set<string> {\n const selectors = new Set<string>();\n const parsed = parseTemplate(html, '', { preserveWhitespaces: true });\n\n const visit = (nodes: TmplAstNode[]): void => {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (knownSelectors.includes(node.name)) selectors.add(node.name);\n for (const attr of node.attributes) {\n if (knownSelectors.includes(attr.name)) selectors.add(attr.name);\n }\n visit(node.children);\n }\n }\n };\n visit(parsed.nodes);\n return selectors;\n}\n\nfunction applyReplacements(source: string, filePath: string, replacements: Map<string, { components: string[]; importPath: string }>): string {\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);\n let result = replaceInImportsArray(source, sourceFile, replacements);\n result = updateEsImports(result, filePath, replacements);\n return result;\n}\n\nfunction replaceInImportsArray(source: string, sourceFile: ts.SourceFile, replacements: Map<string, { components: string[]; importPath: string }>): string {\n let result = source;\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node)) {\n const decs = ts.getDecorators(node);\n if (!decs) return;\n for (const dec of decs) {\n if (!ts.isCallExpression(dec.expression) || !ts.isIdentifier(dec.expression.expression) || dec.expression.expression.text !== 'Component') continue;\n const metadata = dec.expression.arguments[0];\n if (!ts.isObjectLiteralExpression(metadata)) continue;\n for (const prop of metadata.properties) {\n if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name) || prop.name.text !== 'imports') continue;\n if (!ts.isArrayLiteralExpression(prop.initializer)) continue;\n const newElements = prop.initializer.elements\n .map((el) => {\n const text = result.slice(el.getStart(sourceFile), el.getEnd()).trim();\n const replacement = replacements.get(text);\n return replacement ? replacement.components.join(', ') : text;\n })\n .join(', ');\n result = result.slice(0, prop.initializer.getStart(sourceFile) + 1) + newElements + result.slice(prop.initializer.getEnd() - 1);\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n return result;\n}\n\nfunction updateEsImports(source: string, filePath: string, replacements: Map<string, { components: string[]; importPath: string }>): string {\n let result = source;\n\n for (const [moduleName, { components, importPath }] of replacements) {\n const sf = ts.createSourceFile(filePath, result, ts.ScriptTarget.Latest, true);\n\n for (const stmt of sf.statements) {\n if (!ts.isImportDeclaration(stmt) || !stmt.importClause?.namedBindings || !ts.isNamedImports(stmt.importClause.namedBindings)) continue;\n const namedBindings = stmt.importClause.namedBindings;\n const importNames = namedBindings.elements.map((el) => el.name.text);\n if (!importNames.includes(moduleName)) continue;\n\n const moduleSpecifier = (stmt.moduleSpecifier as ts.StringLiteral).text;\n const remaining = importNames.filter((n) => n !== moduleName);\n const toAdd = components.filter((c) => !importNames.includes(c));\n\n if (moduleSpecifier === importPath) {\n const newNames = [...remaining, ...toAdd].sort();\n const newClause = `{ ${newNames.join(', ')} }`;\n result = result.slice(0, namedBindings.getStart(sf)) + newClause + result.slice(namedBindings.getEnd());\n } else {\n if (remaining.length > 0) {\n const newClause = `{ ${remaining.join(', ')} }`;\n result = result.slice(0, namedBindings.getStart(sf)) + newClause + result.slice(namedBindings.getEnd());\n } else {\n result = result.slice(0, stmt.getStart(sf)) + result.slice(stmt.getEnd()).replace(/^\\r?\\n/, '');\n }\n\n if (toAdd.length > 0) {\n const updatedSf = ts.createSourceFile(filePath, result, ts.ScriptTarget.Latest, true);\n const existing = updatedSf.statements.find(\n (s) => ts.isImportDeclaration(s) && ts.isStringLiteral(s.moduleSpecifier) && s.moduleSpecifier.text === importPath,\n ) as ts.ImportDeclaration | undefined;\n\n if (existing?.importClause?.namedBindings && ts.isNamedImports(existing.importClause.namedBindings)) {\n const existingNames = existing.importClause.namedBindings.elements.map((el) => el.name.text);\n const allNames = [...new Set([...existingNames, ...toAdd])].sort();\n const newClause = `{ ${allNames.join(', ')} }`;\n result = result.slice(0, existing.importClause.namedBindings.getStart(updatedSf)) + newClause + result.slice(existing.importClause.namedBindings.getEnd());\n } else {\n const newImport = `import { ${toAdd.join(', ')} } from '${importPath}';\\n`;\n result = newImport + result;\n }\n }\n }\n break;\n }\n }\n\n return result;\n}\n",
|
|
3108
|
+
"sourceCode": "import { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\ninterface Edit {\n start: number;\n end: number;\n replacement: string;\n}\n\nconst OLD_CLASS = 'EuiTooltipConfig';\nconst NEW_INTERFACE = 'EuiTooltipInterface';\n\nexport function migrateEuiTooltip(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let fileCount = 0;\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n if (!path.endsWith('.ts')) return;\n\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(OLD_CLASS)) return;\n\n const result = migrateTypeScript(original, path, context);\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would migrate EuiTooltipConfig → EuiTooltipInterface in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n fileCount++;\n }\n });\n\n context.logger.info(`Migrated EuiTooltipConfig → EuiTooltipInterface in ${fileCount} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction migrateTypeScript(source: string, filePath: string, context: SchematicContext): string {\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: Edit[] = [];\n\n // Track if EuiTooltipInterface is already imported\n let hasInterfaceImport = false;\n let classImportDecl: ts.ImportDeclaration | null = null;\n let classImportModuleSpecifier: string | null = null;\n\n // First pass: analyze imports\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const namedBindings = stmt.importClause?.namedBindings;\n if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;\n\n for (const specifier of namedBindings.elements) {\n if (specifier.name.text === NEW_INTERFACE) {\n hasInterfaceImport = true;\n }\n if (specifier.name.text === OLD_CLASS) {\n classImportDecl = stmt;\n classImportModuleSpecifier = (stmt.moduleSpecifier as ts.StringLiteral).text;\n }\n }\n }\n\n // Second pass: handle import declarations\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const namedBindings = stmt.importClause?.namedBindings;\n if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;\n\n const specifiers = namedBindings.elements;\n const classSpecifier = specifiers.find((s) => s.name.text === OLD_CLASS);\n if (!classSpecifier) continue;\n\n if (hasInterfaceImport) {\n // EuiTooltipInterface is already imported elsewhere → remove EuiTooltipConfig from this import\n removeImportSpecifier(namedBindings, classSpecifier, sourceFile, edits);\n } else {\n // Rename EuiTooltipConfig → EuiTooltipInterface in the import\n edits.push({\n start: classSpecifier.name.getStart(sourceFile),\n end: classSpecifier.name.getEnd(),\n replacement: NEW_INTERFACE,\n });\n hasInterfaceImport = true;\n }\n }\n\n // Third pass: replace `new EuiTooltipConfig(...)` → spread/cast to interface\n const visitNewExpressions = (node: ts.Node): void => {\n if (ts.isNewExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === OLD_CLASS) {\n const args = node.arguments;\n if (args && args.length === 1) {\n const arg = args[0];\n // `new EuiTooltipConfig({ ... })` → `{ ... } as EuiTooltipInterface`\n // But if the argument is just a variable, we keep it: `varName as EuiTooltipInterface`\n const argText = source.slice(arg.getStart(sourceFile), arg.getEnd());\n\n if (ts.isObjectLiteralExpression(arg)) {\n // Inline object: `new EuiTooltipConfig({ x: 1 })` → `{ x: 1 }`\n edits.push({\n start: node.getStart(sourceFile),\n end: node.getEnd(),\n replacement: argText,\n });\n } else {\n // Variable or expression: `new EuiTooltipConfig(opts)` → `opts`\n edits.push({\n start: node.getStart(sourceFile),\n end: node.getEnd(),\n replacement: argText,\n });\n }\n } else if (!args || args.length === 0) {\n // `new EuiTooltipConfig()` → `{} as EuiTooltipInterface`\n edits.push({\n start: node.getStart(sourceFile),\n end: node.getEnd(),\n replacement: `{} as ${NEW_INTERFACE}`,\n });\n }\n return; // don't recurse into children we've already replaced\n }\n ts.forEachChild(node, visitNewExpressions);\n };\n\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) {\n visitNewExpressions(stmt);\n }\n }\n\n // Fourth pass: rename all remaining identifier references (type annotations, etc.)\n const visitRefs = (node: ts.Node): void => {\n if (ts.isImportDeclaration(node)) return;\n // Skip nodes we already covered in new expressions\n if (ts.isNewExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === OLD_CLASS) return;\n\n if (ts.isIdentifier(node) && node.text === OLD_CLASS) {\n // Ensure this is not part of an import declaration\n if (!isPartOfImport(node)) {\n edits.push({\n start: node.getStart(sourceFile),\n end: node.getEnd(),\n replacement: NEW_INTERFACE,\n });\n }\n }\n ts.forEachChild(node, visitRefs);\n };\n\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) {\n visitRefs(stmt);\n }\n }\n\n return applyEdits(source, edits);\n}\n\nfunction isPartOfImport(node: ts.Node): boolean {\n let current: ts.Node | undefined = node.parent;\n while (current) {\n if (ts.isImportDeclaration(current)) return true;\n current = current.parent;\n }\n return false;\n}\n\nfunction removeImportSpecifier(\n namedImports: ts.NamedImports,\n specifier: ts.ImportSpecifier,\n sourceFile: ts.SourceFile,\n edits: Edit[],\n): void {\n const elements = namedImports.elements;\n if (elements.length === 1) {\n // Remove the entire import declaration\n const importDecl = namedImports.parent.parent;\n let end = importDecl.getEnd();\n // Also remove trailing newline if present\n const fullText = sourceFile.getFullText();\n if (fullText[end] === '\\n') end++;\n edits.push({\n start: importDecl.getStart(sourceFile),\n end,\n replacement: '',\n });\n } else {\n // Remove just this specifier with surrounding comma/whitespace\n const idx = elements.indexOf(specifier);\n let start: number;\n let end: number;\n if (idx < elements.length - 1) {\n // Not the last → remove from this specifier start to next specifier start\n start = specifier.getStart(sourceFile);\n end = elements[idx + 1].getStart(sourceFile);\n } else {\n // Last element → remove from previous element end to this end\n start = elements[idx - 1].getEnd();\n end = specifier.getEnd();\n }\n edits.push({ start, end, replacement: '' });\n }\n}\n\nfunction applyEdits(source: string, edits: Edit[]): string {\n const unique = deduplicateEdits(edits);\n let result = source;\n for (const edit of unique.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n\nfunction deduplicateEdits(edits: Edit[]): Edit[] {\n const seen = new Map<string, Edit>();\n for (const edit of edits) {\n const key = `${edit.start}:${edit.end}`;\n seen.set(key, edit);\n }\n return Array.from(seen.values());\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n",
|
|
3109
3109
|
"displayName": "Schema",
|
|
3110
3110
|
"properties": [
|
|
3111
3111
|
{
|
|
@@ -3117,7 +3117,7 @@
|
|
|
3117
3117
|
"indexKey": "",
|
|
3118
3118
|
"optional": true,
|
|
3119
3119
|
"description": "",
|
|
3120
|
-
"line":
|
|
3120
|
+
"line": 7,
|
|
3121
3121
|
"rawdescription": "\n"
|
|
3122
3122
|
},
|
|
3123
3123
|
{
|
|
@@ -3129,7 +3129,7 @@
|
|
|
3129
3129
|
"indexKey": "",
|
|
3130
3130
|
"optional": true,
|
|
3131
3131
|
"description": "",
|
|
3132
|
-
"line":
|
|
3132
|
+
"line": 6,
|
|
3133
3133
|
"rawdescription": "\n"
|
|
3134
3134
|
}
|
|
3135
3135
|
],
|
|
@@ -3147,12 +3147,12 @@
|
|
|
3147
3147
|
},
|
|
3148
3148
|
{
|
|
3149
3149
|
"name": "Schema",
|
|
3150
|
-
"id": "interface-Schema-
|
|
3151
|
-
"file": "packages/core/schematics/migrate-
|
|
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 { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\ninterface Edit {\n start: number;\n end: number;\n replacement: string;\n}\n\nconst OLD_CLASS = 'EuiTooltipConfig';\nconst NEW_INTERFACE = 'EuiTooltipInterface';\n\nexport function migrateEuiTooltip(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let fileCount = 0;\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n if (!path.endsWith('.ts')) return;\n\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(OLD_CLASS)) return;\n\n const result = migrateTypeScript(original, path, context);\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would migrate EuiTooltipConfig → EuiTooltipInterface in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n fileCount++;\n }\n });\n\n context.logger.info(`Migrated EuiTooltipConfig → EuiTooltipInterface in ${fileCount} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction migrateTypeScript(source: string, filePath: string, context: SchematicContext): string {\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: Edit[] = [];\n\n // Track if EuiTooltipInterface is already imported\n let hasInterfaceImport = false;\n let classImportDecl: ts.ImportDeclaration | null = null;\n let classImportModuleSpecifier: string | null = null;\n\n // First pass: analyze imports\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const namedBindings = stmt.importClause?.namedBindings;\n if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;\n\n for (const specifier of namedBindings.elements) {\n if (specifier.name.text === NEW_INTERFACE) {\n hasInterfaceImport = true;\n }\n if (specifier.name.text === OLD_CLASS) {\n classImportDecl = stmt;\n classImportModuleSpecifier = (stmt.moduleSpecifier as ts.StringLiteral).text;\n }\n }\n }\n\n // Second pass: handle import declarations\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const namedBindings = stmt.importClause?.namedBindings;\n if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;\n\n const specifiers = namedBindings.elements;\n const classSpecifier = specifiers.find((s) => s.name.text === OLD_CLASS);\n if (!classSpecifier) continue;\n\n if (hasInterfaceImport) {\n // EuiTooltipInterface is already imported elsewhere → remove EuiTooltipConfig from this import\n removeImportSpecifier(namedBindings, classSpecifier, sourceFile, edits);\n } else {\n // Rename EuiTooltipConfig → EuiTooltipInterface in the import\n edits.push({\n start: classSpecifier.name.getStart(sourceFile),\n end: classSpecifier.name.getEnd(),\n replacement: NEW_INTERFACE,\n });\n hasInterfaceImport = true;\n }\n }\n\n // Third pass: replace `new EuiTooltipConfig(...)` → spread/cast to interface\n const visitNewExpressions = (node: ts.Node): void => {\n if (ts.isNewExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === OLD_CLASS) {\n const args = node.arguments;\n if (args && args.length === 1) {\n const arg = args[0];\n // `new EuiTooltipConfig({ ... })` → `{ ... } as EuiTooltipInterface`\n // But if the argument is just a variable, we keep it: `varName as EuiTooltipInterface`\n const argText = source.slice(arg.getStart(sourceFile), arg.getEnd());\n\n if (ts.isObjectLiteralExpression(arg)) {\n // Inline object: `new EuiTooltipConfig({ x: 1 })` → `{ x: 1 }`\n edits.push({\n start: node.getStart(sourceFile),\n end: node.getEnd(),\n replacement: argText,\n });\n } else {\n // Variable or expression: `new EuiTooltipConfig(opts)` → `opts`\n edits.push({\n start: node.getStart(sourceFile),\n end: node.getEnd(),\n replacement: argText,\n });\n }\n } else if (!args || args.length === 0) {\n // `new EuiTooltipConfig()` → `{} as EuiTooltipInterface`\n edits.push({\n start: node.getStart(sourceFile),\n end: node.getEnd(),\n replacement: `{} as ${NEW_INTERFACE}`,\n });\n }\n return; // don't recurse into children we've already replaced\n }\n ts.forEachChild(node, visitNewExpressions);\n };\n\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) {\n visitNewExpressions(stmt);\n }\n }\n\n // Fourth pass: rename all remaining identifier references (type annotations, etc.)\n const visitRefs = (node: ts.Node): void => {\n if (ts.isImportDeclaration(node)) return;\n // Skip nodes we already covered in new expressions\n if (ts.isNewExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === OLD_CLASS) return;\n\n if (ts.isIdentifier(node) && node.text === OLD_CLASS) {\n // Ensure this is not part of an import declaration\n if (!isPartOfImport(node)) {\n edits.push({\n start: node.getStart(sourceFile),\n end: node.getEnd(),\n replacement: NEW_INTERFACE,\n });\n }\n }\n ts.forEachChild(node, visitRefs);\n };\n\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) {\n visitRefs(stmt);\n }\n }\n\n return applyEdits(source, edits);\n}\n\nfunction isPartOfImport(node: ts.Node): boolean {\n let current: ts.Node | undefined = node.parent;\n while (current) {\n if (ts.isImportDeclaration(current)) return true;\n current = current.parent;\n }\n return false;\n}\n\nfunction removeImportSpecifier(\n namedImports: ts.NamedImports,\n specifier: ts.ImportSpecifier,\n sourceFile: ts.SourceFile,\n edits: Edit[],\n): void {\n const elements = namedImports.elements;\n if (elements.length === 1) {\n // Remove the entire import declaration\n const importDecl = namedImports.parent.parent;\n let end = importDecl.getEnd();\n // Also remove trailing newline if present\n const fullText = sourceFile.getFullText();\n if (fullText[end] === '\\n') end++;\n edits.push({\n start: importDecl.getStart(sourceFile),\n end,\n replacement: '',\n });\n } else {\n // Remove just this specifier with surrounding comma/whitespace\n const idx = elements.indexOf(specifier);\n let start: number;\n let end: number;\n if (idx < elements.length - 1) {\n // Not the last → remove from this specifier start to next specifier start\n start = specifier.getStart(sourceFile);\n end = elements[idx + 1].getStart(sourceFile);\n } else {\n // Last element → remove from previous element end to this end\n start = elements[idx - 1].getEnd();\n end = specifier.getEnd();\n }\n edits.push({ start, end, replacement: '' });\n }\n}\n\nfunction applyEdits(source: string, edits: Edit[]): string {\n const unique = deduplicateEdits(edits);\n let result = source;\n for (const edit of unique.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n\nfunction deduplicateEdits(edits: Edit[]): Edit[] {\n const seen = new Map<string, Edit>();\n for (const edit of edits) {\n const key = `${edit.start}:${edit.end}`;\n seen.set(key, edit);\n }\n return Array.from(seen.values());\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n",
|
|
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":
|
|
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":
|
|
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-
|
|
3198
|
-
"file": "packages/core/schematics/migrate-
|
|
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 { 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",
|
|
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":
|
|
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":
|
|
3226
|
+
"line": 459,
|
|
3227
3227
|
"rawdescription": "\n"
|
|
3228
3228
|
}
|
|
3229
3229
|
],
|
|
@@ -23213,23 +23213,23 @@
|
|
|
23213
23213
|
"name": "NEW_INTERFACE",
|
|
23214
23214
|
"ctype": "miscellaneous",
|
|
23215
23215
|
"subtype": "variable",
|
|
23216
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
23216
|
+
"file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
|
|
23217
23217
|
"coverageIgnore": false,
|
|
23218
23218
|
"deprecated": false,
|
|
23219
23219
|
"deprecationMessage": "",
|
|
23220
23220
|
"type": "string",
|
|
23221
|
-
"defaultValue": "'
|
|
23221
|
+
"defaultValue": "'EuiMenuItem'"
|
|
23222
23222
|
},
|
|
23223
23223
|
{
|
|
23224
23224
|
"name": "NEW_INTERFACE",
|
|
23225
23225
|
"ctype": "miscellaneous",
|
|
23226
23226
|
"subtype": "variable",
|
|
23227
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
23227
|
+
"file": "packages/core/schematics/migrate-eui-tooltip/index.ts",
|
|
23228
23228
|
"coverageIgnore": false,
|
|
23229
23229
|
"deprecated": false,
|
|
23230
23230
|
"deprecationMessage": "",
|
|
23231
23231
|
"type": "string",
|
|
23232
|
-
"defaultValue": "'
|
|
23232
|
+
"defaultValue": "'EuiTooltipInterface'"
|
|
23233
23233
|
},
|
|
23234
23234
|
{
|
|
23235
23235
|
"name": "NEW_INTERFACE_PATH",
|
|
@@ -23615,23 +23615,23 @@
|
|
|
23615
23615
|
"name": "REMOVED_INPUTS",
|
|
23616
23616
|
"ctype": "miscellaneous",
|
|
23617
23617
|
"subtype": "variable",
|
|
23618
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
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(['
|
|
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-
|
|
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(['
|
|
23634
|
+
"defaultValue": "new Set(['euiTableBordered', 'isHoverable', 'defaultMultiOrder', 'paginable'])"
|
|
23635
23635
|
},
|
|
23636
23636
|
{
|
|
23637
23637
|
"name": "REMOVED_OUTPUT",
|
|
@@ -24788,7 +24788,7 @@
|
|
|
24788
24788
|
},
|
|
24789
24789
|
{
|
|
24790
24790
|
"name": "applyEdits",
|
|
24791
|
-
"file": "packages/core/schematics/migrate-
|
|
24791
|
+
"file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
|
|
24792
24792
|
"ctype": "miscellaneous",
|
|
24793
24793
|
"subtype": "function",
|
|
24794
24794
|
"coverageIgnore": false,
|
|
@@ -24878,7 +24878,7 @@
|
|
|
24878
24878
|
},
|
|
24879
24879
|
{
|
|
24880
24880
|
"name": "applyEdits",
|
|
24881
|
-
"file": "packages/core/schematics/migrate-
|
|
24881
|
+
"file": "packages/core/schematics/migrate-to-signals/index.ts",
|
|
24882
24882
|
"ctype": "miscellaneous",
|
|
24883
24883
|
"subtype": "function",
|
|
24884
24884
|
"coverageIgnore": false,
|
|
@@ -27038,7 +27038,7 @@
|
|
|
27038
27038
|
},
|
|
27039
27039
|
{
|
|
27040
27040
|
"name": "deduplicateEdits",
|
|
27041
|
-
"file": "packages/core/schematics/migrate-
|
|
27041
|
+
"file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
|
|
27042
27042
|
"ctype": "miscellaneous",
|
|
27043
27043
|
"subtype": "function",
|
|
27044
27044
|
"coverageIgnore": false,
|
|
@@ -27098,7 +27098,7 @@
|
|
|
27098
27098
|
},
|
|
27099
27099
|
{
|
|
27100
27100
|
"name": "deduplicateEdits",
|
|
27101
|
-
"file": "packages/core/schematics/migrate-
|
|
27101
|
+
"file": "packages/core/schematics/migrate-to-signals/index.ts",
|
|
27102
27102
|
"ctype": "miscellaneous",
|
|
27103
27103
|
"subtype": "function",
|
|
27104
27104
|
"coverageIgnore": false,
|
|
@@ -32579,7 +32579,7 @@
|
|
|
32579
32579
|
},
|
|
32580
32580
|
{
|
|
32581
32581
|
"name": "migrateInlineTemplates",
|
|
32582
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
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-
|
|
32614
|
+
"file": "packages/core/schematics/migrate-eui-progress-circle/index.ts",
|
|
32615
32615
|
"ctype": "miscellaneous",
|
|
32616
32616
|
"subtype": "function",
|
|
32617
32617
|
"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",
|
|
@@ -33211,38 +33243,6 @@
|
|
|
33211
33243
|
}
|
|
33212
33244
|
]
|
|
33213
33245
|
},
|
|
33214
|
-
{
|
|
33215
|
-
"name": "migrateTemplate",
|
|
33216
|
-
"file": "packages/core/schematics/migrate-eui-popover/index.ts",
|
|
33217
|
-
"ctype": "miscellaneous",
|
|
33218
|
-
"subtype": "function",
|
|
33219
|
-
"coverageIgnore": false,
|
|
33220
|
-
"deprecated": false,
|
|
33221
|
-
"deprecationMessage": "",
|
|
33222
|
-
"rawdescription": "",
|
|
33223
|
-
"description": "",
|
|
33224
|
-
"displayName": "migrateTemplate",
|
|
33225
|
-
"args": [
|
|
33226
|
-
{
|
|
33227
|
-
"name": "source",
|
|
33228
|
-
"type": "string",
|
|
33229
|
-
"deprecated": false,
|
|
33230
|
-
"deprecationMessage": ""
|
|
33231
|
-
}
|
|
33232
|
-
],
|
|
33233
|
-
"returnType": "string",
|
|
33234
|
-
"jsdoctags": [
|
|
33235
|
-
{
|
|
33236
|
-
"name": "source",
|
|
33237
|
-
"type": "string",
|
|
33238
|
-
"deprecated": false,
|
|
33239
|
-
"deprecationMessage": "",
|
|
33240
|
-
"tagName": {
|
|
33241
|
-
"text": "param"
|
|
33242
|
-
}
|
|
33243
|
-
}
|
|
33244
|
-
]
|
|
33245
|
-
},
|
|
33246
33246
|
{
|
|
33247
33247
|
"name": "migrateTemplate",
|
|
33248
33248
|
"file": "packages/core/schematics/migrate-eui-tabs/index.ts",
|
|
@@ -33561,7 +33561,7 @@
|
|
|
33561
33561
|
},
|
|
33562
33562
|
{
|
|
33563
33563
|
"name": "migrateTypeScript",
|
|
33564
|
-
"file": "packages/core/schematics/migrate-
|
|
33564
|
+
"file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
|
|
33565
33565
|
"ctype": "miscellaneous",
|
|
33566
33566
|
"subtype": "function",
|
|
33567
33567
|
"coverageIgnore": false,
|
|
@@ -33583,12 +33583,6 @@
|
|
|
33583
33583
|
"deprecated": false,
|
|
33584
33584
|
"deprecationMessage": ""
|
|
33585
33585
|
},
|
|
33586
|
-
{
|
|
33587
|
-
"name": "tree",
|
|
33588
|
-
"type": "Tree",
|
|
33589
|
-
"deprecated": false,
|
|
33590
|
-
"deprecationMessage": ""
|
|
33591
|
-
},
|
|
33592
33586
|
{
|
|
33593
33587
|
"name": "context",
|
|
33594
33588
|
"type": "SchematicContext",
|
|
@@ -33596,7 +33590,7 @@
|
|
|
33596
33590
|
"deprecationMessage": ""
|
|
33597
33591
|
}
|
|
33598
33592
|
],
|
|
33599
|
-
"returnType": "
|
|
33593
|
+
"returnType": "string",
|
|
33600
33594
|
"jsdoctags": [
|
|
33601
33595
|
{
|
|
33602
33596
|
"name": "source",
|
|
@@ -33616,15 +33610,6 @@
|
|
|
33616
33610
|
"text": "param"
|
|
33617
33611
|
}
|
|
33618
33612
|
},
|
|
33619
|
-
{
|
|
33620
|
-
"name": "tree",
|
|
33621
|
-
"type": "Tree",
|
|
33622
|
-
"deprecated": false,
|
|
33623
|
-
"deprecationMessage": "",
|
|
33624
|
-
"tagName": {
|
|
33625
|
-
"text": "param"
|
|
33626
|
-
}
|
|
33627
|
-
},
|
|
33628
33613
|
{
|
|
33629
33614
|
"name": "context",
|
|
33630
33615
|
"type": "SchematicContext",
|
|
@@ -33700,7 +33685,7 @@
|
|
|
33700
33685
|
},
|
|
33701
33686
|
{
|
|
33702
33687
|
"name": "migrateTypeScript",
|
|
33703
|
-
"file": "packages/core/schematics/migrate-
|
|
33688
|
+
"file": "packages/core/schematics/migrate-to-signals/index.ts",
|
|
33704
33689
|
"ctype": "miscellaneous",
|
|
33705
33690
|
"subtype": "function",
|
|
33706
33691
|
"coverageIgnore": false,
|
|
@@ -33722,6 +33707,12 @@
|
|
|
33722
33707
|
"deprecated": false,
|
|
33723
33708
|
"deprecationMessage": ""
|
|
33724
33709
|
},
|
|
33710
|
+
{
|
|
33711
|
+
"name": "tree",
|
|
33712
|
+
"type": "Tree",
|
|
33713
|
+
"deprecated": false,
|
|
33714
|
+
"deprecationMessage": ""
|
|
33715
|
+
},
|
|
33725
33716
|
{
|
|
33726
33717
|
"name": "context",
|
|
33727
33718
|
"type": "SchematicContext",
|
|
@@ -33729,7 +33720,7 @@
|
|
|
33729
33720
|
"deprecationMessage": ""
|
|
33730
33721
|
}
|
|
33731
33722
|
],
|
|
33732
|
-
"returnType": "
|
|
33723
|
+
"returnType": "MigrateResult",
|
|
33733
33724
|
"jsdoctags": [
|
|
33734
33725
|
{
|
|
33735
33726
|
"name": "source",
|
|
@@ -33749,6 +33740,15 @@
|
|
|
33749
33740
|
"text": "param"
|
|
33750
33741
|
}
|
|
33751
33742
|
},
|
|
33743
|
+
{
|
|
33744
|
+
"name": "tree",
|
|
33745
|
+
"type": "Tree",
|
|
33746
|
+
"deprecated": false,
|
|
33747
|
+
"deprecationMessage": "",
|
|
33748
|
+
"tagName": {
|
|
33749
|
+
"text": "param"
|
|
33750
|
+
}
|
|
33751
|
+
},
|
|
33752
33752
|
{
|
|
33753
33753
|
"name": "context",
|
|
33754
33754
|
"type": "SchematicContext",
|
|
@@ -34139,7 +34139,7 @@
|
|
|
34139
34139
|
},
|
|
34140
34140
|
{
|
|
34141
34141
|
"name": "removeImportSpecifier",
|
|
34142
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
34142
|
+
"file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
|
|
34143
34143
|
"ctype": "miscellaneous",
|
|
34144
34144
|
"subtype": "function",
|
|
34145
34145
|
"coverageIgnore": false,
|
|
@@ -34208,7 +34208,7 @@
|
|
|
34208
34208
|
},
|
|
34209
34209
|
{
|
|
34210
34210
|
"name": "removeImportSpecifier",
|
|
34211
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
34211
|
+
"file": "packages/core/schematics/migrate-eui-tooltip/index.ts",
|
|
34212
34212
|
"ctype": "miscellaneous",
|
|
34213
34213
|
"subtype": "function",
|
|
34214
34214
|
"coverageIgnore": false,
|
|
@@ -35601,7 +35601,7 @@
|
|
|
35601
35601
|
},
|
|
35602
35602
|
{
|
|
35603
35603
|
"name": "unwrapExpression",
|
|
35604
|
-
"file": "packages/core/schematics/migrate-
|
|
35604
|
+
"file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
|
|
35605
35605
|
"ctype": "miscellaneous",
|
|
35606
35606
|
"subtype": "function",
|
|
35607
35607
|
"coverageIgnore": false,
|
|
@@ -35631,7 +35631,7 @@
|
|
|
35631
35631
|
},
|
|
35632
35632
|
{
|
|
35633
35633
|
"name": "unwrapExpression",
|
|
35634
|
-
"file": "packages/core/schematics/migrate-
|
|
35634
|
+
"file": "packages/core/schematics/migrate-to-signals/index.ts",
|
|
35635
35635
|
"ctype": "miscellaneous",
|
|
35636
35636
|
"subtype": "function",
|
|
35637
35637
|
"coverageIgnore": false,
|
|
@@ -35854,7 +35854,7 @@
|
|
|
35854
35854
|
},
|
|
35855
35855
|
{
|
|
35856
35856
|
"name": "visitDir",
|
|
35857
|
-
"file": "packages/core/schematics/
|
|
35857
|
+
"file": "packages/core/schematics/add-eui-imports/index.ts",
|
|
35858
35858
|
"ctype": "miscellaneous",
|
|
35859
35859
|
"subtype": "function",
|
|
35860
35860
|
"coverageIgnore": false,
|
|
@@ -35899,7 +35899,7 @@
|
|
|
35899
35899
|
},
|
|
35900
35900
|
{
|
|
35901
35901
|
"name": "visitDir",
|
|
35902
|
-
"file": "packages/core/schematics/
|
|
35902
|
+
"file": "packages/core/schematics/fix-no-multiple-empty-lines/index.ts",
|
|
35903
35903
|
"ctype": "miscellaneous",
|
|
35904
35904
|
"subtype": "function",
|
|
35905
35905
|
"coverageIgnore": false,
|
|
@@ -35944,7 +35944,7 @@
|
|
|
35944
35944
|
},
|
|
35945
35945
|
{
|
|
35946
35946
|
"name": "visitDir",
|
|
35947
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
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-
|
|
35992
|
+
"file": "packages/core/schematics/migrate-eui-alert/index.ts",
|
|
35993
35993
|
"ctype": "miscellaneous",
|
|
35994
35994
|
"subtype": "function",
|
|
35995
35995
|
"coverageIgnore": false,
|
|
@@ -36034,7 +36034,7 @@
|
|
|
36034
36034
|
},
|
|
36035
36035
|
{
|
|
36036
36036
|
"name": "visitDir",
|
|
36037
|
-
"file": "packages/core/schematics/
|
|
36037
|
+
"file": "packages/core/schematics/migrate-eui-avatar/index.ts",
|
|
36038
36038
|
"ctype": "miscellaneous",
|
|
36039
36039
|
"subtype": "function",
|
|
36040
36040
|
"coverageIgnore": false,
|
|
@@ -36439,7 +36439,7 @@
|
|
|
36439
36439
|
},
|
|
36440
36440
|
{
|
|
36441
36441
|
"name": "visitDir",
|
|
36442
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
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-
|
|
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-
|
|
36532
|
+
"file": "packages/core/schematics/migrate-eui-table/index.ts",
|
|
36533
36533
|
"ctype": "miscellaneous",
|
|
36534
36534
|
"subtype": "function",
|
|
36535
36535
|
"coverageIgnore": false,
|
|
@@ -36619,7 +36619,7 @@
|
|
|
36619
36619
|
},
|
|
36620
36620
|
{
|
|
36621
36621
|
"name": "visitDir",
|
|
36622
|
-
"file": "packages/core/schematics/migrate-
|
|
36622
|
+
"file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
|
|
36623
36623
|
"ctype": "miscellaneous",
|
|
36624
36624
|
"subtype": "function",
|
|
36625
36625
|
"coverageIgnore": false,
|
|
@@ -36664,7 +36664,7 @@
|
|
|
36664
36664
|
},
|
|
36665
36665
|
{
|
|
36666
36666
|
"name": "visitDir",
|
|
36667
|
-
"file": "packages/core/schematics/migrate-
|
|
36667
|
+
"file": "packages/core/schematics/migrate-eui-tooltip/index.ts",
|
|
36668
36668
|
"ctype": "miscellaneous",
|
|
36669
36669
|
"subtype": "function",
|
|
36670
36670
|
"coverageIgnore": false,
|
|
@@ -36709,7 +36709,7 @@
|
|
|
36709
36709
|
},
|
|
36710
36710
|
{
|
|
36711
36711
|
"name": "visitDir",
|
|
36712
|
-
"file": "packages/core/schematics/migrate-
|
|
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-
|
|
36757
|
+
"file": "packages/core/schematics/migrate-to-standalone/index.ts",
|
|
36758
36758
|
"ctype": "miscellaneous",
|
|
36759
36759
|
"subtype": "function",
|
|
36760
36760
|
"coverageIgnore": false,
|
|
@@ -37347,7 +37347,7 @@
|
|
|
37347
37347
|
},
|
|
37348
37348
|
{
|
|
37349
37349
|
"name": "visitNodes",
|
|
37350
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
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": "
|
|
37366
|
+
"name": "removals",
|
|
37367
37367
|
"deprecated": false,
|
|
37368
37368
|
"deprecationMessage": ""
|
|
37369
37369
|
}
|
|
@@ -37379,7 +37379,7 @@
|
|
|
37379
37379
|
}
|
|
37380
37380
|
},
|
|
37381
37381
|
{
|
|
37382
|
-
"name": "
|
|
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-
|
|
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": "
|
|
37409
|
+
"name": "edits",
|
|
37410
37410
|
"deprecated": false,
|
|
37411
37411
|
"deprecationMessage": ""
|
|
37412
37412
|
}
|
|
@@ -37422,7 +37422,7 @@
|
|
|
37422
37422
|
}
|
|
37423
37423
|
},
|
|
37424
37424
|
{
|
|
37425
|
-
"name": "
|
|
37425
|
+
"name": "edits",
|
|
37426
37426
|
"deprecated": false,
|
|
37427
37427
|
"deprecationMessage": "",
|
|
37428
37428
|
"tagName": {
|
|
@@ -47529,10 +47529,10 @@
|
|
|
47529
47529
|
]
|
|
47530
47530
|
}
|
|
47531
47531
|
],
|
|
47532
|
-
"packages/core/schematics/migrate-
|
|
47532
|
+
"packages/core/schematics/migrate-eui-toolbar-menu/index.ts": [
|
|
47533
47533
|
{
|
|
47534
47534
|
"name": "applyEdits",
|
|
47535
|
-
"file": "packages/core/schematics/migrate-
|
|
47535
|
+
"file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
|
|
47536
47536
|
"ctype": "miscellaneous",
|
|
47537
47537
|
"subtype": "function",
|
|
47538
47538
|
"coverageIgnore": false,
|
|
@@ -47576,19 +47576,20 @@
|
|
|
47576
47576
|
]
|
|
47577
47577
|
},
|
|
47578
47578
|
{
|
|
47579
|
-
"name": "
|
|
47580
|
-
"file": "packages/core/schematics/migrate-
|
|
47579
|
+
"name": "collectOutputRemovals",
|
|
47580
|
+
"file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
|
|
47581
47581
|
"ctype": "miscellaneous",
|
|
47582
47582
|
"subtype": "function",
|
|
47583
47583
|
"coverageIgnore": false,
|
|
47584
47584
|
"deprecated": false,
|
|
47585
47585
|
"deprecationMessage": "",
|
|
47586
|
-
"rawdescription": "
|
|
47587
|
-
"description": "
|
|
47588
|
-
"displayName": "
|
|
47586
|
+
"rawdescription": "",
|
|
47587
|
+
"description": "",
|
|
47588
|
+
"displayName": "collectOutputRemovals",
|
|
47589
47589
|
"args": [
|
|
47590
47590
|
{
|
|
47591
|
-
"name": "
|
|
47591
|
+
"name": "element",
|
|
47592
|
+
"type": "TmplAstElement",
|
|
47592
47593
|
"deprecated": false,
|
|
47593
47594
|
"deprecationMessage": ""
|
|
47594
47595
|
},
|
|
@@ -47598,6 +47599,11 @@
|
|
|
47598
47599
|
"deprecated": false,
|
|
47599
47600
|
"deprecationMessage": ""
|
|
47600
47601
|
},
|
|
47602
|
+
{
|
|
47603
|
+
"name": "edits",
|
|
47604
|
+
"deprecated": false,
|
|
47605
|
+
"deprecationMessage": ""
|
|
47606
|
+
},
|
|
47601
47607
|
{
|
|
47602
47608
|
"name": "filePath",
|
|
47603
47609
|
"type": "string",
|
|
@@ -47605,16 +47611,17 @@
|
|
|
47605
47611
|
"deprecationMessage": ""
|
|
47606
47612
|
},
|
|
47607
47613
|
{
|
|
47608
|
-
"name": "
|
|
47609
|
-
"type": "
|
|
47614
|
+
"name": "context",
|
|
47615
|
+
"type": "SchematicContext",
|
|
47610
47616
|
"deprecated": false,
|
|
47611
47617
|
"deprecationMessage": ""
|
|
47612
47618
|
}
|
|
47613
47619
|
],
|
|
47614
|
-
"returnType": "
|
|
47620
|
+
"returnType": "void",
|
|
47615
47621
|
"jsdoctags": [
|
|
47616
47622
|
{
|
|
47617
|
-
"name": "
|
|
47623
|
+
"name": "element",
|
|
47624
|
+
"type": "TmplAstElement",
|
|
47618
47625
|
"deprecated": false,
|
|
47619
47626
|
"deprecationMessage": "",
|
|
47620
47627
|
"tagName": {
|
|
@@ -47630,6 +47637,14 @@
|
|
|
47630
47637
|
"text": "param"
|
|
47631
47638
|
}
|
|
47632
47639
|
},
|
|
47640
|
+
{
|
|
47641
|
+
"name": "edits",
|
|
47642
|
+
"deprecated": false,
|
|
47643
|
+
"deprecationMessage": "",
|
|
47644
|
+
"tagName": {
|
|
47645
|
+
"text": "param"
|
|
47646
|
+
}
|
|
47647
|
+
},
|
|
47633
47648
|
{
|
|
47634
47649
|
"name": "filePath",
|
|
47635
47650
|
"type": "string",
|
|
@@ -47640,8 +47655,8 @@
|
|
|
47640
47655
|
}
|
|
47641
47656
|
},
|
|
47642
47657
|
{
|
|
47643
|
-
"name": "
|
|
47644
|
-
"type": "
|
|
47658
|
+
"name": "context",
|
|
47659
|
+
"type": "SchematicContext",
|
|
47645
47660
|
"deprecated": false,
|
|
47646
47661
|
"deprecationMessage": "",
|
|
47647
47662
|
"tagName": {
|
|
@@ -47651,19 +47666,20 @@
|
|
|
47651
47666
|
]
|
|
47652
47667
|
},
|
|
47653
47668
|
{
|
|
47654
|
-
"name": "
|
|
47655
|
-
"file": "packages/core/schematics/migrate-
|
|
47669
|
+
"name": "collectTagRenames",
|
|
47670
|
+
"file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
|
|
47656
47671
|
"ctype": "miscellaneous",
|
|
47657
47672
|
"subtype": "function",
|
|
47658
47673
|
"coverageIgnore": false,
|
|
47659
47674
|
"deprecated": false,
|
|
47660
47675
|
"deprecationMessage": "",
|
|
47661
|
-
"rawdescription": "
|
|
47662
|
-
"description": "
|
|
47663
|
-
"displayName": "
|
|
47676
|
+
"rawdescription": "",
|
|
47677
|
+
"description": "",
|
|
47678
|
+
"displayName": "collectTagRenames",
|
|
47664
47679
|
"args": [
|
|
47665
47680
|
{
|
|
47666
|
-
"name": "
|
|
47681
|
+
"name": "element",
|
|
47682
|
+
"type": "TmplAstElement",
|
|
47667
47683
|
"deprecated": false,
|
|
47668
47684
|
"deprecationMessage": ""
|
|
47669
47685
|
},
|
|
@@ -47674,16 +47690,16 @@
|
|
|
47674
47690
|
"deprecationMessage": ""
|
|
47675
47691
|
},
|
|
47676
47692
|
{
|
|
47677
|
-
"name": "
|
|
47678
|
-
"type": "Map",
|
|
47693
|
+
"name": "edits",
|
|
47679
47694
|
"deprecated": false,
|
|
47680
47695
|
"deprecationMessage": ""
|
|
47681
47696
|
}
|
|
47682
47697
|
],
|
|
47683
|
-
"returnType": "
|
|
47698
|
+
"returnType": "void",
|
|
47684
47699
|
"jsdoctags": [
|
|
47685
47700
|
{
|
|
47686
|
-
"name": "
|
|
47701
|
+
"name": "element",
|
|
47702
|
+
"type": "TmplAstElement",
|
|
47687
47703
|
"deprecated": false,
|
|
47688
47704
|
"deprecationMessage": "",
|
|
47689
47705
|
"tagName": {
|
|
@@ -47700,53 +47716,7 @@
|
|
|
47700
47716
|
}
|
|
47701
47717
|
},
|
|
47702
47718
|
{
|
|
47703
|
-
"name": "
|
|
47704
|
-
"type": "Map",
|
|
47705
|
-
"deprecated": false,
|
|
47706
|
-
"deprecationMessage": "",
|
|
47707
|
-
"tagName": {
|
|
47708
|
-
"text": "param"
|
|
47709
|
-
}
|
|
47710
|
-
}
|
|
47711
|
-
]
|
|
47712
|
-
},
|
|
47713
|
-
{
|
|
47714
|
-
"name": "collectTemplateRefs",
|
|
47715
|
-
"file": "packages/core/schematics/migrate-to-signals/index.ts",
|
|
47716
|
-
"ctype": "miscellaneous",
|
|
47717
|
-
"subtype": "function",
|
|
47718
|
-
"coverageIgnore": false,
|
|
47719
|
-
"deprecated": false,
|
|
47720
|
-
"deprecationMessage": "",
|
|
47721
|
-
"rawdescription": "Recursively walks template AST nodes, collecting #refName → EUI class name mappings.\n\nFor each element with a template reference (#ref), we try to resolve its EUI class:\n1. Match element tag name against EUI_SELECTOR_TO_CLASS (e.g. 'eui-alert' → EuiAlertComponent)\n2. Match element attributes against attribute selectors (e.g. [euiAutocomplete] → EuiAutocompleteComponent)",
|
|
47722
|
-
"description": "<p>Recursively walks template AST nodes, collecting #refName → EUI class name mappings.</p>\n<p>For each element with a template reference (#ref), we try to resolve its EUI class:</p>\n<ol>\n<li>Match element tag name against EUI_SELECTOR_TO_CLASS (e.g. 'eui-alert' → EuiAlertComponent)</li>\n<li>Match element attributes against attribute selectors (e.g. [euiAutocomplete] → EuiAutocompleteComponent)</li>\n</ol>\n",
|
|
47723
|
-
"displayName": "collectTemplateRefs",
|
|
47724
|
-
"args": [
|
|
47725
|
-
{
|
|
47726
|
-
"name": "nodes",
|
|
47727
|
-
"deprecated": false,
|
|
47728
|
-
"deprecationMessage": ""
|
|
47729
|
-
},
|
|
47730
|
-
{
|
|
47731
|
-
"name": "refMap",
|
|
47732
|
-
"type": "Map",
|
|
47733
|
-
"deprecated": false,
|
|
47734
|
-
"deprecationMessage": ""
|
|
47735
|
-
}
|
|
47736
|
-
],
|
|
47737
|
-
"returnType": "void",
|
|
47738
|
-
"jsdoctags": [
|
|
47739
|
-
{
|
|
47740
|
-
"name": "nodes",
|
|
47741
|
-
"deprecated": false,
|
|
47742
|
-
"deprecationMessage": "",
|
|
47743
|
-
"tagName": {
|
|
47744
|
-
"text": "param"
|
|
47745
|
-
}
|
|
47746
|
-
},
|
|
47747
|
-
{
|
|
47748
|
-
"name": "refMap",
|
|
47749
|
-
"type": "Map",
|
|
47719
|
+
"name": "edits",
|
|
47750
47720
|
"deprecated": false,
|
|
47751
47721
|
"deprecationMessage": "",
|
|
47752
47722
|
"tagName": {
|
|
@@ -47757,7 +47727,7 @@
|
|
|
47757
47727
|
},
|
|
47758
47728
|
{
|
|
47759
47729
|
"name": "deduplicateEdits",
|
|
47760
|
-
"file": "packages/core/schematics/migrate-
|
|
47730
|
+
"file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
|
|
47761
47731
|
"ctype": "miscellaneous",
|
|
47762
47732
|
"subtype": "function",
|
|
47763
47733
|
"coverageIgnore": false,
|
|
@@ -47786,42 +47756,27 @@
|
|
|
47786
47756
|
]
|
|
47787
47757
|
},
|
|
47788
47758
|
{
|
|
47789
|
-
"name": "
|
|
47790
|
-
"file": "packages/core/schematics/migrate-
|
|
47759
|
+
"name": "isComponentMetadataProperty",
|
|
47760
|
+
"file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
|
|
47791
47761
|
"ctype": "miscellaneous",
|
|
47792
47762
|
"subtype": "function",
|
|
47793
47763
|
"coverageIgnore": false,
|
|
47794
47764
|
"deprecated": false,
|
|
47795
47765
|
"deprecationMessage": "",
|
|
47796
|
-
"rawdescription": "
|
|
47797
|
-
"description": "
|
|
47798
|
-
"displayName": "
|
|
47766
|
+
"rawdescription": "",
|
|
47767
|
+
"description": "",
|
|
47768
|
+
"displayName": "isComponentMetadataProperty",
|
|
47799
47769
|
"args": [
|
|
47800
47770
|
{
|
|
47801
|
-
"name": "
|
|
47802
|
-
"deprecated": false,
|
|
47803
|
-
"deprecationMessage": ""
|
|
47804
|
-
},
|
|
47805
|
-
{
|
|
47806
|
-
"name": "source",
|
|
47807
|
-
"type": "string",
|
|
47771
|
+
"name": "node",
|
|
47808
47772
|
"deprecated": false,
|
|
47809
47773
|
"deprecationMessage": ""
|
|
47810
47774
|
}
|
|
47811
47775
|
],
|
|
47812
|
-
"returnType": "
|
|
47776
|
+
"returnType": "boolean",
|
|
47813
47777
|
"jsdoctags": [
|
|
47814
47778
|
{
|
|
47815
|
-
"name": "
|
|
47816
|
-
"deprecated": false,
|
|
47817
|
-
"deprecationMessage": "",
|
|
47818
|
-
"tagName": {
|
|
47819
|
-
"text": "param"
|
|
47820
|
-
}
|
|
47821
|
-
},
|
|
47822
|
-
{
|
|
47823
|
-
"name": "source",
|
|
47824
|
-
"type": "string",
|
|
47779
|
+
"name": "node",
|
|
47825
47780
|
"deprecated": false,
|
|
47826
47781
|
"deprecationMessage": "",
|
|
47827
47782
|
"tagName": {
|
|
@@ -47831,8 +47786,8 @@
|
|
|
47831
47786
|
]
|
|
47832
47787
|
},
|
|
47833
47788
|
{
|
|
47834
|
-
"name": "
|
|
47835
|
-
"file": "packages/core/schematics/migrate-
|
|
47789
|
+
"name": "isTemplateProperty",
|
|
47790
|
+
"file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
|
|
47836
47791
|
"ctype": "miscellaneous",
|
|
47837
47792
|
"subtype": "function",
|
|
47838
47793
|
"coverageIgnore": false,
|
|
@@ -47840,18 +47795,18 @@
|
|
|
47840
47795
|
"deprecationMessage": "",
|
|
47841
47796
|
"rawdescription": "",
|
|
47842
47797
|
"description": "",
|
|
47843
|
-
"displayName": "
|
|
47798
|
+
"displayName": "isTemplateProperty",
|
|
47844
47799
|
"args": [
|
|
47845
47800
|
{
|
|
47846
|
-
"name": "
|
|
47801
|
+
"name": "node",
|
|
47847
47802
|
"deprecated": false,
|
|
47848
47803
|
"deprecationMessage": ""
|
|
47849
47804
|
}
|
|
47850
47805
|
],
|
|
47851
|
-
"returnType": "
|
|
47806
|
+
"returnType": "boolean",
|
|
47852
47807
|
"jsdoctags": [
|
|
47853
47808
|
{
|
|
47854
|
-
"name": "
|
|
47809
|
+
"name": "node",
|
|
47855
47810
|
"deprecated": false,
|
|
47856
47811
|
"deprecationMessage": "",
|
|
47857
47812
|
"tagName": {
|
|
@@ -47861,8 +47816,8 @@
|
|
|
47861
47816
|
]
|
|
47862
47817
|
},
|
|
47863
47818
|
{
|
|
47864
|
-
"name": "
|
|
47865
|
-
"file": "packages/core/schematics/migrate-
|
|
47819
|
+
"name": "migrateEuiToolbarMenu",
|
|
47820
|
+
"file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
|
|
47866
47821
|
"ctype": "miscellaneous",
|
|
47867
47822
|
"subtype": "function",
|
|
47868
47823
|
"coverageIgnore": false,
|
|
@@ -47870,20 +47825,24 @@
|
|
|
47870
47825
|
"deprecationMessage": "",
|
|
47871
47826
|
"rawdescription": "",
|
|
47872
47827
|
"description": "",
|
|
47873
|
-
"displayName": "
|
|
47828
|
+
"displayName": "migrateEuiToolbarMenu",
|
|
47874
47829
|
"args": [
|
|
47875
47830
|
{
|
|
47876
|
-
"name": "
|
|
47831
|
+
"name": "options",
|
|
47832
|
+
"type": "Schema",
|
|
47877
47833
|
"deprecated": false,
|
|
47878
|
-
"deprecationMessage": ""
|
|
47834
|
+
"deprecationMessage": "",
|
|
47835
|
+
"defaultValue": "{}"
|
|
47879
47836
|
}
|
|
47880
47837
|
],
|
|
47881
|
-
"returnType": "
|
|
47838
|
+
"returnType": "Rule",
|
|
47882
47839
|
"jsdoctags": [
|
|
47883
47840
|
{
|
|
47884
|
-
"name": "
|
|
47841
|
+
"name": "options",
|
|
47842
|
+
"type": "Schema",
|
|
47885
47843
|
"deprecated": false,
|
|
47886
47844
|
"deprecationMessage": "",
|
|
47845
|
+
"defaultValue": "{}",
|
|
47887
47846
|
"tagName": {
|
|
47888
47847
|
"text": "param"
|
|
47889
47848
|
}
|
|
@@ -47891,25 +47850,37 @@
|
|
|
47891
47850
|
]
|
|
47892
47851
|
},
|
|
47893
47852
|
{
|
|
47894
|
-
"name": "
|
|
47895
|
-
"file": "packages/core/schematics/migrate-
|
|
47853
|
+
"name": "migrateImportsAndTypes",
|
|
47854
|
+
"file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
|
|
47896
47855
|
"ctype": "miscellaneous",
|
|
47897
47856
|
"subtype": "function",
|
|
47898
47857
|
"coverageIgnore": false,
|
|
47899
47858
|
"deprecated": false,
|
|
47900
47859
|
"deprecationMessage": "",
|
|
47901
|
-
"rawdescription": "
|
|
47902
|
-
"description": "
|
|
47903
|
-
"displayName": "
|
|
47860
|
+
"rawdescription": "",
|
|
47861
|
+
"description": "",
|
|
47862
|
+
"displayName": "migrateImportsAndTypes",
|
|
47904
47863
|
"args": [
|
|
47905
47864
|
{
|
|
47906
47865
|
"name": "source",
|
|
47907
47866
|
"type": "string",
|
|
47908
47867
|
"deprecated": false,
|
|
47909
47868
|
"deprecationMessage": ""
|
|
47869
|
+
},
|
|
47870
|
+
{
|
|
47871
|
+
"name": "filePath",
|
|
47872
|
+
"type": "string",
|
|
47873
|
+
"deprecated": false,
|
|
47874
|
+
"deprecationMessage": ""
|
|
47875
|
+
},
|
|
47876
|
+
{
|
|
47877
|
+
"name": "context",
|
|
47878
|
+
"type": "SchematicContext",
|
|
47879
|
+
"deprecated": false,
|
|
47880
|
+
"deprecationMessage": ""
|
|
47910
47881
|
}
|
|
47911
47882
|
],
|
|
47912
|
-
"returnType": "
|
|
47883
|
+
"returnType": "string",
|
|
47913
47884
|
"jsdoctags": [
|
|
47914
47885
|
{
|
|
47915
47886
|
"name": "source",
|
|
@@ -47919,33 +47890,19 @@
|
|
|
47919
47890
|
"tagName": {
|
|
47920
47891
|
"text": "param"
|
|
47921
47892
|
}
|
|
47922
|
-
}
|
|
47923
|
-
]
|
|
47924
|
-
},
|
|
47925
|
-
{
|
|
47926
|
-
"name": "hasStringRefQuery",
|
|
47927
|
-
"file": "packages/core/schematics/migrate-to-signals/index.ts",
|
|
47928
|
-
"ctype": "miscellaneous",
|
|
47929
|
-
"subtype": "function",
|
|
47930
|
-
"coverageIgnore": false,
|
|
47931
|
-
"deprecated": false,
|
|
47932
|
-
"deprecationMessage": "",
|
|
47933
|
-
"rawdescription": "Quick check: does the file have",
|
|
47934
|
-
"description": "<p>Quick check: does the file have</p>\n",
|
|
47935
|
-
"displayName": "hasStringRefQuery",
|
|
47936
|
-
"args": [
|
|
47893
|
+
},
|
|
47937
47894
|
{
|
|
47938
|
-
"name": "
|
|
47895
|
+
"name": "filePath",
|
|
47939
47896
|
"type": "string",
|
|
47940
47897
|
"deprecated": false,
|
|
47941
|
-
"deprecationMessage": ""
|
|
47942
|
-
|
|
47943
|
-
|
|
47944
|
-
|
|
47945
|
-
|
|
47898
|
+
"deprecationMessage": "",
|
|
47899
|
+
"tagName": {
|
|
47900
|
+
"text": "param"
|
|
47901
|
+
}
|
|
47902
|
+
},
|
|
47946
47903
|
{
|
|
47947
|
-
"name": "
|
|
47948
|
-
"type": "
|
|
47904
|
+
"name": "context",
|
|
47905
|
+
"type": "SchematicContext",
|
|
47949
47906
|
"deprecated": false,
|
|
47950
47907
|
"deprecationMessage": "",
|
|
47951
47908
|
"tagName": {
|
|
@@ -47955,8 +47912,8 @@
|
|
|
47955
47912
|
]
|
|
47956
47913
|
},
|
|
47957
47914
|
{
|
|
47958
|
-
"name": "
|
|
47959
|
-
"file": "packages/core/schematics/migrate-
|
|
47915
|
+
"name": "migrateInlineTemplates",
|
|
47916
|
+
"file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
|
|
47960
47917
|
"ctype": "miscellaneous",
|
|
47961
47918
|
"subtype": "function",
|
|
47962
47919
|
"coverageIgnore": false,
|
|
@@ -47964,54 +47921,52 @@
|
|
|
47964
47921
|
"deprecationMessage": "",
|
|
47965
47922
|
"rawdescription": "",
|
|
47966
47923
|
"description": "",
|
|
47967
|
-
"displayName": "
|
|
47924
|
+
"displayName": "migrateInlineTemplates",
|
|
47968
47925
|
"args": [
|
|
47969
47926
|
{
|
|
47970
|
-
"name": "
|
|
47927
|
+
"name": "source",
|
|
47928
|
+
"type": "string",
|
|
47929
|
+
"deprecated": false,
|
|
47930
|
+
"deprecationMessage": ""
|
|
47931
|
+
},
|
|
47932
|
+
{
|
|
47933
|
+
"name": "filePath",
|
|
47934
|
+
"type": "string",
|
|
47935
|
+
"deprecated": false,
|
|
47936
|
+
"deprecationMessage": ""
|
|
47937
|
+
},
|
|
47938
|
+
{
|
|
47939
|
+
"name": "context",
|
|
47940
|
+
"type": "SchematicContext",
|
|
47971
47941
|
"deprecated": false,
|
|
47972
47942
|
"deprecationMessage": ""
|
|
47973
47943
|
}
|
|
47974
47944
|
],
|
|
47975
|
-
"returnType": "
|
|
47945
|
+
"returnType": "string",
|
|
47976
47946
|
"jsdoctags": [
|
|
47977
47947
|
{
|
|
47978
|
-
"name": "
|
|
47948
|
+
"name": "source",
|
|
47949
|
+
"type": "string",
|
|
47979
47950
|
"deprecated": false,
|
|
47980
47951
|
"deprecationMessage": "",
|
|
47981
47952
|
"tagName": {
|
|
47982
47953
|
"text": "param"
|
|
47983
47954
|
}
|
|
47984
|
-
}
|
|
47985
|
-
]
|
|
47986
|
-
},
|
|
47987
|
-
{
|
|
47988
|
-
"name": "migrateToSignals",
|
|
47989
|
-
"file": "packages/core/schematics/migrate-to-signals/index.ts",
|
|
47990
|
-
"ctype": "miscellaneous",
|
|
47991
|
-
"subtype": "function",
|
|
47992
|
-
"coverageIgnore": false,
|
|
47993
|
-
"deprecated": false,
|
|
47994
|
-
"deprecationMessage": "",
|
|
47995
|
-
"rawdescription": "Migrates consumer code that accesses EUI component instances programmatically.\n\nWhen EUI migrated",
|
|
47996
|
-
"description": "<p>Migrates consumer code that accesses EUI component instances programmatically.</p>\n<p>When EUI migrated</p>\n",
|
|
47997
|
-
"displayName": "migrateToSignals",
|
|
47998
|
-
"args": [
|
|
47955
|
+
},
|
|
47999
47956
|
{
|
|
48000
|
-
"name": "
|
|
48001
|
-
"type": "
|
|
47957
|
+
"name": "filePath",
|
|
47958
|
+
"type": "string",
|
|
48002
47959
|
"deprecated": false,
|
|
48003
47960
|
"deprecationMessage": "",
|
|
48004
|
-
"
|
|
48005
|
-
|
|
48006
|
-
|
|
48007
|
-
|
|
48008
|
-
"jsdoctags": [
|
|
47961
|
+
"tagName": {
|
|
47962
|
+
"text": "param"
|
|
47963
|
+
}
|
|
47964
|
+
},
|
|
48009
47965
|
{
|
|
48010
|
-
"name": "
|
|
48011
|
-
"type": "
|
|
47966
|
+
"name": "context",
|
|
47967
|
+
"type": "SchematicContext",
|
|
48012
47968
|
"deprecated": false,
|
|
48013
47969
|
"deprecationMessage": "",
|
|
48014
|
-
"defaultValue": "{}",
|
|
48015
47970
|
"tagName": {
|
|
48016
47971
|
"text": "param"
|
|
48017
47972
|
}
|
|
@@ -48019,8 +47974,8 @@
|
|
|
48019
47974
|
]
|
|
48020
47975
|
},
|
|
48021
47976
|
{
|
|
48022
|
-
"name": "
|
|
48023
|
-
"file": "packages/core/schematics/migrate-
|
|
47977
|
+
"name": "migrateTemplate",
|
|
47978
|
+
"file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
|
|
48024
47979
|
"ctype": "miscellaneous",
|
|
48025
47980
|
"subtype": "function",
|
|
48026
47981
|
"coverageIgnore": false,
|
|
@@ -48028,7 +47983,7 @@
|
|
|
48028
47983
|
"deprecationMessage": "",
|
|
48029
47984
|
"rawdescription": "",
|
|
48030
47985
|
"description": "",
|
|
48031
|
-
"displayName": "
|
|
47986
|
+
"displayName": "migrateTemplate",
|
|
48032
47987
|
"args": [
|
|
48033
47988
|
{
|
|
48034
47989
|
"name": "source",
|
|
@@ -48042,12 +47997,6 @@
|
|
|
48042
47997
|
"deprecated": false,
|
|
48043
47998
|
"deprecationMessage": ""
|
|
48044
47999
|
},
|
|
48045
|
-
{
|
|
48046
|
-
"name": "tree",
|
|
48047
|
-
"type": "Tree",
|
|
48048
|
-
"deprecated": false,
|
|
48049
|
-
"deprecationMessage": ""
|
|
48050
|
-
},
|
|
48051
48000
|
{
|
|
48052
48001
|
"name": "context",
|
|
48053
48002
|
"type": "SchematicContext",
|
|
@@ -48055,7 +48004,7 @@
|
|
|
48055
48004
|
"deprecationMessage": ""
|
|
48056
48005
|
}
|
|
48057
48006
|
],
|
|
48058
|
-
"returnType": "
|
|
48007
|
+
"returnType": "string",
|
|
48059
48008
|
"jsdoctags": [
|
|
48060
48009
|
{
|
|
48061
48010
|
"name": "source",
|
|
@@ -48075,15 +48024,6 @@
|
|
|
48075
48024
|
"text": "param"
|
|
48076
48025
|
}
|
|
48077
48026
|
},
|
|
48078
|
-
{
|
|
48079
|
-
"name": "tree",
|
|
48080
|
-
"type": "Tree",
|
|
48081
|
-
"deprecated": false,
|
|
48082
|
-
"deprecationMessage": "",
|
|
48083
|
-
"tagName": {
|
|
48084
|
-
"text": "param"
|
|
48085
|
-
}
|
|
48086
|
-
},
|
|
48087
48027
|
{
|
|
48088
48028
|
"name": "context",
|
|
48089
48029
|
"type": "SchematicContext",
|
|
@@ -48096,29 +48036,59 @@
|
|
|
48096
48036
|
]
|
|
48097
48037
|
},
|
|
48098
48038
|
{
|
|
48099
|
-
"name": "
|
|
48100
|
-
"file": "packages/core/schematics/migrate-
|
|
48039
|
+
"name": "migrateTypeScript",
|
|
48040
|
+
"file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
|
|
48101
48041
|
"ctype": "miscellaneous",
|
|
48102
48042
|
"subtype": "function",
|
|
48103
48043
|
"coverageIgnore": false,
|
|
48104
48044
|
"deprecated": false,
|
|
48105
48045
|
"deprecationMessage": "",
|
|
48106
|
-
"rawdescription": "
|
|
48107
|
-
"description": "
|
|
48108
|
-
"displayName": "
|
|
48046
|
+
"rawdescription": "",
|
|
48047
|
+
"description": "",
|
|
48048
|
+
"displayName": "migrateTypeScript",
|
|
48109
48049
|
"args": [
|
|
48110
48050
|
{
|
|
48111
|
-
"name": "
|
|
48112
|
-
"type": "
|
|
48051
|
+
"name": "source",
|
|
48052
|
+
"type": "string",
|
|
48053
|
+
"deprecated": false,
|
|
48054
|
+
"deprecationMessage": ""
|
|
48055
|
+
},
|
|
48056
|
+
{
|
|
48057
|
+
"name": "filePath",
|
|
48058
|
+
"type": "string",
|
|
48059
|
+
"deprecated": false,
|
|
48060
|
+
"deprecationMessage": ""
|
|
48061
|
+
},
|
|
48062
|
+
{
|
|
48063
|
+
"name": "context",
|
|
48064
|
+
"type": "SchematicContext",
|
|
48113
48065
|
"deprecated": false,
|
|
48114
48066
|
"deprecationMessage": ""
|
|
48115
48067
|
}
|
|
48116
48068
|
],
|
|
48117
|
-
"returnType": "string
|
|
48069
|
+
"returnType": "string",
|
|
48118
48070
|
"jsdoctags": [
|
|
48119
48071
|
{
|
|
48120
|
-
"name": "
|
|
48121
|
-
"type": "
|
|
48072
|
+
"name": "source",
|
|
48073
|
+
"type": "string",
|
|
48074
|
+
"deprecated": false,
|
|
48075
|
+
"deprecationMessage": "",
|
|
48076
|
+
"tagName": {
|
|
48077
|
+
"text": "param"
|
|
48078
|
+
}
|
|
48079
|
+
},
|
|
48080
|
+
{
|
|
48081
|
+
"name": "filePath",
|
|
48082
|
+
"type": "string",
|
|
48083
|
+
"deprecated": false,
|
|
48084
|
+
"deprecationMessage": "",
|
|
48085
|
+
"tagName": {
|
|
48086
|
+
"text": "param"
|
|
48087
|
+
}
|
|
48088
|
+
},
|
|
48089
|
+
{
|
|
48090
|
+
"name": "context",
|
|
48091
|
+
"type": "SchematicContext",
|
|
48122
48092
|
"deprecated": false,
|
|
48123
48093
|
"deprecationMessage": "",
|
|
48124
48094
|
"tagName": {
|
|
@@ -48128,19 +48098,24 @@
|
|
|
48128
48098
|
]
|
|
48129
48099
|
},
|
|
48130
48100
|
{
|
|
48131
|
-
"name": "
|
|
48132
|
-
"file": "packages/core/schematics/migrate-
|
|
48101
|
+
"name": "removeImportSpecifier",
|
|
48102
|
+
"file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
|
|
48133
48103
|
"ctype": "miscellaneous",
|
|
48134
48104
|
"subtype": "function",
|
|
48135
48105
|
"coverageIgnore": false,
|
|
48136
48106
|
"deprecated": false,
|
|
48137
48107
|
"deprecationMessage": "",
|
|
48138
|
-
"rawdescription": "
|
|
48139
|
-
"description": "
|
|
48140
|
-
"displayName": "
|
|
48108
|
+
"rawdescription": "",
|
|
48109
|
+
"description": "",
|
|
48110
|
+
"displayName": "removeImportSpecifier",
|
|
48141
48111
|
"args": [
|
|
48142
48112
|
{
|
|
48143
|
-
"name": "
|
|
48113
|
+
"name": "namedImports",
|
|
48114
|
+
"deprecated": false,
|
|
48115
|
+
"deprecationMessage": ""
|
|
48116
|
+
},
|
|
48117
|
+
{
|
|
48118
|
+
"name": "specifier",
|
|
48144
48119
|
"deprecated": false,
|
|
48145
48120
|
"deprecationMessage": ""
|
|
48146
48121
|
},
|
|
@@ -48150,16 +48125,23 @@
|
|
|
48150
48125
|
"deprecationMessage": ""
|
|
48151
48126
|
},
|
|
48152
48127
|
{
|
|
48153
|
-
"name": "
|
|
48154
|
-
"type": "Map",
|
|
48128
|
+
"name": "edits",
|
|
48155
48129
|
"deprecated": false,
|
|
48156
48130
|
"deprecationMessage": ""
|
|
48157
48131
|
}
|
|
48158
48132
|
],
|
|
48159
|
-
"returnType": "
|
|
48133
|
+
"returnType": "void",
|
|
48160
48134
|
"jsdoctags": [
|
|
48161
48135
|
{
|
|
48162
|
-
"name": "
|
|
48136
|
+
"name": "namedImports",
|
|
48137
|
+
"deprecated": false,
|
|
48138
|
+
"deprecationMessage": "",
|
|
48139
|
+
"tagName": {
|
|
48140
|
+
"text": "param"
|
|
48141
|
+
}
|
|
48142
|
+
},
|
|
48143
|
+
{
|
|
48144
|
+
"name": "specifier",
|
|
48163
48145
|
"deprecated": false,
|
|
48164
48146
|
"deprecationMessage": "",
|
|
48165
48147
|
"tagName": {
|
|
@@ -48175,8 +48157,7 @@
|
|
|
48175
48157
|
}
|
|
48176
48158
|
},
|
|
48177
48159
|
{
|
|
48178
|
-
"name": "
|
|
48179
|
-
"type": "Map",
|
|
48160
|
+
"name": "edits",
|
|
48180
48161
|
"deprecated": false,
|
|
48181
48162
|
"deprecationMessage": "",
|
|
48182
48163
|
"tagName": {
|
|
@@ -48186,27 +48167,27 @@
|
|
|
48186
48167
|
]
|
|
48187
48168
|
},
|
|
48188
48169
|
{
|
|
48189
|
-
"name": "
|
|
48190
|
-
"file": "packages/core/schematics/migrate-
|
|
48170
|
+
"name": "unwrapExpression",
|
|
48171
|
+
"file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
|
|
48191
48172
|
"ctype": "miscellaneous",
|
|
48192
48173
|
"subtype": "function",
|
|
48193
48174
|
"coverageIgnore": false,
|
|
48194
48175
|
"deprecated": false,
|
|
48195
48176
|
"deprecationMessage": "",
|
|
48196
|
-
"rawdescription": "
|
|
48197
|
-
"description": "
|
|
48198
|
-
"displayName": "
|
|
48177
|
+
"rawdescription": "",
|
|
48178
|
+
"description": "",
|
|
48179
|
+
"displayName": "unwrapExpression",
|
|
48199
48180
|
"args": [
|
|
48200
48181
|
{
|
|
48201
|
-
"name": "
|
|
48182
|
+
"name": "expression",
|
|
48202
48183
|
"deprecated": false,
|
|
48203
48184
|
"deprecationMessage": ""
|
|
48204
48185
|
}
|
|
48205
48186
|
],
|
|
48206
|
-
"returnType": "
|
|
48187
|
+
"returnType": "ts.Expression",
|
|
48207
48188
|
"jsdoctags": [
|
|
48208
48189
|
{
|
|
48209
|
-
"name": "
|
|
48190
|
+
"name": "expression",
|
|
48210
48191
|
"deprecated": false,
|
|
48211
48192
|
"deprecationMessage": "",
|
|
48212
48193
|
"tagName": {
|
|
@@ -48216,30 +48197,64 @@
|
|
|
48216
48197
|
]
|
|
48217
48198
|
},
|
|
48218
48199
|
{
|
|
48219
|
-
"name": "
|
|
48220
|
-
"file": "packages/core/schematics/migrate-
|
|
48200
|
+
"name": "visitDir",
|
|
48201
|
+
"file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
|
|
48221
48202
|
"ctype": "miscellaneous",
|
|
48222
48203
|
"subtype": "function",
|
|
48223
48204
|
"coverageIgnore": false,
|
|
48224
48205
|
"deprecated": false,
|
|
48225
48206
|
"deprecationMessage": "",
|
|
48226
|
-
"rawdescription": "
|
|
48227
|
-
"description": "
|
|
48228
|
-
"displayName": "
|
|
48207
|
+
"rawdescription": "",
|
|
48208
|
+
"description": "",
|
|
48209
|
+
"displayName": "visitDir",
|
|
48229
48210
|
"args": [
|
|
48230
48211
|
{
|
|
48231
|
-
"name": "
|
|
48212
|
+
"name": "dir",
|
|
48213
|
+
"type": "DirEntry",
|
|
48232
48214
|
"deprecated": false,
|
|
48233
48215
|
"deprecationMessage": ""
|
|
48234
48216
|
},
|
|
48235
48217
|
{
|
|
48236
|
-
"name": "
|
|
48237
|
-
"type": "Map",
|
|
48218
|
+
"name": "callback",
|
|
48238
48219
|
"deprecated": false,
|
|
48239
48220
|
"deprecationMessage": ""
|
|
48221
|
+
}
|
|
48222
|
+
],
|
|
48223
|
+
"returnType": "void",
|
|
48224
|
+
"jsdoctags": [
|
|
48225
|
+
{
|
|
48226
|
+
"name": "dir",
|
|
48227
|
+
"type": "DirEntry",
|
|
48228
|
+
"deprecated": false,
|
|
48229
|
+
"deprecationMessage": "",
|
|
48230
|
+
"tagName": {
|
|
48231
|
+
"text": "param"
|
|
48232
|
+
}
|
|
48240
48233
|
},
|
|
48241
48234
|
{
|
|
48242
|
-
"name": "
|
|
48235
|
+
"name": "callback",
|
|
48236
|
+
"deprecated": false,
|
|
48237
|
+
"deprecationMessage": "",
|
|
48238
|
+
"tagName": {
|
|
48239
|
+
"text": "param"
|
|
48240
|
+
}
|
|
48241
|
+
}
|
|
48242
|
+
]
|
|
48243
|
+
},
|
|
48244
|
+
{
|
|
48245
|
+
"name": "visitNodes",
|
|
48246
|
+
"file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
|
|
48247
|
+
"ctype": "miscellaneous",
|
|
48248
|
+
"subtype": "function",
|
|
48249
|
+
"coverageIgnore": false,
|
|
48250
|
+
"deprecated": false,
|
|
48251
|
+
"deprecationMessage": "",
|
|
48252
|
+
"rawdescription": "",
|
|
48253
|
+
"description": "",
|
|
48254
|
+
"displayName": "visitNodes",
|
|
48255
|
+
"args": [
|
|
48256
|
+
{
|
|
48257
|
+
"name": "nodes",
|
|
48243
48258
|
"deprecated": false,
|
|
48244
48259
|
"deprecationMessage": ""
|
|
48245
48260
|
},
|
|
@@ -48248,12 +48263,29 @@
|
|
|
48248
48263
|
"type": "string",
|
|
48249
48264
|
"deprecated": false,
|
|
48250
48265
|
"deprecationMessage": ""
|
|
48266
|
+
},
|
|
48267
|
+
{
|
|
48268
|
+
"name": "edits",
|
|
48269
|
+
"deprecated": false,
|
|
48270
|
+
"deprecationMessage": ""
|
|
48271
|
+
},
|
|
48272
|
+
{
|
|
48273
|
+
"name": "filePath",
|
|
48274
|
+
"type": "string",
|
|
48275
|
+
"deprecated": false,
|
|
48276
|
+
"deprecationMessage": ""
|
|
48277
|
+
},
|
|
48278
|
+
{
|
|
48279
|
+
"name": "context",
|
|
48280
|
+
"type": "SchematicContext",
|
|
48281
|
+
"deprecated": false,
|
|
48282
|
+
"deprecationMessage": ""
|
|
48251
48283
|
}
|
|
48252
48284
|
],
|
|
48253
|
-
"returnType": "
|
|
48285
|
+
"returnType": "void",
|
|
48254
48286
|
"jsdoctags": [
|
|
48255
48287
|
{
|
|
48256
|
-
"name": "
|
|
48288
|
+
"name": "nodes",
|
|
48257
48289
|
"deprecated": false,
|
|
48258
48290
|
"deprecationMessage": "",
|
|
48259
48291
|
"tagName": {
|
|
@@ -48261,8 +48293,8 @@
|
|
|
48261
48293
|
}
|
|
48262
48294
|
},
|
|
48263
48295
|
{
|
|
48264
|
-
"name": "
|
|
48265
|
-
"type": "
|
|
48296
|
+
"name": "source",
|
|
48297
|
+
"type": "string",
|
|
48266
48298
|
"deprecated": false,
|
|
48267
48299
|
"deprecationMessage": "",
|
|
48268
48300
|
"tagName": {
|
|
@@ -48270,7 +48302,7 @@
|
|
|
48270
48302
|
}
|
|
48271
48303
|
},
|
|
48272
48304
|
{
|
|
48273
|
-
"name": "
|
|
48305
|
+
"name": "edits",
|
|
48274
48306
|
"deprecated": false,
|
|
48275
48307
|
"deprecationMessage": "",
|
|
48276
48308
|
"tagName": {
|
|
@@ -48278,38 +48310,17 @@
|
|
|
48278
48310
|
}
|
|
48279
48311
|
},
|
|
48280
48312
|
{
|
|
48281
|
-
"name": "
|
|
48313
|
+
"name": "filePath",
|
|
48282
48314
|
"type": "string",
|
|
48283
48315
|
"deprecated": false,
|
|
48284
48316
|
"deprecationMessage": "",
|
|
48285
48317
|
"tagName": {
|
|
48286
48318
|
"text": "param"
|
|
48287
48319
|
}
|
|
48288
|
-
}
|
|
48289
|
-
]
|
|
48290
|
-
},
|
|
48291
|
-
{
|
|
48292
|
-
"name": "unwrapExpression",
|
|
48293
|
-
"file": "packages/core/schematics/migrate-to-signals/index.ts",
|
|
48294
|
-
"ctype": "miscellaneous",
|
|
48295
|
-
"subtype": "function",
|
|
48296
|
-
"coverageIgnore": false,
|
|
48297
|
-
"deprecated": false,
|
|
48298
|
-
"deprecationMessage": "",
|
|
48299
|
-
"rawdescription": "",
|
|
48300
|
-
"description": "",
|
|
48301
|
-
"displayName": "unwrapExpression",
|
|
48302
|
-
"args": [
|
|
48303
|
-
{
|
|
48304
|
-
"name": "expression",
|
|
48305
|
-
"deprecated": false,
|
|
48306
|
-
"deprecationMessage": ""
|
|
48307
|
-
}
|
|
48308
|
-
],
|
|
48309
|
-
"returnType": "ts.Expression",
|
|
48310
|
-
"jsdoctags": [
|
|
48320
|
+
},
|
|
48311
48321
|
{
|
|
48312
|
-
"name": "
|
|
48322
|
+
"name": "context",
|
|
48323
|
+
"type": "SchematicContext",
|
|
48313
48324
|
"deprecated": false,
|
|
48314
48325
|
"deprecationMessage": "",
|
|
48315
48326
|
"tagName": {
|
|
@@ -48319,8 +48330,8 @@
|
|
|
48319
48330
|
]
|
|
48320
48331
|
},
|
|
48321
48332
|
{
|
|
48322
|
-
"name": "
|
|
48323
|
-
"file": "packages/core/schematics/migrate-
|
|
48333
|
+
"name": "warnRemovedProperties",
|
|
48334
|
+
"file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
|
|
48324
48335
|
"ctype": "miscellaneous",
|
|
48325
48336
|
"subtype": "function",
|
|
48326
48337
|
"coverageIgnore": false,
|
|
@@ -48328,16 +48339,22 @@
|
|
|
48328
48339
|
"deprecationMessage": "",
|
|
48329
48340
|
"rawdescription": "",
|
|
48330
48341
|
"description": "",
|
|
48331
|
-
"displayName": "
|
|
48342
|
+
"displayName": "warnRemovedProperties",
|
|
48332
48343
|
"args": [
|
|
48333
48344
|
{
|
|
48334
|
-
"name": "
|
|
48335
|
-
"type": "DirEntry",
|
|
48345
|
+
"name": "sourceFile",
|
|
48336
48346
|
"deprecated": false,
|
|
48337
48347
|
"deprecationMessage": ""
|
|
48338
48348
|
},
|
|
48339
48349
|
{
|
|
48340
|
-
"name": "
|
|
48350
|
+
"name": "filePath",
|
|
48351
|
+
"type": "string",
|
|
48352
|
+
"deprecated": false,
|
|
48353
|
+
"deprecationMessage": ""
|
|
48354
|
+
},
|
|
48355
|
+
{
|
|
48356
|
+
"name": "context",
|
|
48357
|
+
"type": "SchematicContext",
|
|
48341
48358
|
"deprecated": false,
|
|
48342
48359
|
"deprecationMessage": ""
|
|
48343
48360
|
}
|
|
@@ -48345,8 +48362,7 @@
|
|
|
48345
48362
|
"returnType": "void",
|
|
48346
48363
|
"jsdoctags": [
|
|
48347
48364
|
{
|
|
48348
|
-
"name": "
|
|
48349
|
-
"type": "DirEntry",
|
|
48365
|
+
"name": "sourceFile",
|
|
48350
48366
|
"deprecated": false,
|
|
48351
48367
|
"deprecationMessage": "",
|
|
48352
48368
|
"tagName": {
|
|
@@ -48354,7 +48370,17 @@
|
|
|
48354
48370
|
}
|
|
48355
48371
|
},
|
|
48356
48372
|
{
|
|
48357
|
-
"name": "
|
|
48373
|
+
"name": "filePath",
|
|
48374
|
+
"type": "string",
|
|
48375
|
+
"deprecated": false,
|
|
48376
|
+
"deprecationMessage": "",
|
|
48377
|
+
"tagName": {
|
|
48378
|
+
"text": "param"
|
|
48379
|
+
}
|
|
48380
|
+
},
|
|
48381
|
+
{
|
|
48382
|
+
"name": "context",
|
|
48383
|
+
"type": "SchematicContext",
|
|
48358
48384
|
"deprecated": false,
|
|
48359
48385
|
"deprecationMessage": "",
|
|
48360
48386
|
"tagName": {
|
|
@@ -48681,10 +48707,10 @@
|
|
|
48681
48707
|
]
|
|
48682
48708
|
}
|
|
48683
48709
|
],
|
|
48684
|
-
"packages/core/schematics/migrate-
|
|
48710
|
+
"packages/core/schematics/migrate-to-signals/index.ts": [
|
|
48685
48711
|
{
|
|
48686
48712
|
"name": "applyEdits",
|
|
48687
|
-
"file": "packages/core/schematics/migrate-
|
|
48713
|
+
"file": "packages/core/schematics/migrate-to-signals/index.ts",
|
|
48688
48714
|
"ctype": "miscellaneous",
|
|
48689
48715
|
"subtype": "function",
|
|
48690
48716
|
"coverageIgnore": false,
|
|
@@ -48728,20 +48754,19 @@
|
|
|
48728
48754
|
]
|
|
48729
48755
|
},
|
|
48730
48756
|
{
|
|
48731
|
-
"name": "
|
|
48732
|
-
"file": "packages/core/schematics/migrate-
|
|
48757
|
+
"name": "buildTemplateRefMap",
|
|
48758
|
+
"file": "packages/core/schematics/migrate-to-signals/index.ts",
|
|
48733
48759
|
"ctype": "miscellaneous",
|
|
48734
48760
|
"subtype": "function",
|
|
48735
48761
|
"coverageIgnore": false,
|
|
48736
48762
|
"deprecated": false,
|
|
48737
48763
|
"deprecationMessage": "",
|
|
48738
|
-
"rawdescription": "",
|
|
48739
|
-
"description": "",
|
|
48740
|
-
"displayName": "
|
|
48764
|
+
"rawdescription": "Builds a map of template reference variable names (#ref) to EUI class names.\n\nParses the component's template (inline or external) and finds elements like:\n <eui-alert #myAlert> → myAlert → EuiAlertComponent\n <input euiAutocomplete #ac> → ac → EuiAutocompleteComponent\n\nReturns Map<refName, euiClassName>.",
|
|
48765
|
+
"description": "<p>Builds a map of template reference variable names (#ref) to EUI class names.</p>\n<p>Parses the component's template (inline or external) and finds elements like:\n <eui-alert #myAlert> → myAlert → EuiAlertComponent\n <input euiAutocomplete #ac> → ac → EuiAutocompleteComponent</p>\n<p>Returns Map<refName, euiClassName>.</p>\n",
|
|
48766
|
+
"displayName": "buildTemplateRefMap",
|
|
48741
48767
|
"args": [
|
|
48742
48768
|
{
|
|
48743
|
-
"name": "
|
|
48744
|
-
"type": "TmplAstElement",
|
|
48769
|
+
"name": "sourceFile",
|
|
48745
48770
|
"deprecated": false,
|
|
48746
48771
|
"deprecationMessage": ""
|
|
48747
48772
|
},
|
|
@@ -48751,11 +48776,6 @@
|
|
|
48751
48776
|
"deprecated": false,
|
|
48752
48777
|
"deprecationMessage": ""
|
|
48753
48778
|
},
|
|
48754
|
-
{
|
|
48755
|
-
"name": "edits",
|
|
48756
|
-
"deprecated": false,
|
|
48757
|
-
"deprecationMessage": ""
|
|
48758
|
-
},
|
|
48759
48779
|
{
|
|
48760
48780
|
"name": "filePath",
|
|
48761
48781
|
"type": "string",
|
|
@@ -48763,17 +48783,16 @@
|
|
|
48763
48783
|
"deprecationMessage": ""
|
|
48764
48784
|
},
|
|
48765
48785
|
{
|
|
48766
|
-
"name": "
|
|
48767
|
-
"type": "
|
|
48786
|
+
"name": "tree",
|
|
48787
|
+
"type": "Tree",
|
|
48768
48788
|
"deprecated": false,
|
|
48769
48789
|
"deprecationMessage": ""
|
|
48770
48790
|
}
|
|
48771
48791
|
],
|
|
48772
|
-
"returnType": "
|
|
48792
|
+
"returnType": "Map<string, string>",
|
|
48773
48793
|
"jsdoctags": [
|
|
48774
48794
|
{
|
|
48775
|
-
"name": "
|
|
48776
|
-
"type": "TmplAstElement",
|
|
48795
|
+
"name": "sourceFile",
|
|
48777
48796
|
"deprecated": false,
|
|
48778
48797
|
"deprecationMessage": "",
|
|
48779
48798
|
"tagName": {
|
|
@@ -48789,14 +48808,6 @@
|
|
|
48789
48808
|
"text": "param"
|
|
48790
48809
|
}
|
|
48791
48810
|
},
|
|
48792
|
-
{
|
|
48793
|
-
"name": "edits",
|
|
48794
|
-
"deprecated": false,
|
|
48795
|
-
"deprecationMessage": "",
|
|
48796
|
-
"tagName": {
|
|
48797
|
-
"text": "param"
|
|
48798
|
-
}
|
|
48799
|
-
},
|
|
48800
48811
|
{
|
|
48801
48812
|
"name": "filePath",
|
|
48802
48813
|
"type": "string",
|
|
@@ -48807,8 +48818,8 @@
|
|
|
48807
48818
|
}
|
|
48808
48819
|
},
|
|
48809
48820
|
{
|
|
48810
|
-
"name": "
|
|
48811
|
-
"type": "
|
|
48821
|
+
"name": "tree",
|
|
48822
|
+
"type": "Tree",
|
|
48812
48823
|
"deprecated": false,
|
|
48813
48824
|
"deprecationMessage": "",
|
|
48814
48825
|
"tagName": {
|
|
@@ -48818,20 +48829,19 @@
|
|
|
48818
48829
|
]
|
|
48819
48830
|
},
|
|
48820
48831
|
{
|
|
48821
|
-
"name": "
|
|
48822
|
-
"file": "packages/core/schematics/migrate-
|
|
48832
|
+
"name": "collectEuiReferences",
|
|
48833
|
+
"file": "packages/core/schematics/migrate-to-signals/index.ts",
|
|
48823
48834
|
"ctype": "miscellaneous",
|
|
48824
48835
|
"subtype": "function",
|
|
48825
48836
|
"coverageIgnore": false,
|
|
48826
48837
|
"deprecated": false,
|
|
48827
48838
|
"deprecationMessage": "",
|
|
48828
|
-
"rawdescription": "",
|
|
48829
|
-
"description": "",
|
|
48830
|
-
"displayName": "
|
|
48839
|
+
"rawdescription": "Scans the source file for class properties or variables typed as EUI component classes.\nReturns a map: localName → Set of signal property names.\n\nHandles:",
|
|
48840
|
+
"description": "<p>Scans the source file for class properties or variables typed as EUI component classes.\nReturns a map: localName → Set of signal property names.</p>\n<p>Handles:</p>\n",
|
|
48841
|
+
"displayName": "collectEuiReferences",
|
|
48831
48842
|
"args": [
|
|
48832
48843
|
{
|
|
48833
|
-
"name": "
|
|
48834
|
-
"type": "TmplAstElement",
|
|
48844
|
+
"name": "sourceFile",
|
|
48835
48845
|
"deprecated": false,
|
|
48836
48846
|
"deprecationMessage": ""
|
|
48837
48847
|
},
|
|
@@ -48842,16 +48852,16 @@
|
|
|
48842
48852
|
"deprecationMessage": ""
|
|
48843
48853
|
},
|
|
48844
48854
|
{
|
|
48845
|
-
"name": "
|
|
48855
|
+
"name": "templateRefMap",
|
|
48856
|
+
"type": "Map",
|
|
48846
48857
|
"deprecated": false,
|
|
48847
48858
|
"deprecationMessage": ""
|
|
48848
48859
|
}
|
|
48849
48860
|
],
|
|
48850
|
-
"returnType": "
|
|
48861
|
+
"returnType": "Map<string, ReadonlySet<string>>",
|
|
48851
48862
|
"jsdoctags": [
|
|
48852
48863
|
{
|
|
48853
|
-
"name": "
|
|
48854
|
-
"type": "TmplAstElement",
|
|
48864
|
+
"name": "sourceFile",
|
|
48855
48865
|
"deprecated": false,
|
|
48856
48866
|
"deprecationMessage": "",
|
|
48857
48867
|
"tagName": {
|
|
@@ -48868,7 +48878,8 @@
|
|
|
48868
48878
|
}
|
|
48869
48879
|
},
|
|
48870
48880
|
{
|
|
48871
|
-
"name": "
|
|
48881
|
+
"name": "templateRefMap",
|
|
48882
|
+
"type": "Map",
|
|
48872
48883
|
"deprecated": false,
|
|
48873
48884
|
"deprecationMessage": "",
|
|
48874
48885
|
"tagName": {
|
|
@@ -48878,27 +48889,42 @@
|
|
|
48878
48889
|
]
|
|
48879
48890
|
},
|
|
48880
48891
|
{
|
|
48881
|
-
"name": "
|
|
48882
|
-
"file": "packages/core/schematics/migrate-
|
|
48892
|
+
"name": "collectTemplateRefs",
|
|
48893
|
+
"file": "packages/core/schematics/migrate-to-signals/index.ts",
|
|
48883
48894
|
"ctype": "miscellaneous",
|
|
48884
48895
|
"subtype": "function",
|
|
48885
48896
|
"coverageIgnore": false,
|
|
48886
48897
|
"deprecated": false,
|
|
48887
48898
|
"deprecationMessage": "",
|
|
48888
|
-
"rawdescription": "",
|
|
48889
|
-
"description": "",
|
|
48890
|
-
"displayName": "
|
|
48899
|
+
"rawdescription": "Recursively walks template AST nodes, collecting #refName → EUI class name mappings.\n\nFor each element with a template reference (#ref), we try to resolve its EUI class:\n1. Match element tag name against EUI_SELECTOR_TO_CLASS (e.g. 'eui-alert' → EuiAlertComponent)\n2. Match element attributes against attribute selectors (e.g. [euiAutocomplete] → EuiAutocompleteComponent)",
|
|
48900
|
+
"description": "<p>Recursively walks template AST nodes, collecting #refName → EUI class name mappings.</p>\n<p>For each element with a template reference (#ref), we try to resolve its EUI class:</p>\n<ol>\n<li>Match element tag name against EUI_SELECTOR_TO_CLASS (e.g. 'eui-alert' → EuiAlertComponent)</li>\n<li>Match element attributes against attribute selectors (e.g. [euiAutocomplete] → EuiAutocompleteComponent)</li>\n</ol>\n",
|
|
48901
|
+
"displayName": "collectTemplateRefs",
|
|
48891
48902
|
"args": [
|
|
48892
48903
|
{
|
|
48893
|
-
"name": "
|
|
48904
|
+
"name": "nodes",
|
|
48905
|
+
"deprecated": false,
|
|
48906
|
+
"deprecationMessage": ""
|
|
48907
|
+
},
|
|
48908
|
+
{
|
|
48909
|
+
"name": "refMap",
|
|
48910
|
+
"type": "Map",
|
|
48894
48911
|
"deprecated": false,
|
|
48895
48912
|
"deprecationMessage": ""
|
|
48896
48913
|
}
|
|
48897
48914
|
],
|
|
48898
|
-
"returnType": "
|
|
48915
|
+
"returnType": "void",
|
|
48899
48916
|
"jsdoctags": [
|
|
48900
48917
|
{
|
|
48901
|
-
"name": "
|
|
48918
|
+
"name": "nodes",
|
|
48919
|
+
"deprecated": false,
|
|
48920
|
+
"deprecationMessage": "",
|
|
48921
|
+
"tagName": {
|
|
48922
|
+
"text": "param"
|
|
48923
|
+
}
|
|
48924
|
+
},
|
|
48925
|
+
{
|
|
48926
|
+
"name": "refMap",
|
|
48927
|
+
"type": "Map",
|
|
48902
48928
|
"deprecated": false,
|
|
48903
48929
|
"deprecationMessage": "",
|
|
48904
48930
|
"tagName": {
|
|
@@ -48908,8 +48934,8 @@
|
|
|
48908
48934
|
]
|
|
48909
48935
|
},
|
|
48910
48936
|
{
|
|
48911
|
-
"name": "
|
|
48912
|
-
"file": "packages/core/schematics/migrate-
|
|
48937
|
+
"name": "deduplicateEdits",
|
|
48938
|
+
"file": "packages/core/schematics/migrate-to-signals/index.ts",
|
|
48913
48939
|
"ctype": "miscellaneous",
|
|
48914
48940
|
"subtype": "function",
|
|
48915
48941
|
"coverageIgnore": false,
|
|
@@ -48917,18 +48943,18 @@
|
|
|
48917
48943
|
"deprecationMessage": "",
|
|
48918
48944
|
"rawdescription": "",
|
|
48919
48945
|
"description": "",
|
|
48920
|
-
"displayName": "
|
|
48946
|
+
"displayName": "deduplicateEdits",
|
|
48921
48947
|
"args": [
|
|
48922
48948
|
{
|
|
48923
|
-
"name": "
|
|
48949
|
+
"name": "edits",
|
|
48924
48950
|
"deprecated": false,
|
|
48925
48951
|
"deprecationMessage": ""
|
|
48926
48952
|
}
|
|
48927
48953
|
],
|
|
48928
|
-
"returnType": "
|
|
48954
|
+
"returnType": "Edit[]",
|
|
48929
48955
|
"jsdoctags": [
|
|
48930
48956
|
{
|
|
48931
|
-
"name": "
|
|
48957
|
+
"name": "edits",
|
|
48932
48958
|
"deprecated": false,
|
|
48933
48959
|
"deprecationMessage": "",
|
|
48934
48960
|
"tagName": {
|
|
@@ -48938,27 +48964,42 @@
|
|
|
48938
48964
|
]
|
|
48939
48965
|
},
|
|
48940
48966
|
{
|
|
48941
|
-
"name": "
|
|
48942
|
-
"file": "packages/core/schematics/migrate-
|
|
48967
|
+
"name": "findComponentMetadata",
|
|
48968
|
+
"file": "packages/core/schematics/migrate-to-signals/index.ts",
|
|
48943
48969
|
"ctype": "miscellaneous",
|
|
48944
48970
|
"subtype": "function",
|
|
48945
48971
|
"coverageIgnore": false,
|
|
48946
48972
|
"deprecated": false,
|
|
48947
48973
|
"deprecationMessage": "",
|
|
48948
|
-
"rawdescription": "",
|
|
48949
|
-
"description": "",
|
|
48950
|
-
"displayName": "
|
|
48974
|
+
"rawdescription": "Extracts",
|
|
48975
|
+
"description": "<p>Extracts</p>\n",
|
|
48976
|
+
"displayName": "findComponentMetadata",
|
|
48951
48977
|
"args": [
|
|
48952
48978
|
{
|
|
48953
|
-
"name": "
|
|
48979
|
+
"name": "sourceFile",
|
|
48980
|
+
"deprecated": false,
|
|
48981
|
+
"deprecationMessage": ""
|
|
48982
|
+
},
|
|
48983
|
+
{
|
|
48984
|
+
"name": "source",
|
|
48985
|
+
"type": "string",
|
|
48954
48986
|
"deprecated": false,
|
|
48955
48987
|
"deprecationMessage": ""
|
|
48956
48988
|
}
|
|
48957
48989
|
],
|
|
48958
|
-
"returnType": "
|
|
48990
|
+
"returnType": "literal type | null",
|
|
48959
48991
|
"jsdoctags": [
|
|
48960
48992
|
{
|
|
48961
|
-
"name": "
|
|
48993
|
+
"name": "sourceFile",
|
|
48994
|
+
"deprecated": false,
|
|
48995
|
+
"deprecationMessage": "",
|
|
48996
|
+
"tagName": {
|
|
48997
|
+
"text": "param"
|
|
48998
|
+
}
|
|
48999
|
+
},
|
|
49000
|
+
{
|
|
49001
|
+
"name": "source",
|
|
49002
|
+
"type": "string",
|
|
48962
49003
|
"deprecated": false,
|
|
48963
49004
|
"deprecationMessage": "",
|
|
48964
49005
|
"tagName": {
|
|
@@ -48968,8 +49009,8 @@
|
|
|
48968
49009
|
]
|
|
48969
49010
|
},
|
|
48970
49011
|
{
|
|
48971
|
-
"name": "
|
|
48972
|
-
"file": "packages/core/schematics/migrate-
|
|
49012
|
+
"name": "getDecoratorName",
|
|
49013
|
+
"file": "packages/core/schematics/migrate-to-signals/index.ts",
|
|
48973
49014
|
"ctype": "miscellaneous",
|
|
48974
49015
|
"subtype": "function",
|
|
48975
49016
|
"coverageIgnore": false,
|
|
@@ -48977,24 +49018,20 @@
|
|
|
48977
49018
|
"deprecationMessage": "",
|
|
48978
49019
|
"rawdescription": "",
|
|
48979
49020
|
"description": "",
|
|
48980
|
-
"displayName": "
|
|
49021
|
+
"displayName": "getDecoratorName",
|
|
48981
49022
|
"args": [
|
|
48982
49023
|
{
|
|
48983
|
-
"name": "
|
|
48984
|
-
"type": "Schema",
|
|
49024
|
+
"name": "callExpr",
|
|
48985
49025
|
"deprecated": false,
|
|
48986
|
-
"deprecationMessage": ""
|
|
48987
|
-
"defaultValue": "{}"
|
|
49026
|
+
"deprecationMessage": ""
|
|
48988
49027
|
}
|
|
48989
49028
|
],
|
|
48990
|
-
"returnType": "
|
|
49029
|
+
"returnType": "string | null",
|
|
48991
49030
|
"jsdoctags": [
|
|
48992
49031
|
{
|
|
48993
|
-
"name": "
|
|
48994
|
-
"type": "Schema",
|
|
49032
|
+
"name": "callExpr",
|
|
48995
49033
|
"deprecated": false,
|
|
48996
49034
|
"deprecationMessage": "",
|
|
48997
|
-
"defaultValue": "{}",
|
|
48998
49035
|
"tagName": {
|
|
48999
49036
|
"text": "param"
|
|
49000
49037
|
}
|
|
@@ -49002,8 +49039,8 @@
|
|
|
49002
49039
|
]
|
|
49003
49040
|
},
|
|
49004
49041
|
{
|
|
49005
|
-
"name": "
|
|
49006
|
-
"file": "packages/core/schematics/migrate-
|
|
49042
|
+
"name": "getTypeAnnotationName",
|
|
49043
|
+
"file": "packages/core/schematics/migrate-to-signals/index.ts",
|
|
49007
49044
|
"ctype": "miscellaneous",
|
|
49008
49045
|
"subtype": "function",
|
|
49009
49046
|
"coverageIgnore": false,
|
|
@@ -49011,50 +49048,18 @@
|
|
|
49011
49048
|
"deprecationMessage": "",
|
|
49012
49049
|
"rawdescription": "",
|
|
49013
49050
|
"description": "",
|
|
49014
|
-
"displayName": "
|
|
49051
|
+
"displayName": "getTypeAnnotationName",
|
|
49015
49052
|
"args": [
|
|
49016
49053
|
{
|
|
49017
|
-
"name": "
|
|
49018
|
-
"type": "string",
|
|
49019
|
-
"deprecated": false,
|
|
49020
|
-
"deprecationMessage": ""
|
|
49021
|
-
},
|
|
49022
|
-
{
|
|
49023
|
-
"name": "filePath",
|
|
49024
|
-
"type": "string",
|
|
49025
|
-
"deprecated": false,
|
|
49026
|
-
"deprecationMessage": ""
|
|
49027
|
-
},
|
|
49028
|
-
{
|
|
49029
|
-
"name": "context",
|
|
49030
|
-
"type": "SchematicContext",
|
|
49054
|
+
"name": "typeNode",
|
|
49031
49055
|
"deprecated": false,
|
|
49032
49056
|
"deprecationMessage": ""
|
|
49033
49057
|
}
|
|
49034
49058
|
],
|
|
49035
|
-
"returnType": "string",
|
|
49059
|
+
"returnType": "string | null",
|
|
49036
49060
|
"jsdoctags": [
|
|
49037
49061
|
{
|
|
49038
|
-
"name": "
|
|
49039
|
-
"type": "string",
|
|
49040
|
-
"deprecated": false,
|
|
49041
|
-
"deprecationMessage": "",
|
|
49042
|
-
"tagName": {
|
|
49043
|
-
"text": "param"
|
|
49044
|
-
}
|
|
49045
|
-
},
|
|
49046
|
-
{
|
|
49047
|
-
"name": "filePath",
|
|
49048
|
-
"type": "string",
|
|
49049
|
-
"deprecated": false,
|
|
49050
|
-
"deprecationMessage": "",
|
|
49051
|
-
"tagName": {
|
|
49052
|
-
"text": "param"
|
|
49053
|
-
}
|
|
49054
|
-
},
|
|
49055
|
-
{
|
|
49056
|
-
"name": "context",
|
|
49057
|
-
"type": "SchematicContext",
|
|
49062
|
+
"name": "typeNode",
|
|
49058
49063
|
"deprecated": false,
|
|
49059
49064
|
"deprecationMessage": "",
|
|
49060
49065
|
"tagName": {
|
|
@@ -49064,37 +49069,25 @@
|
|
|
49064
49069
|
]
|
|
49065
49070
|
},
|
|
49066
49071
|
{
|
|
49067
|
-
"name": "
|
|
49068
|
-
"file": "packages/core/schematics/migrate-
|
|
49072
|
+
"name": "hasEuiReference",
|
|
49073
|
+
"file": "packages/core/schematics/migrate-to-signals/index.ts",
|
|
49069
49074
|
"ctype": "miscellaneous",
|
|
49070
49075
|
"subtype": "function",
|
|
49071
49076
|
"coverageIgnore": false,
|
|
49072
49077
|
"deprecated": false,
|
|
49073
49078
|
"deprecationMessage": "",
|
|
49074
|
-
"rawdescription": "",
|
|
49075
|
-
"description": "",
|
|
49076
|
-
"displayName": "
|
|
49079
|
+
"rawdescription": "Quick check: does the file reference any EUI class name from the signal map?",
|
|
49080
|
+
"description": "<p>Quick check: does the file reference any EUI class name from the signal map?</p>\n",
|
|
49081
|
+
"displayName": "hasEuiReference",
|
|
49077
49082
|
"args": [
|
|
49078
49083
|
{
|
|
49079
49084
|
"name": "source",
|
|
49080
49085
|
"type": "string",
|
|
49081
49086
|
"deprecated": false,
|
|
49082
49087
|
"deprecationMessage": ""
|
|
49083
|
-
},
|
|
49084
|
-
{
|
|
49085
|
-
"name": "filePath",
|
|
49086
|
-
"type": "string",
|
|
49087
|
-
"deprecated": false,
|
|
49088
|
-
"deprecationMessage": ""
|
|
49089
|
-
},
|
|
49090
|
-
{
|
|
49091
|
-
"name": "context",
|
|
49092
|
-
"type": "SchematicContext",
|
|
49093
|
-
"deprecated": false,
|
|
49094
|
-
"deprecationMessage": ""
|
|
49095
49088
|
}
|
|
49096
49089
|
],
|
|
49097
|
-
"returnType": "
|
|
49090
|
+
"returnType": "boolean",
|
|
49098
49091
|
"jsdoctags": [
|
|
49099
49092
|
{
|
|
49100
49093
|
"name": "source",
|
|
@@ -49104,19 +49097,33 @@
|
|
|
49104
49097
|
"tagName": {
|
|
49105
49098
|
"text": "param"
|
|
49106
49099
|
}
|
|
49107
|
-
}
|
|
49100
|
+
}
|
|
49101
|
+
]
|
|
49102
|
+
},
|
|
49103
|
+
{
|
|
49104
|
+
"name": "hasStringRefQuery",
|
|
49105
|
+
"file": "packages/core/schematics/migrate-to-signals/index.ts",
|
|
49106
|
+
"ctype": "miscellaneous",
|
|
49107
|
+
"subtype": "function",
|
|
49108
|
+
"coverageIgnore": false,
|
|
49109
|
+
"deprecated": false,
|
|
49110
|
+
"deprecationMessage": "",
|
|
49111
|
+
"rawdescription": "Quick check: does the file have",
|
|
49112
|
+
"description": "<p>Quick check: does the file have</p>\n",
|
|
49113
|
+
"displayName": "hasStringRefQuery",
|
|
49114
|
+
"args": [
|
|
49108
49115
|
{
|
|
49109
|
-
"name": "
|
|
49116
|
+
"name": "source",
|
|
49110
49117
|
"type": "string",
|
|
49111
49118
|
"deprecated": false,
|
|
49112
|
-
"deprecationMessage": ""
|
|
49113
|
-
|
|
49114
|
-
|
|
49115
|
-
|
|
49116
|
-
|
|
49119
|
+
"deprecationMessage": ""
|
|
49120
|
+
}
|
|
49121
|
+
],
|
|
49122
|
+
"returnType": "boolean",
|
|
49123
|
+
"jsdoctags": [
|
|
49117
49124
|
{
|
|
49118
|
-
"name": "
|
|
49119
|
-
"type": "
|
|
49125
|
+
"name": "source",
|
|
49126
|
+
"type": "string",
|
|
49120
49127
|
"deprecated": false,
|
|
49121
49128
|
"deprecationMessage": "",
|
|
49122
49129
|
"tagName": {
|
|
@@ -49126,8 +49133,8 @@
|
|
|
49126
49133
|
]
|
|
49127
49134
|
},
|
|
49128
49135
|
{
|
|
49129
|
-
"name": "
|
|
49130
|
-
"file": "packages/core/schematics/migrate-
|
|
49136
|
+
"name": "isAssignmentOperator",
|
|
49137
|
+
"file": "packages/core/schematics/migrate-to-signals/index.ts",
|
|
49131
49138
|
"ctype": "miscellaneous",
|
|
49132
49139
|
"subtype": "function",
|
|
49133
49140
|
"coverageIgnore": false,
|
|
@@ -49135,52 +49142,54 @@
|
|
|
49135
49142
|
"deprecationMessage": "",
|
|
49136
49143
|
"rawdescription": "",
|
|
49137
49144
|
"description": "",
|
|
49138
|
-
"displayName": "
|
|
49145
|
+
"displayName": "isAssignmentOperator",
|
|
49139
49146
|
"args": [
|
|
49140
49147
|
{
|
|
49141
|
-
"name": "
|
|
49142
|
-
"type": "string",
|
|
49143
|
-
"deprecated": false,
|
|
49144
|
-
"deprecationMessage": ""
|
|
49145
|
-
},
|
|
49146
|
-
{
|
|
49147
|
-
"name": "filePath",
|
|
49148
|
-
"type": "string",
|
|
49149
|
-
"deprecated": false,
|
|
49150
|
-
"deprecationMessage": ""
|
|
49151
|
-
},
|
|
49152
|
-
{
|
|
49153
|
-
"name": "context",
|
|
49154
|
-
"type": "SchematicContext",
|
|
49148
|
+
"name": "kind",
|
|
49155
49149
|
"deprecated": false,
|
|
49156
49150
|
"deprecationMessage": ""
|
|
49157
49151
|
}
|
|
49158
49152
|
],
|
|
49159
|
-
"returnType": "
|
|
49153
|
+
"returnType": "boolean",
|
|
49160
49154
|
"jsdoctags": [
|
|
49161
49155
|
{
|
|
49162
|
-
"name": "
|
|
49163
|
-
"type": "string",
|
|
49156
|
+
"name": "kind",
|
|
49164
49157
|
"deprecated": false,
|
|
49165
49158
|
"deprecationMessage": "",
|
|
49166
49159
|
"tagName": {
|
|
49167
49160
|
"text": "param"
|
|
49168
49161
|
}
|
|
49169
|
-
}
|
|
49162
|
+
}
|
|
49163
|
+
]
|
|
49164
|
+
},
|
|
49165
|
+
{
|
|
49166
|
+
"name": "migrateToSignals",
|
|
49167
|
+
"file": "packages/core/schematics/migrate-to-signals/index.ts",
|
|
49168
|
+
"ctype": "miscellaneous",
|
|
49169
|
+
"subtype": "function",
|
|
49170
|
+
"coverageIgnore": false,
|
|
49171
|
+
"deprecated": false,
|
|
49172
|
+
"deprecationMessage": "",
|
|
49173
|
+
"rawdescription": "Migrates consumer code that accesses EUI component instances programmatically.\n\nWhen EUI migrated",
|
|
49174
|
+
"description": "<p>Migrates consumer code that accesses EUI component instances programmatically.</p>\n<p>When EUI migrated</p>\n",
|
|
49175
|
+
"displayName": "migrateToSignals",
|
|
49176
|
+
"args": [
|
|
49170
49177
|
{
|
|
49171
|
-
"name": "
|
|
49172
|
-
"type": "
|
|
49178
|
+
"name": "options",
|
|
49179
|
+
"type": "Schema",
|
|
49173
49180
|
"deprecated": false,
|
|
49174
49181
|
"deprecationMessage": "",
|
|
49175
|
-
"
|
|
49176
|
-
|
|
49177
|
-
|
|
49178
|
-
|
|
49182
|
+
"defaultValue": "{}"
|
|
49183
|
+
}
|
|
49184
|
+
],
|
|
49185
|
+
"returnType": "Rule",
|
|
49186
|
+
"jsdoctags": [
|
|
49179
49187
|
{
|
|
49180
|
-
"name": "
|
|
49181
|
-
"type": "
|
|
49188
|
+
"name": "options",
|
|
49189
|
+
"type": "Schema",
|
|
49182
49190
|
"deprecated": false,
|
|
49183
49191
|
"deprecationMessage": "",
|
|
49192
|
+
"defaultValue": "{}",
|
|
49184
49193
|
"tagName": {
|
|
49185
49194
|
"text": "param"
|
|
49186
49195
|
}
|
|
@@ -49189,7 +49198,7 @@
|
|
|
49189
49198
|
},
|
|
49190
49199
|
{
|
|
49191
49200
|
"name": "migrateTypeScript",
|
|
49192
|
-
"file": "packages/core/schematics/migrate-
|
|
49201
|
+
"file": "packages/core/schematics/migrate-to-signals/index.ts",
|
|
49193
49202
|
"ctype": "miscellaneous",
|
|
49194
49203
|
"subtype": "function",
|
|
49195
49204
|
"coverageIgnore": false,
|
|
@@ -49211,6 +49220,12 @@
|
|
|
49211
49220
|
"deprecated": false,
|
|
49212
49221
|
"deprecationMessage": ""
|
|
49213
49222
|
},
|
|
49223
|
+
{
|
|
49224
|
+
"name": "tree",
|
|
49225
|
+
"type": "Tree",
|
|
49226
|
+
"deprecated": false,
|
|
49227
|
+
"deprecationMessage": ""
|
|
49228
|
+
},
|
|
49214
49229
|
{
|
|
49215
49230
|
"name": "context",
|
|
49216
49231
|
"type": "SchematicContext",
|
|
@@ -49218,7 +49233,7 @@
|
|
|
49218
49233
|
"deprecationMessage": ""
|
|
49219
49234
|
}
|
|
49220
49235
|
],
|
|
49221
|
-
"returnType": "
|
|
49236
|
+
"returnType": "MigrateResult",
|
|
49222
49237
|
"jsdoctags": [
|
|
49223
49238
|
{
|
|
49224
49239
|
"name": "source",
|
|
@@ -49238,6 +49253,15 @@
|
|
|
49238
49253
|
"text": "param"
|
|
49239
49254
|
}
|
|
49240
49255
|
},
|
|
49256
|
+
{
|
|
49257
|
+
"name": "tree",
|
|
49258
|
+
"type": "Tree",
|
|
49259
|
+
"deprecated": false,
|
|
49260
|
+
"deprecationMessage": "",
|
|
49261
|
+
"tagName": {
|
|
49262
|
+
"text": "param"
|
|
49263
|
+
}
|
|
49264
|
+
},
|
|
49241
49265
|
{
|
|
49242
49266
|
"name": "context",
|
|
49243
49267
|
"type": "SchematicContext",
|
|
@@ -49250,24 +49274,51 @@
|
|
|
49250
49274
|
]
|
|
49251
49275
|
},
|
|
49252
49276
|
{
|
|
49253
|
-
"name": "
|
|
49254
|
-
"file": "packages/core/schematics/migrate-
|
|
49277
|
+
"name": "resolveElementToEuiClass",
|
|
49278
|
+
"file": "packages/core/schematics/migrate-to-signals/index.ts",
|
|
49255
49279
|
"ctype": "miscellaneous",
|
|
49256
49280
|
"subtype": "function",
|
|
49257
49281
|
"coverageIgnore": false,
|
|
49258
49282
|
"deprecated": false,
|
|
49259
49283
|
"deprecationMessage": "",
|
|
49260
|
-
"rawdescription": "",
|
|
49261
|
-
"description": "",
|
|
49262
|
-
"displayName": "
|
|
49284
|
+
"rawdescription": "Given a template element, resolves it to an EUI class name by checking:\n1. Tag name: <eui-alert> → look up 'eui-alert' in EUI_SELECTOR_TO_CLASS\n2. Attribute selectors: <input euiAutocomplete> → look up 'input[euiAutocomplete]' or '[euiAutocomplete]'",
|
|
49285
|
+
"description": "<p>Given a template element, resolves it to an EUI class name by checking:</p>\n<ol>\n<li>Tag name: <eui-alert> → look up 'eui-alert' in EUI_SELECTOR_TO_CLASS</li>\n<li>Attribute selectors: <input euiAutocomplete> → look up 'input[euiAutocomplete]' or '[euiAutocomplete]'</li>\n</ol>\n",
|
|
49286
|
+
"displayName": "resolveElementToEuiClass",
|
|
49263
49287
|
"args": [
|
|
49264
49288
|
{
|
|
49265
|
-
"name": "
|
|
49289
|
+
"name": "element",
|
|
49290
|
+
"type": "TmplAstElement",
|
|
49266
49291
|
"deprecated": false,
|
|
49267
49292
|
"deprecationMessage": ""
|
|
49268
|
-
}
|
|
49293
|
+
}
|
|
49294
|
+
],
|
|
49295
|
+
"returnType": "string | null",
|
|
49296
|
+
"jsdoctags": [
|
|
49269
49297
|
{
|
|
49270
|
-
"name": "
|
|
49298
|
+
"name": "element",
|
|
49299
|
+
"type": "TmplAstElement",
|
|
49300
|
+
"deprecated": false,
|
|
49301
|
+
"deprecationMessage": "",
|
|
49302
|
+
"tagName": {
|
|
49303
|
+
"text": "param"
|
|
49304
|
+
}
|
|
49305
|
+
}
|
|
49306
|
+
]
|
|
49307
|
+
},
|
|
49308
|
+
{
|
|
49309
|
+
"name": "resolveEuiClassName",
|
|
49310
|
+
"file": "packages/core/schematics/migrate-to-signals/index.ts",
|
|
49311
|
+
"ctype": "miscellaneous",
|
|
49312
|
+
"subtype": "function",
|
|
49313
|
+
"coverageIgnore": false,
|
|
49314
|
+
"deprecated": false,
|
|
49315
|
+
"deprecationMessage": "",
|
|
49316
|
+
"rawdescription": "Resolves the EUI class name from a property declaration.\nChecks: type annotation → decorator class argument → template ref map.",
|
|
49317
|
+
"description": "<p>Resolves the EUI class name from a property declaration.\nChecks: type annotation → decorator class argument → template ref map.</p>\n",
|
|
49318
|
+
"displayName": "resolveEuiClassName",
|
|
49319
|
+
"args": [
|
|
49320
|
+
{
|
|
49321
|
+
"name": "node",
|
|
49271
49322
|
"deprecated": false,
|
|
49272
49323
|
"deprecationMessage": ""
|
|
49273
49324
|
},
|
|
@@ -49277,23 +49328,16 @@
|
|
|
49277
49328
|
"deprecationMessage": ""
|
|
49278
49329
|
},
|
|
49279
49330
|
{
|
|
49280
|
-
"name": "
|
|
49331
|
+
"name": "templateRefMap",
|
|
49332
|
+
"type": "Map",
|
|
49281
49333
|
"deprecated": false,
|
|
49282
49334
|
"deprecationMessage": ""
|
|
49283
49335
|
}
|
|
49284
49336
|
],
|
|
49285
|
-
"returnType": "
|
|
49337
|
+
"returnType": "string | null",
|
|
49286
49338
|
"jsdoctags": [
|
|
49287
49339
|
{
|
|
49288
|
-
"name": "
|
|
49289
|
-
"deprecated": false,
|
|
49290
|
-
"deprecationMessage": "",
|
|
49291
|
-
"tagName": {
|
|
49292
|
-
"text": "param"
|
|
49293
|
-
}
|
|
49294
|
-
},
|
|
49295
|
-
{
|
|
49296
|
-
"name": "specifier",
|
|
49340
|
+
"name": "node",
|
|
49297
49341
|
"deprecated": false,
|
|
49298
49342
|
"deprecationMessage": "",
|
|
49299
49343
|
"tagName": {
|
|
@@ -49309,7 +49353,8 @@
|
|
|
49309
49353
|
}
|
|
49310
49354
|
},
|
|
49311
49355
|
{
|
|
49312
|
-
"name": "
|
|
49356
|
+
"name": "templateRefMap",
|
|
49357
|
+
"type": "Map",
|
|
49313
49358
|
"deprecated": false,
|
|
49314
49359
|
"deprecationMessage": "",
|
|
49315
49360
|
"tagName": {
|
|
@@ -49319,27 +49364,27 @@
|
|
|
49319
49364
|
]
|
|
49320
49365
|
},
|
|
49321
49366
|
{
|
|
49322
|
-
"name": "
|
|
49323
|
-
"file": "packages/core/schematics/migrate-
|
|
49367
|
+
"name": "resolveRefName",
|
|
49368
|
+
"file": "packages/core/schematics/migrate-to-signals/index.ts",
|
|
49324
49369
|
"ctype": "miscellaneous",
|
|
49325
49370
|
"subtype": "function",
|
|
49326
49371
|
"coverageIgnore": false,
|
|
49327
49372
|
"deprecated": false,
|
|
49328
49373
|
"deprecationMessage": "",
|
|
49329
|
-
"rawdescription": "",
|
|
49330
|
-
"description": "",
|
|
49331
|
-
"displayName": "
|
|
49374
|
+
"rawdescription": "Resolves a reference name from an expression.\nHandles: `ref`, `this.ref`, `this._ref`",
|
|
49375
|
+
"description": "<p>Resolves a reference name from an expression.\nHandles: <code>ref</code>, <code>this.ref</code>, <code>this._ref</code></p>\n",
|
|
49376
|
+
"displayName": "resolveRefName",
|
|
49332
49377
|
"args": [
|
|
49333
49378
|
{
|
|
49334
|
-
"name": "
|
|
49379
|
+
"name": "expr",
|
|
49335
49380
|
"deprecated": false,
|
|
49336
49381
|
"deprecationMessage": ""
|
|
49337
49382
|
}
|
|
49338
49383
|
],
|
|
49339
|
-
"returnType": "
|
|
49384
|
+
"returnType": "string | null",
|
|
49340
49385
|
"jsdoctags": [
|
|
49341
49386
|
{
|
|
49342
|
-
"name": "
|
|
49387
|
+
"name": "expr",
|
|
49343
49388
|
"deprecated": false,
|
|
49344
49389
|
"deprecationMessage": "",
|
|
49345
49390
|
"tagName": {
|
|
@@ -49349,64 +49394,30 @@
|
|
|
49349
49394
|
]
|
|
49350
49395
|
},
|
|
49351
49396
|
{
|
|
49352
|
-
"name": "
|
|
49353
|
-
"file": "packages/core/schematics/migrate-
|
|
49397
|
+
"name": "tryRewritePropertyAccess",
|
|
49398
|
+
"file": "packages/core/schematics/migrate-to-signals/index.ts",
|
|
49354
49399
|
"ctype": "miscellaneous",
|
|
49355
49400
|
"subtype": "function",
|
|
49356
49401
|
"coverageIgnore": false,
|
|
49357
49402
|
"deprecated": false,
|
|
49358
49403
|
"deprecationMessage": "",
|
|
49359
|
-
"rawdescription": "",
|
|
49360
|
-
"description": "",
|
|
49361
|
-
"displayName": "
|
|
49404
|
+
"rawdescription": "Checks if a property access `ref.prop` should be rewritten to `ref.prop()`.\n\nWe rewrite if:\n- `ref` is tracked in euiRefs\n- `prop` is in the signal set for that EUI class\n- The access is NOT already a call expression (not `ref.prop()` or `ref.prop(args)`)\n- The access is NOT a write target (not `ref.prop = value`)\n- The access is NOT a template binding argument (those are unaffected)",
|
|
49405
|
+
"description": "<p>Checks if a property access <code>ref.prop</code> should be rewritten to <code>ref.prop()</code>.</p>\n<p>We rewrite if:</p>\n<ul>\n<li><code>ref</code> is tracked in euiRefs</li>\n<li><code>prop</code> is in the signal set for that EUI class</li>\n<li>The access is NOT already a call expression (not <code>ref.prop()</code> or <code>ref.prop(args)</code>)</li>\n<li>The access is NOT a write target (not <code>ref.prop = value</code>)</li>\n<li>The access is NOT a template binding argument (those are unaffected)</li>\n</ul>\n",
|
|
49406
|
+
"displayName": "tryRewritePropertyAccess",
|
|
49362
49407
|
"args": [
|
|
49363
49408
|
{
|
|
49364
|
-
"name": "
|
|
49365
|
-
"type": "DirEntry",
|
|
49409
|
+
"name": "node",
|
|
49366
49410
|
"deprecated": false,
|
|
49367
49411
|
"deprecationMessage": ""
|
|
49368
49412
|
},
|
|
49369
49413
|
{
|
|
49370
|
-
"name": "
|
|
49414
|
+
"name": "euiRefs",
|
|
49415
|
+
"type": "Map",
|
|
49371
49416
|
"deprecated": false,
|
|
49372
49417
|
"deprecationMessage": ""
|
|
49373
|
-
}
|
|
49374
|
-
],
|
|
49375
|
-
"returnType": "void",
|
|
49376
|
-
"jsdoctags": [
|
|
49377
|
-
{
|
|
49378
|
-
"name": "dir",
|
|
49379
|
-
"type": "DirEntry",
|
|
49380
|
-
"deprecated": false,
|
|
49381
|
-
"deprecationMessage": "",
|
|
49382
|
-
"tagName": {
|
|
49383
|
-
"text": "param"
|
|
49384
|
-
}
|
|
49385
49418
|
},
|
|
49386
49419
|
{
|
|
49387
|
-
"name": "
|
|
49388
|
-
"deprecated": false,
|
|
49389
|
-
"deprecationMessage": "",
|
|
49390
|
-
"tagName": {
|
|
49391
|
-
"text": "param"
|
|
49392
|
-
}
|
|
49393
|
-
}
|
|
49394
|
-
]
|
|
49395
|
-
},
|
|
49396
|
-
{
|
|
49397
|
-
"name": "visitNodes",
|
|
49398
|
-
"file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
|
|
49399
|
-
"ctype": "miscellaneous",
|
|
49400
|
-
"subtype": "function",
|
|
49401
|
-
"coverageIgnore": false,
|
|
49402
|
-
"deprecated": false,
|
|
49403
|
-
"deprecationMessage": "",
|
|
49404
|
-
"rawdescription": "",
|
|
49405
|
-
"description": "",
|
|
49406
|
-
"displayName": "visitNodes",
|
|
49407
|
-
"args": [
|
|
49408
|
-
{
|
|
49409
|
-
"name": "nodes",
|
|
49420
|
+
"name": "sourceFile",
|
|
49410
49421
|
"deprecated": false,
|
|
49411
49422
|
"deprecationMessage": ""
|
|
49412
49423
|
},
|
|
@@ -49415,29 +49426,12 @@
|
|
|
49415
49426
|
"type": "string",
|
|
49416
49427
|
"deprecated": false,
|
|
49417
49428
|
"deprecationMessage": ""
|
|
49418
|
-
},
|
|
49419
|
-
{
|
|
49420
|
-
"name": "edits",
|
|
49421
|
-
"deprecated": false,
|
|
49422
|
-
"deprecationMessage": ""
|
|
49423
|
-
},
|
|
49424
|
-
{
|
|
49425
|
-
"name": "filePath",
|
|
49426
|
-
"type": "string",
|
|
49427
|
-
"deprecated": false,
|
|
49428
|
-
"deprecationMessage": ""
|
|
49429
|
-
},
|
|
49430
|
-
{
|
|
49431
|
-
"name": "context",
|
|
49432
|
-
"type": "SchematicContext",
|
|
49433
|
-
"deprecated": false,
|
|
49434
|
-
"deprecationMessage": ""
|
|
49435
49429
|
}
|
|
49436
49430
|
],
|
|
49437
|
-
"returnType": "
|
|
49431
|
+
"returnType": "Edit | null",
|
|
49438
49432
|
"jsdoctags": [
|
|
49439
49433
|
{
|
|
49440
|
-
"name": "
|
|
49434
|
+
"name": "node",
|
|
49441
49435
|
"deprecated": false,
|
|
49442
49436
|
"deprecationMessage": "",
|
|
49443
49437
|
"tagName": {
|
|
@@ -49445,8 +49439,8 @@
|
|
|
49445
49439
|
}
|
|
49446
49440
|
},
|
|
49447
49441
|
{
|
|
49448
|
-
"name": "
|
|
49449
|
-
"type": "
|
|
49442
|
+
"name": "euiRefs",
|
|
49443
|
+
"type": "Map",
|
|
49450
49444
|
"deprecated": false,
|
|
49451
49445
|
"deprecationMessage": "",
|
|
49452
49446
|
"tagName": {
|
|
@@ -49454,7 +49448,7 @@
|
|
|
49454
49448
|
}
|
|
49455
49449
|
},
|
|
49456
49450
|
{
|
|
49457
|
-
"name": "
|
|
49451
|
+
"name": "sourceFile",
|
|
49458
49452
|
"deprecated": false,
|
|
49459
49453
|
"deprecationMessage": "",
|
|
49460
49454
|
"tagName": {
|
|
@@ -49462,17 +49456,38 @@
|
|
|
49462
49456
|
}
|
|
49463
49457
|
},
|
|
49464
49458
|
{
|
|
49465
|
-
"name": "
|
|
49459
|
+
"name": "source",
|
|
49466
49460
|
"type": "string",
|
|
49467
49461
|
"deprecated": false,
|
|
49468
49462
|
"deprecationMessage": "",
|
|
49469
49463
|
"tagName": {
|
|
49470
49464
|
"text": "param"
|
|
49471
49465
|
}
|
|
49472
|
-
}
|
|
49466
|
+
}
|
|
49467
|
+
]
|
|
49468
|
+
},
|
|
49469
|
+
{
|
|
49470
|
+
"name": "unwrapExpression",
|
|
49471
|
+
"file": "packages/core/schematics/migrate-to-signals/index.ts",
|
|
49472
|
+
"ctype": "miscellaneous",
|
|
49473
|
+
"subtype": "function",
|
|
49474
|
+
"coverageIgnore": false,
|
|
49475
|
+
"deprecated": false,
|
|
49476
|
+
"deprecationMessage": "",
|
|
49477
|
+
"rawdescription": "",
|
|
49478
|
+
"description": "",
|
|
49479
|
+
"displayName": "unwrapExpression",
|
|
49480
|
+
"args": [
|
|
49473
49481
|
{
|
|
49474
|
-
"name": "
|
|
49475
|
-
"
|
|
49482
|
+
"name": "expression",
|
|
49483
|
+
"deprecated": false,
|
|
49484
|
+
"deprecationMessage": ""
|
|
49485
|
+
}
|
|
49486
|
+
],
|
|
49487
|
+
"returnType": "ts.Expression",
|
|
49488
|
+
"jsdoctags": [
|
|
49489
|
+
{
|
|
49490
|
+
"name": "expression",
|
|
49476
49491
|
"deprecated": false,
|
|
49477
49492
|
"deprecationMessage": "",
|
|
49478
49493
|
"tagName": {
|
|
@@ -49482,8 +49497,8 @@
|
|
|
49482
49497
|
]
|
|
49483
49498
|
},
|
|
49484
49499
|
{
|
|
49485
|
-
"name": "
|
|
49486
|
-
"file": "packages/core/schematics/migrate-
|
|
49500
|
+
"name": "visitDir",
|
|
49501
|
+
"file": "packages/core/schematics/migrate-to-signals/index.ts",
|
|
49487
49502
|
"ctype": "miscellaneous",
|
|
49488
49503
|
"subtype": "function",
|
|
49489
49504
|
"coverageIgnore": false,
|
|
@@ -49491,22 +49506,16 @@
|
|
|
49491
49506
|
"deprecationMessage": "",
|
|
49492
49507
|
"rawdescription": "",
|
|
49493
49508
|
"description": "",
|
|
49494
|
-
"displayName": "
|
|
49509
|
+
"displayName": "visitDir",
|
|
49495
49510
|
"args": [
|
|
49496
49511
|
{
|
|
49497
|
-
"name": "
|
|
49498
|
-
"
|
|
49499
|
-
"deprecationMessage": ""
|
|
49500
|
-
},
|
|
49501
|
-
{
|
|
49502
|
-
"name": "filePath",
|
|
49503
|
-
"type": "string",
|
|
49512
|
+
"name": "dir",
|
|
49513
|
+
"type": "DirEntry",
|
|
49504
49514
|
"deprecated": false,
|
|
49505
49515
|
"deprecationMessage": ""
|
|
49506
49516
|
},
|
|
49507
49517
|
{
|
|
49508
|
-
"name": "
|
|
49509
|
-
"type": "SchematicContext",
|
|
49518
|
+
"name": "callback",
|
|
49510
49519
|
"deprecated": false,
|
|
49511
49520
|
"deprecationMessage": ""
|
|
49512
49521
|
}
|
|
@@ -49514,16 +49523,8 @@
|
|
|
49514
49523
|
"returnType": "void",
|
|
49515
49524
|
"jsdoctags": [
|
|
49516
49525
|
{
|
|
49517
|
-
"name": "
|
|
49518
|
-
"
|
|
49519
|
-
"deprecationMessage": "",
|
|
49520
|
-
"tagName": {
|
|
49521
|
-
"text": "param"
|
|
49522
|
-
}
|
|
49523
|
-
},
|
|
49524
|
-
{
|
|
49525
|
-
"name": "filePath",
|
|
49526
|
-
"type": "string",
|
|
49526
|
+
"name": "dir",
|
|
49527
|
+
"type": "DirEntry",
|
|
49527
49528
|
"deprecated": false,
|
|
49528
49529
|
"deprecationMessage": "",
|
|
49529
49530
|
"tagName": {
|
|
@@ -49531,8 +49532,7 @@
|
|
|
49531
49532
|
}
|
|
49532
49533
|
},
|
|
49533
49534
|
{
|
|
49534
|
-
"name": "
|
|
49535
|
-
"type": "SchematicContext",
|
|
49535
|
+
"name": "callback",
|
|
49536
49536
|
"deprecated": false,
|
|
49537
49537
|
"deprecationMessage": "",
|
|
49538
49538
|
"tagName": {
|