UNPKG

@progress/kendo-angular-dropdowns

Version:

A wide variety of native Angular dropdown components including AutoComplete, ComboBox, DropDownList, DropDownTree, MultiColumnComboBox, MultiSelect, and MultiSelectTree

969 lines 57.9 kB
/**----------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the project root for more information *-------------------------------------------------------------------------------------------*/ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.tsInterfaceTransformer = exports.tsPropertyValueTransformer = exports.tsPropertyTransformer = exports.tsComponentPropertyRemoval = exports.attributeRemoval = exports.attributeValueUpdate = exports.attributeNameValueUpdate = exports.attributeNameUpdate = exports.eventUpdate = exports.htmlTransformer = exports.blockTextElements = void 0; exports.hasKendoInTemplate = hasKendoInTemplate; exports.isImportedFromPackage = isImportedFromPackage; exports.tsPropertyRemoval = tsPropertyRemoval; exports.blockTextElements = { script: true, noscript: true, style: true, pre: true, }; function hasKendoInTemplate(source) { const kendoPattern = /(<kendo-[^>\s]+|<[^>]*\s+kendo[A-Z][^>\s]*)/g; return kendoPattern.test(source); } /** * Checks if a specific type/interface is imported from the given package * @param root - The JSCodeshift collection * @param j - JSCodeshift instance * @param packageName - The package name to check (e.g., '@progress/kendo-angular-dateinputs') * @param typeName - The type/interface name to check (e.g., 'DatePickerComponent') * @returns true if the type is imported from the package, false otherwise */ function isImportedFromPackage(root, j, packageName, typeName) { let isImported = false; root.find(j.ImportDeclaration).forEach((path) => { if (path.node.source && path.node.source.value === packageName && path.node.specifiers) { path.node.specifiers.forEach((specifier) => { if (specifier.type === 'ImportSpecifier' && specifier.imported.type === 'Identifier' && specifier.imported.name === typeName) { isImported = true; } }); } }); return isImported; } /** * Transforms HTML files and inline templates using a provided transformer function * * @param fileInfo - The file info containing path and source * @param api - JSCodeshift API * @param transformerFn - The transformer function to apply to template content * @returns The transformed source code or undefined if no changes */ const htmlTransformer = (fileInfo, api, transformerFn) => { const filePath = fileInfo.path; if (filePath.endsWith('.html')) { if (hasKendoInTemplate(fileInfo.source)) { let updatedContent = fileInfo.source; updatedContent = transformerFn(updatedContent); return updatedContent; } return fileInfo.source; } const j = api.jscodeshift; const rootSource = j(fileInfo.source); // Transform inline templates using the provided transformer function rootSource .find(j.ClassDeclaration) .forEach(classPath => { const classNode = classPath.node; if (!classNode.decorators || !classNode.decorators.length) return; const componentDecorator = classNode.decorators.find((decorator) => { if (decorator.expression && decorator.expression.type === 'CallExpression') { const callee = decorator.expression.callee; if (callee.type === 'Identifier' && callee.name === 'Component') { return true; } if (callee.type === 'MemberExpression' && callee.property && callee.property.type === 'Identifier' && callee.property.name === 'Component') { return true; } } return false; }); if (!componentDecorator || !componentDecorator.expression) return; const expression = componentDecorator.expression; if (expression.type !== 'CallExpression' || !expression.arguments.length) return; const componentOptions = expression.arguments[0]; if (componentOptions.type !== 'ObjectExpression') return; const props = componentOptions.properties || []; const templateProp = props.find((prop) => (prop.key.type === 'Identifier' && prop.key.name === 'template') || (prop.key.type === 'StringLiteral' && prop.key.value === 'template')); if (templateProp) { let originalTemplate; if (templateProp.value.type === 'StringLiteral' || templateProp.value.type === 'Literal') { originalTemplate = templateProp.value.value; } else if (templateProp.value.type === 'TemplateLiteral') { if (templateProp.value.quasis && templateProp.value.quasis.length) { originalTemplate = templateProp.value.quasis .map((q) => q.value.cooked || q.value.raw) .join(''); } else { return; } } else { return; } if (hasKendoInTemplate(originalTemplate)) { // Apply the provided transformer function const transformedTemplate = transformerFn(originalTemplate); if (transformedTemplate !== originalTemplate) { if (templateProp.value.type === 'TemplateLiteral') { templateProp.value = j.templateLiteral([j.templateElement({ cooked: transformedTemplate, raw: transformedTemplate }, true)], []); } else { templateProp.value.value = transformedTemplate; } } } } }); return rootSource.toSource(); }; exports.htmlTransformer = htmlTransformer; const eventUpdate = (templateContent, tagName, oldEventName, newEventName) => { // Escape special regex characters in tag name const escapedTagName = tagName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // Create regex pattern to match the tag with the old event binding // Pattern matches: <tagName ...attributes... (oldEventName)="handler" ...> const eventPattern = new RegExp(`(<${escapedTagName}[^>]*\\()${oldEventName}(\\)=)`, 'g'); // Replace old event name with new event name const updatedContent = templateContent.replace(eventPattern, `$1${newEventName}$2`); return updatedContent; }; exports.eventUpdate = eventUpdate; /** * Transforms attributes in inline templates using regex patterns. * This function handles bound ([attribute]), static (attribute="value"), * two-way binding ([(attribute)]), and boolean (attribute) attributes * within a specific tag using regular expressions. * * @param templateContent - The template string content to transform * @param tagName - The HTML tag name to target (e.g., 'kendo-datepicker') * @param attributeName - The current attribute name to replace * @param newAttributeName - The new attribute name * @returns The transformed template content */ const attributeNameUpdate = (templateContent, tagName, attributeName, newAttributeName) => { // Escape special regex characters in tag and attribute names const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const escapedTag = escapeRegex(tagName); const escapedAttr = escapeRegex(attributeName); const escapedNewAttr = escapeRegex(newAttributeName); // Pattern to match the opening tag with the attribute // This pattern matches: <tagName ... attribute="value" ... > or <tagName ... [attribute]="value" ... > // It captures the tag opening, everything before the attribute, the attribute binding type, and everything after const boundAttributePattern = new RegExp(`(<${escapedTag}[^>]*?\\s+)\\[${escapedAttr}\\]\\s*=\\s*("(?:[^"\\\\]|\\\\.)*?"|'(?:[^'\\\\]|\\\\.)*?')([^>]*?>)`, 'gi'); const staticAttributePattern = new RegExp(`(<${escapedTag}[^>]*?\\s+)${escapedAttr}\\s*=\\s*("(?:[^"\\\\]|\\\\.)*?"|'(?:[^'\\\\]|\\\\.)*?')([^>]*?>)`, 'gi'); // Pattern for two-way data binding [(attribute)]="value" const twoWayBindingPattern = new RegExp(`(<${escapedTag}[^>]*?\\s+)\\[\\(${escapedAttr}\\)\\]\\s*=\\s*("(?:[^"\\\\]|\\\\.)*?"|'(?:[^'\\\\]|\\\\.)*?')([^>]*?>)`, 'gi'); // Pattern for boolean attributes without values (e.g., <tag attribute>) const booleanAttributePattern = new RegExp(`(<${escapedTag}[^>]*?\\s+)${escapedAttr}(\\s+[^>=]|\\s*[/>])`, 'gi'); // Replace two-way data binding [(attribute)]="value" let result = templateContent.replace(twoWayBindingPattern, `$1[(${escapedNewAttr})]=$2$3`); // Replace bound attributes [attribute]="value" result = result.replace(boundAttributePattern, `$1[${escapedNewAttr}]=$2$3`); // Replace static attributes attribute="value" result = result.replace(staticAttributePattern, `$1${escapedNewAttr}=$2$3`); // Replace boolean attributes attribute (without value) result = result.replace(booleanAttributePattern, `$1${escapedNewAttr}$2`); return result; }; exports.attributeNameUpdate = attributeNameUpdate; /** * Transforms bound attributes with value property extraction using regex patterns. * This function handles bound attributes and extracts a specific property from object literals * or appends the property to variable references. * * @param templateContent - The template string content to transform * @param tagName - The HTML tag name to target (e.g., 'kendo-chat') * @param attributeName - The current attribute name to replace * @param newAttributeName - The new attribute name * @param valueProperty - The property to extract (e.g., 'id') * @returns The transformed template content */ const attributeNameValueUpdate = (templateContent, tagName, attributeName, newAttributeName, valueProperty) => { // Escape special regex characters in tag and attribute names const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const escapedTag = escapeRegex(tagName); const escapedAttr = escapeRegex(attributeName); const escapedNewAttr = escapeRegex(newAttributeName); // Pattern to match bound attributes [attribute]="value" // This captures: opening tag, bound attribute, and closing tag const boundAttributePattern = new RegExp(`(<${escapedTag}[^>]*?\\s+)\\[${escapedAttr}\\]\\s*=\\s*("(?:[^"\\\\]|\\\\.)*?"|'(?:[^'\\\\]|\\\\.)*?'|[^\\s>]+)([^>]*?>)`, 'gi'); return templateContent.replace(boundAttributePattern, (match, beforeAttr, value, afterAttr) => { // Remove quotes from value to analyze it const trimmedValue = value.replace(/^["']|["']$/g, ''); // Check if it's an object literal (starts with { and ends with }) if (trimmedValue.trim().startsWith('{') && trimmedValue.trim().endsWith('}')) { // For object literals like {id: 'foo'} or {id: 'foo', bar: 'baz'} // We need to append .id inside the quotes if the value is quoted if (value.match(/^["']/)) { // Extract quote type and content, then add .id before the closing quote const quoteType = value.charAt(0); const content = value.slice(1, -1); // Remove quotes const newValue = `${quoteType}${content}.${valueProperty}${quoteType}`; return `${beforeAttr}[${escapedNewAttr}]=${newValue}${afterAttr}`; } else { // Unquoted object literal const newValue = `${value}.${valueProperty}`; return `${beforeAttr}[${escapedNewAttr}]=${newValue}${afterAttr}`; } } else { // For variable references like "user" // We append .id to the variable name const newValue = value.replace(/^["'](.*)["']$/, (_match, content) => { return `"${content}.${valueProperty}"`; }); // If it's not quoted (bare variable), add quotes and property if (!value.match(/^["']/)) { return `${beforeAttr}[${escapedNewAttr}]="${trimmedValue}.${valueProperty}"${afterAttr}`; } return `${beforeAttr}[${escapedNewAttr}]=${newValue}${afterAttr}`; } }); }; exports.attributeNameValueUpdate = attributeNameValueUpdate; /** * Updates attribute values in HTML templates using regex patterns. * This function handles both bound ([attribute]="'value'") and static (attribute="value") attributes * and replaces old values with new values within a specific tag. * * @param templateContent - The template string content to transform * @param tagName - The HTML tag name to target (e.g., 'kendo-toolbar') * @param attributeName - The attribute name to target (e.g., 'showIcon') * @param oldValue - The old attribute value to replace (e.g., 'overflow') * @param newValue - The new attribute value (e.g., 'menu') * @returns The transformed template content */ const attributeValueUpdate = (templateContent, tagName, attributeName, oldValue, newValue) => { // Escape special regex characters in tag, attribute names, and values const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const escapedTag = escapeRegex(tagName); const escapedAttr = escapeRegex(attributeName); const escapedOldValue = escapeRegex(oldValue); // Pattern for bound attributes [attribute]="'value'" (double quotes containing single quotes) const boundDoubleSinglePattern = new RegExp(`(<${escapedTag}[^>]*?\\s+\\[${escapedAttr}\\]\\s*=\\s*")'${escapedOldValue}'("\\s*[^>]*?>)`, 'gi'); // Pattern for bound attributes [attribute]="\"value\"" (double quotes containing double quotes) const boundDoubleDoublePattern = new RegExp(`(<${escapedTag}[^>]*?\\s+\\[${escapedAttr}\\]\\s*=\\s*")\\\\"${escapedOldValue}\\\\"("\\s*[^>]*?>)`, 'gi'); // Pattern for bound attributes [attribute]='value' (single quotes containing value) const boundSinglePattern = new RegExp(`(<${escapedTag}[^>]*?\\s+\\[${escapedAttr}\\]\\s*=\\s*)'${escapedOldValue}'([^>]*?>)`, 'gi'); // Pattern for static attributes attribute="value" const staticAttributePattern = new RegExp(`(<${escapedTag}[^>]*?\\s+${escapedAttr}\\s*=\\s*["'])${escapedOldValue}(["'][^>]*?>)`, 'gi'); // Replace bound attributes [attribute]="'overflow'" -> [attribute]="'menu'" let result = templateContent.replace(boundDoubleSinglePattern, `$1'${newValue}'$2`); // Replace bound attributes [attribute]="\"overflow\"" -> [attribute]="\"menu\"" result = result.replace(boundDoubleDoublePattern, `$1\\"${newValue}\\"$2`); // Replace bound attributes [attribute]='overflow' -> [attribute]='menu' result = result.replace(boundSinglePattern, `$1'${newValue}'$2`); // Replace static attributes showIcon="overflow" -> showIcon="menu" result = result.replace(staticAttributePattern, `$1${newValue}$2`); return result; }; exports.attributeValueUpdate = attributeValueUpdate; /** * Removes attributes from HTML templates using regex patterns. * This function can remove entire attributes or specific properties from object literal attributes. * * @param templateContent - The template string content to transform * @param tagName - The HTML tag name to target (e.g., 'kendo-chat') * @param attributeName - The attribute name to remove or modify * @param propertyToRemove - Optional: specific property to remove from object literal attributes * @returns The transformed template content */ const attributeRemoval = (templateContent, tagName, attributeName, propertyToRemove) => { // Escape special regex characters in tag and attribute names const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const escapedTag = escapeRegex(tagName); const escapedAttr = escapeRegex(attributeName); // If no propertyToRemove is specified, remove the entire attribute if (!propertyToRemove) { // Remove bound attributes [attribute]="value" const boundAttributePattern = new RegExp(`(\\s+)\\[${escapedAttr}\\]\\s*=\\s*("(?:[^"\\\\]|\\\\.)*?"|'(?:[^'\\\\]|\\\\.)*?'|[^\\s>]+)`, 'gi'); // Remove static attributes attribute="value" const staticAttributePattern = new RegExp(`(\\s+)${escapedAttr}\\s*=\\s*("(?:[^"\\\\]|\\\\.)*?"|'(?:[^'\\\\]|\\\\.)*?'|[^\\s>]+)`, 'gi'); // Apply removals let result = templateContent.replace(boundAttributePattern, ''); result = result.replace(staticAttributePattern, ''); return result; } // Remove specific property from object literal attributes const boundAttributePattern = new RegExp(`(<${escapedTag}[^>]*?\\s+\\[${escapedAttr}\\]\\s*=\\s*)("(?:[^"\\\\]|\\\\.)*?"|'(?:[^'\\\\]|\\\\.)*?'|[^\\s>]+)([^>]*?>)`, 'gi'); return templateContent.replace(boundAttributePattern, (match, beforeAttr, value, afterAttr) => { // Remove quotes from value to analyze it const trimmedValue = value.replace(/^["']|["']$/g, ''); // Check if it's an object literal (starts with { and ends with }) if (trimmedValue.trim().startsWith('{') && trimmedValue.trim().endsWith('}')) { const objectLiteral = trimmedValue.trim(); // Create regex to remove the specific property const propRegex = new RegExp(`\\s*${escapeRegex(propertyToRemove)}\\s*:\\s*[^,}]+\\s*(,\\s*)?`, 'g'); let newObjectLiteral = objectLiteral.replace(propRegex, ''); // Clean up trailing comma before closing brace newObjectLiteral = newObjectLiteral.replace(/,\s*}$/, '}'); // If the object is now empty, remove the entire attribute if (newObjectLiteral === '{}') { return beforeAttr.replace(/\s+\[[^\]]+\]\s*=\s*$/, '') + afterAttr; } else { // Restore quotes if the original value was quoted if (value.match(/^["']/)) { const quoteType = value.charAt(0); return `${beforeAttr}${quoteType}${newObjectLiteral}${quoteType}${afterAttr}`; } else { return `${beforeAttr}${newObjectLiteral}${afterAttr}`; } } } else { // For non-object literals, we can't remove a specific property console.warn(`Cannot remove property '${propertyToRemove}' from non-object literal: ${value}`); return match; // Return unchanged } }); }; exports.attributeRemoval = attributeRemoval; function tsPropertyRemoval(source, rootSource, j, packageName, typeName, propertyName) { if (source.includes(typeName)) { if (!isImportedFromPackage(rootSource, j, packageName, typeName)) { return; } const typeVariables = new Set(); // Find function parameters of target type rootSource.find(j.Function).forEach((path) => { if (path.node.params) { path.node.params.forEach((param) => { if (param.type === 'Identifier' && param.typeAnnotation && param.typeAnnotation.typeAnnotation?.type === 'TSTypeReference' && param.typeAnnotation.typeAnnotation.typeName?.type === 'Identifier' && param.typeAnnotation.typeAnnotation.typeName.name === typeName) { typeVariables.add(param.name); } }); } }); // Find local variables of target type rootSource.find(j.VariableDeclarator).forEach((path) => { if (path.node.id.type === 'Identifier' && path.node.id.typeAnnotation && path.node.id.typeAnnotation.typeAnnotation?.type === 'TSTypeReference' && path.node.id.typeAnnotation.typeAnnotation.typeName?.type === 'Identifier' && path.node.id.typeAnnotation.typeAnnotation.typeName.name === typeName) { typeVariables.add(path.node.id.name); } }); // Find class properties of target type rootSource.find(j.ClassProperty).forEach((path) => { if (path.node.key.type === 'Identifier' && path.node.typeAnnotation && path.node.typeAnnotation.typeAnnotation && path.node.typeAnnotation.typeAnnotation.type === 'TSTypeReference' && path.node.typeAnnotation.typeAnnotation.typeName && path.node.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' && path.node.typeAnnotation.typeAnnotation.typeName.name === typeName) { typeVariables.add(path.node.key.name); } }); // Handle class properties with object expressions rootSource .find(j.ClassProperty) .filter((path) => { // Check if the property has the correct type annotation return !!(path.node.typeAnnotation && path.node.typeAnnotation.typeAnnotation && path.node.typeAnnotation.typeAnnotation.type === 'TSTypeReference' && path.node.typeAnnotation.typeAnnotation.typeName && path.node.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' && path.node.typeAnnotation.typeAnnotation.typeName.name === typeName); }) .forEach((path) => { if (path.node.value && path.node.value.type === 'ObjectExpression') { const properties = path.node.value.properties; const propIndex = properties.findIndex((p) => p.type === 'ObjectProperty' && p.key && p.key.type === 'Identifier' && p.key.name === propertyName); if (propIndex !== -1) { // Just remove the property, leaving an empty object if it was the only one properties.splice(propIndex, 1); } } }); // Handle variable declarations with object literal initializers // e.g., const modelFields: ConversationalUIModelFields = { pinnedByField: 'value', id: 'bar' }; rootSource .find(j.VariableDeclarator, { id: { type: 'Identifier', typeAnnotation: { typeAnnotation: { type: 'TSTypeReference', typeName: { name: typeName, }, }, }, }, }) .forEach((path) => { if (path.node.init && path.node.init.type === 'ObjectExpression') { const properties = path.node.init.properties; const propIndex = properties.findIndex((p) => p.type === 'ObjectProperty' && p.key && p.key.type === 'Identifier' && p.key.name === propertyName); if (propIndex !== -1) { // Just remove the property, leaving an empty object if it was the only one properties.splice(propIndex, 1); } } }); // Handle return statements with object literals rootSource.find(j.ReturnStatement).forEach((path) => { if (path.node.argument && path.node.argument.type === 'ObjectExpression') { const properties = path.node.argument.properties; const propIndex = properties.findIndex((p) => p.type === 'ObjectProperty' && p.key && p.key.type === 'Identifier' && p.key.name === propertyName); if (propIndex !== -1) { properties.splice(propIndex, 1); } } }); // Handle direct member expression assignments to properties of variables/parameters // e.g., fields.pinnedByField = value; or this.chatModelFields.pinnedByField = value; rootSource .find(j.AssignmentExpression) .filter((path) => { const { left } = path.node; if (left.type === 'MemberExpression' && left.property.type === 'Identifier' && left.property.name === propertyName) { // Check if the object is a variable/parameter of our type if (left.object.type === 'Identifier' && typeVariables.has(left.object.name)) { return true; } // Check if it's this.property where property is of our type if (left.object.type === 'MemberExpression' && left.object.object.type === 'ThisExpression' && left.object.property.type === 'Identifier' && typeVariables.has(left.object.property.name)) { return true; } // Check for type assertions: (expr as Type).property if (left.object.type === 'TSAsExpression' && left.object.typeAnnotation.type === 'TSTypeReference' && left.object.typeAnnotation.typeName?.type === 'Identifier' && left.object.typeAnnotation.typeName.name === typeName) { return true; } } return false; }) .forEach((path) => { // Remove the entire expression statement const statement = j(path).closest(j.ExpressionStatement); if (statement.length > 0) { statement.remove(); } }); // Handle nested member expressions like chatConfig.chat.modelFields.pinnedByField rootSource .find(j.AssignmentExpression, { left: { type: 'MemberExpression', object: { type: 'MemberExpression', }, property: { name: propertyName, }, }, }) .forEach((path) => { j(path).closest(j.ExpressionStatement).remove(); }); return rootSource; } } const tsComponentPropertyRemoval = (source, root, j, packageName, componentType, componentProperty, propertyToRemove) => { if (source.includes(componentType)) { // Check if componentType is imported from the specified package if (!isImportedFromPackage(root, j, packageName, componentType)) { return; } // Find all class properties that are of type componentType const properties = new Set(); root.find(j.ClassProperty, { typeAnnotation: { typeAnnotation: { typeName: { name: componentType, }, }, }, }).forEach((path) => { if (path.node.key.type === 'Identifier') { properties.add(path.node.key.name); } }); // Find function parameters of type componentType const parameters = new Set(); root.find(j.FunctionDeclaration).forEach((path) => { if (path.node.params) { path.node.params.forEach((param) => { if (param.type === 'Identifier' && param.typeAnnotation && param.typeAnnotation.typeAnnotation?.type === 'TSTypeReference' && param.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' && param.typeAnnotation.typeAnnotation.typeName.name === componentType) { parameters.add(param.name); } }); } }); // Also check method declarations in classes root.find(j.ClassMethod).forEach((path) => { if (path.node.params) { path.node.params.forEach((param) => { if (param.type === 'Identifier' && param.typeAnnotation && param.typeAnnotation.typeAnnotation?.type === 'TSTypeReference' && param.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' && param.typeAnnotation.typeAnnotation.typeName.name === componentType) { parameters.add(param.name); } }); } }); // Also check arrow functions root.find(j.ArrowFunctionExpression).forEach((path) => { if (path.node.params) { path.node.params.forEach((param) => { if (param.type === 'Identifier' && param.typeAnnotation && param.typeAnnotation.typeAnnotation?.type === 'TSTypeReference' && param.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' && param.typeAnnotation.typeAnnotation.typeName.name === componentType) { parameters.add(param.name); } }); } }); // Find local variable declarations of type componentType const localVariables = new Set(); root.find(j.VariableDeclarator).forEach((path) => { if (path.node.id.type === 'Identifier' && path.node.id.typeAnnotation && path.node.id.typeAnnotation.typeAnnotation?.type === 'TSTypeReference' && path.node.id.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' && path.node.id.typeAnnotation.typeAnnotation.typeName.name === componentType) { localVariables.add(path.node.id.name); } }); // Find array variables of type componentType[] // This handles cases like: const arr: ChatComponent[] = [...]; arr[0].property = value; const arrayVariables = new Set(); root.find(j.VariableDeclarator).forEach((path) => { if (path.node.id.type === 'Identifier' && path.node.id.typeAnnotation && path.node.id.typeAnnotation.typeAnnotation?.type === 'TSArrayType' && path.node.id.typeAnnotation.typeAnnotation.elementType?.type === 'TSTypeReference' && path.node.id.typeAnnotation.typeAnnotation.elementType.typeName?.type === 'Identifier' && path.node.id.typeAnnotation.typeAnnotation.elementType.typeName.name === componentType) { arrayVariables.add(path.node.id.name); } }); // Find object properties that have componentType (e.g., {chat: ChatComponent}) // This handles cases like: const config: {chat: ChatComponent} = {...}; config.chat.property = value; const objectProperties = new Map(); // Maps variable name to property name root.find(j.VariableDeclarator).forEach((path) => { if (path.node.id.type === 'Identifier' && path.node.id.typeAnnotation && path.node.id.typeAnnotation.typeAnnotation?.type === 'TSTypeLiteral') { const varName = path.node.id.name; const members = path.node.id.typeAnnotation.typeAnnotation.members; members.forEach((member) => { if (member.type === 'TSPropertySignature' && member.key && member.key.type === 'Identifier' && member.typeAnnotation && member.typeAnnotation.typeAnnotation?.type === 'TSTypeReference' && member.typeAnnotation.typeAnnotation.typeName?.type === 'Identifier' && member.typeAnnotation.typeAnnotation.typeName.name === componentType) { if (!objectProperties.has(varName)) { objectProperties.set(varName, []); } objectProperties.get(varName).push(member.key.name); } }); } }); // If no propertyToRemove is specified, remove the entire componentProperty if (!propertyToRemove) { // Handle direct property assignments like: foo.scrollable = value; root.find(j.AssignmentExpression) .filter((path) => { const { left } = path.value; // Check if this assigns to component.componentProperty if (left && left.type === 'MemberExpression' && left.property && left.property.name === componentProperty) { // Check if the base object is our component type return isComponentTypeMatch(root, j, left.object, componentType); } return false; }) .forEach((path) => { // Remove the entire statement j(path).closest(j.ExpressionStatement).remove(); }); return root; } // CASE 1: Handle direct property assignments like: foo.scrollable.mouseScrollSpeed = 3000; root.find(j.AssignmentExpression) .filter((path) => { const { left } = path.value; // Check if this is a member expression assignment if (left && left.type === 'MemberExpression') { // Check if we're accessing the property to remove if (left.property && left.property.name === propertyToRemove) { // Check if we're accessing it from component.componentProperty const obj = left.object; if (obj && obj.type === 'MemberExpression' && obj.property && obj.property.name === componentProperty) { // Now check if the base object is our component type (includes all cases: this, parameters, variables, casts) return isComponentTypeMatch(root, j, obj.object, componentType); } } } return false; }) .forEach((path) => { // Remove the entire statement j(path).closest(j.ExpressionStatement).remove(); }); // CASE 2 & 3: Handle object assignments like: foo.scrollable = { mouseScrollSpeed: 3000, ... }; root.find(j.AssignmentExpression) .filter((path) => { const { left, right } = path.value; // Check if this assigns to component.componentProperty if (left && left.type === 'MemberExpression' && left.property && left.property.name === componentProperty && right && right.type === 'ObjectExpression') { // Check if the base object is our component type (includes all cases: this, parameters, variables, casts) return isComponentTypeMatch(root, j, left.object, componentType); } return false; }) .forEach((path) => { const properties = path.value.right.properties; // Find the property we want to remove const propIndex = properties.findIndex((p) => p && p.type === 'ObjectProperty' && p.key && p.key.type === 'Identifier' && p.key.name === propertyToRemove); if (propIndex !== -1) { // Case 2: If it's the only property, remove the entire statement if (properties.length === 1) { j(path).closest(j.ExpressionStatement).remove(); } // Case 3: If there are other properties, just remove this one property else { properties.splice(propIndex, 1); } } }); return root; } }; exports.tsComponentPropertyRemoval = tsComponentPropertyRemoval; const tsPropertyTransformer = (source, root, j, packageName, componentType, propertyName, newPropertyName, valueProperty) => { if (source.includes(componentType)) { // Check if componentType is imported from the specified package if (!isImportedFromPackage(root, j, packageName, componentType)) { return; } // Find all class properties that are of type componentType const properties = new Set(); // Find properties with type annotations root.find(j.ClassProperty, { typeAnnotation: { typeAnnotation: { typeName: { name: componentType, }, }, }, }).forEach((path) => { if (path.node.key.type === 'Identifier') { properties.add(path.node.key.name); } }); // Find function parameters of type componentType const parameters = new Set(); root.find(j.FunctionDeclaration).forEach((path) => { if (path.node.params) { path.node.params.forEach((param) => { if (param.type === 'Identifier' && param.typeAnnotation && param.typeAnnotation.typeAnnotation?.type === 'TSTypeReference' && param.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' && param.typeAnnotation.typeAnnotation.typeName.name === componentType) { parameters.add(param.name); } }); } }); // Also check method declarations in classes root.find(j.ClassMethod).forEach((path) => { if (path.node.params) { path.node.params.forEach((param) => { if (param.type === 'Identifier' && param.typeAnnotation && param.typeAnnotation.typeAnnotation?.type === 'TSTypeReference' && param.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' && param.typeAnnotation.typeAnnotation.typeName.name === componentType) { parameters.add(param.name); } }); } }); // Also check arrow functions root.find(j.ArrowFunctionExpression).forEach((path) => { if (path.node.params) { path.node.params.forEach((param) => { if (param.type === 'Identifier' && param.typeAnnotation && param.typeAnnotation.typeAnnotation?.type === 'TSTypeReference' && param.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' && param.typeAnnotation.typeAnnotation.typeName.name === componentType) { parameters.add(param.name); } }); } }); // Find local variable declarations of type componentType const localVariables = new Set(); root.find(j.VariableDeclarator).forEach((path) => { if (path.node.id.type === 'Identifier' && path.node.id.typeAnnotation && path.node.id.typeAnnotation.typeAnnotation?.type === 'TSTypeReference' && path.node.id.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' && path.node.id.typeAnnotation.typeAnnotation.typeName.name === componentType) { localVariables.add(path.node.id.name); } }); // Find all member expressions where propertyName is accessed on any componentType instance root.find(j.MemberExpression, { property: { type: 'Identifier', name: propertyName, }, }) .filter((path) => { // Filter to only include accesses on properties that are componentType instances if (path.node.object.type === 'MemberExpression' && path.node.object.property.type === 'Identifier') { // handle properties of this if (path.node.object.object.type === 'ThisExpression' && properties.has(path.node.object.property.name)) { return true; } } // Handle function parameters and local variables if (path.node.object.type === 'Identifier') { return parameters.has(path.node.object.name) || localVariables.has(path.node.object.name); } // Handle TypeScript type assertions like (this.componentProperty as ComponentType).property if (path.node.object.type === 'TSAsExpression') { const typeAssertion = path.node.object; // Check if the type assertion is casting to our componentType if (typeAssertion.typeAnnotation && typeAssertion.typeAnnotation.type === 'TSTypeReference' && typeAssertion.typeAnnotation.typeName && typeAssertion.typeAnnotation.typeName.type === 'Identifier' && typeAssertion.typeAnnotation.typeName.name === componentType) { return true; } } return false; }) .forEach((path) => { // Replace old property name with new property name if (path.node.property.type === 'Identifier') { path.node.property.name = newPropertyName; } // If valueProperty is specified and this is part of an assignment, // we need to also modify the right-hand side of the assignment if (valueProperty) { const assignmentExpression = path.parent; if (assignmentExpression && assignmentExpression.value && assignmentExpression.value.type === 'AssignmentExpression' && assignmentExpression.value.left === path.node) { const rightSide = assignmentExpression.value.right; // Case 1: Right side is a member expression (e.g., this.user, obj.user) -> transform to this.user.id, obj.user.id // Case 2: Right side is an identifier (e.g., user, foo) -> transform to user.id, foo.id if (rightSide.type === 'MemberExpression' || rightSide.type === 'Identifier') { const newRightSide = j.memberExpression(rightSide, j.identifier(valueProperty)); assignmentExpression.value.right = newRightSide; } // Case 3: Right side is object literal -> extract the valueProperty value else if (rightSide.type === 'ObjectExpression') { // Find the property that matches valueProperty const targetProperty = rightSide.properties.find((prop) => prop.type === 'ObjectProperty' && prop.key && prop.key.type === 'Identifier' && prop.key.name === valueProperty); if (targetProperty) { // Replace the entire object literal with just the value of the target property assignmentExpression.value.right = targetProperty.value; } } } } }); } }; exports.tsPropertyTransformer = tsPropertyTransformer; const tsPropertyValueTransformer = (source, root, j, packageName, typeName, oldValue, newValue) => { if (source.includes(typeName)) { // Check if typeName is imported from the specified package if (!isImportedFromPackage(root, j, packageName, typeName)) { return; } root.find(j.ClassProperty) .filter((path) => { if (path.node.typeAnnotation?.typeAnnotation && path.node.typeAnnotation.typeAnnotation.type === 'TSTypeReference' && path.node.typeAnnotation.typeAnnotation.typeName && path.node.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' && path.node.typeAnnotation.typeAnnotation.typeName.name === typeName) { return true; } return false; }) .forEach((path) => { if (path.node.value && path.node.value.type === 'StringLiteral' && path.node.value.value === oldValue) { path.node.value.value = newValue; } }); root.find(j.VariableDeclarator) .filter((path) => { if (path.node.id.type === 'Identifier' && path.node.id.typeAnnotation?.typeAnnotation && path.node.id.typeAnnotation.typeAnnotation.type === 'TSTypeReference' && path.node.id.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' && path.node.id.typeAnnotation.typeAnnotation.typeName.name === typeName) { return true; } return false; }) .forEach((path) => { if (path.node.id.type === 'Identifier') { if (path.node.init && path.node.init.type === 'StringLiteral' && path.node.init.value === oldValue) { path.node.init.value = newValue; } } }); root.find(j.AssignmentExpression) .filter((path) => { return path.node.right.type === 'StringLiteral' && path.node.right.value === oldValue; }) .forEach((path) => { path.node.right.value = newValue; }); root.find(j.JSXAttribute, { value: { type: 'StringLiteral', value: oldValue, }, }).forEach((path) => { if (path.node.value?.type === 'StringLiteral') { path.node.value.value = newValue; } }); } }; exports.tsPropertyValueTransformer = tsPropertyValueTransformer; const tsInterfaceTransformer = (fileInfo, rootSource, j, packageName, interfaceName, newName) => { const source = fileInfo.source; if (source.includes(interfaceName)) { // Check if interface is imported from the specified package and rename it let isImported = false; rootSource.find(j.ImportDeclaration).forEach((path) => { if (path.node.source && path.node.source.value === packageName && path.node.specifiers) { path.node.specifiers.forEach((specifier) => { if (specifier.type === 'ImportSpecifier' && specifier.imported.type === 'Identifier' && specifier.imported.name === interfaceName) { isImported = true; specifier.imported.name = newName; } }); } }); if (!isImported) { return; } rootSource.find(j.ClassProperty).forEach((path) => { if (path.node.typeAnnotation && path.node.typeAnnotation.typeAnnotation && path.node.typeAnnotation.typeAnnotation.type === 'TSTypeReference' && path.node.typeAnnotation.typeAnnotation.typeName && path.node.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' && path.node.typeAnnotation.typeAnnotation.typeName.name === interfaceName) { path.node.typeAnnotation.typeAnnotation.typeName.name = newName; } }); rootSource.find(j.VariableDeclarator).forEach((path) => { if (path.node.id.type === 'Identifier' && path.node.id.typeAnnotation && path.node.id.typeAnnotation.typeAnnotation && path.node.id.typeAnnotation.typeAnnotation.type === 'TSTypeReference' && path.node.id.typeAnnotation.typeAnnotation.typeName && path.node.id.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' && path.node.id.typeAnnotation.typeAnnotation.typeName.name === interfaceName) { path.node.id.typeAnnotation.typeAnnotation.typeName.name = newName; } }); rootSource.find(j.FunctionDeclaration).forEach((path) => { if (path.node.params) { path.node.params.forEach((param) => { if (param.type === 'Identifier' && param.typeAnnotation && param.typeAnnotation.typeAnnotation && param.typeAnnotation.typeAnnotation.type === 'TSTypeReference' && param.typeAnnotation.typeAnnotation.typeName && param.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' && param.typeAnnotation.typeAnnotation.typeName.name === interfaceName) { param.typeAnnotation.typeAnnotation.typeName.name = newName; } }); } }); rootSource.find(j.ArrowFunctionExpression).forEach((path) => { if (path.node.params