UNPKG

@progress/kendo-angular-toolbar

Version:

Kendo UI Angular Toolbar component - a single UI element that organizes buttons and other navigation elements

554 lines (553 loc) 24.8 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"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.tsComponentPropertyRemoval = exports.tsPropertyRemoval = exports.templateAttributeRemoval = exports.htmlAttributeRemoval = exports.htmlAttributeValueTransformer = exports.tsPropertyValueTransformer = exports.templateAttributeValueTransformer = exports.htmlAttributeTransformer = exports.htmlBoundAttributeTransformer = exports.htmlStaticAttributeTransformer = exports.tsPropertyTransformer = exports.templateAttributeTransformer = exports.templateBoundAttributeTransformer = exports.templateStaticAttributeTransformer = void 0; const node_html_parser_1 = __importDefault(require("node-html-parser")); const templateStaticAttributeTransformer = (root, tagName, attributeName, newAttributeName) => { const elements = Array.from(root.getElementsByTagName(tagName)) || []; for (const element of elements) { // Handle static attributes like title="foo" const staticAttr = element.getAttribute(attributeName); if (staticAttr) { element.setAttribute(newAttributeName, staticAttr); element.removeAttribute(attributeName); } } }; exports.templateStaticAttributeTransformer = templateStaticAttributeTransformer; const templateBoundAttributeTransformer = (root, tagName, attributeName, newAttributeName) => { const elements = Array.from(root.getElementsByTagName(tagName)) || []; for (const element of elements) { // Handle bound attributes like [title]="foo" or [title]="'foo'" const boundAttr = element.getAttribute(`[${attributeName}]`); if (boundAttr) { element.setAttribute(`[${newAttributeName}]`, boundAttr); element.removeAttribute(`[${attributeName}]`); } } }; exports.templateBoundAttributeTransformer = templateBoundAttributeTransformer; const templateAttributeTransformer = (root, tagName, attributeName, newAttributeName) => { (0, exports.templateBoundAttributeTransformer)(root, tagName, attributeName, newAttributeName); (0, exports.templateStaticAttributeTransformer)(root, tagName, attributeName, newAttributeName); }; exports.templateAttributeTransformer = templateAttributeTransformer; const tsPropertyTransformer = (root, j, componentType, propertyName, newPropertyName) => { // Find all class properties that are of type DropDownListComponent 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); } }); } }); // Find all member expressions where title property 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 if (path.node.object.type === 'Identifier' && parameters.has(path.node.object.name)) { 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; } }); }; exports.tsPropertyTransformer = tsPropertyTransformer; const htmlStaticAttributeTransformer = (fileInfo, tagName, oldName, newName) => { const fileContent = fileInfo.source; const root = (0, node_html_parser_1.default)(fileContent); let modified = false; const elements = Array.from(root.querySelectorAll(tagName)); for (const element of elements) { const staticAttr = element.getAttribute(oldName); if (staticAttr) { element.removeAttribute(oldName); element.setAttribute(newName, staticAttr); modified = true; } } if (modified) { return root.toString(); } return fileContent; }; exports.htmlStaticAttributeTransformer = htmlStaticAttributeTransformer; const htmlBoundAttributeTransformer = (fileInfo, tagName, oldName, newName) => { const fileContent = fileInfo.source; const root = (0, node_html_parser_1.default)(fileContent); let modified = false; const elements = Array.from(root.querySelectorAll(tagName)); for (const element of elements) { const boundAttr = element.getAttribute(`[${oldName}]`); if (boundAttr) { element.removeAttribute(`[${oldName}]`); element.setAttribute(`[${newName}]`, boundAttr); modified = true; } } if (modified) { return root.toString(); } return fileContent; }; exports.htmlBoundAttributeTransformer = htmlBoundAttributeTransformer; const htmlAttributeTransformer = (fileInfo, tagName, oldName, newName) => { let content = (0, exports.htmlBoundAttributeTransformer)(fileInfo, tagName, oldName, newName); content = (0, exports.htmlStaticAttributeTransformer)({ path: fileInfo.path, source: content }, tagName, oldName, newName); return content; }; exports.htmlAttributeTransformer = htmlAttributeTransformer; const templateAttributeValueTransformer = (root, tagName, attributeName, oldAttributeValue, newAttributeValue) => { const elements = Array.from(root.getElementsByTagName(tagName)) || []; for (const element of elements) { // Handle bound attributes (e.g., [showText]="'overflow'") const boundAttr = element.getAttribute(`[${attributeName}]`); if (boundAttr === `'${oldAttributeValue}'`) { // For bound literals like [showText]="'overflow'" or [showText]="\"overflow\"" element.setAttribute(`[${attributeName}]`, boundAttr.replace(oldAttributeValue, newAttributeValue)); } // Handle static attributes like title="foo" const staticAttrValue = element.getAttribute(attributeName); if (staticAttrValue === oldAttributeValue) { element.setAttribute(attributeName, newAttributeValue); } } }; exports.templateAttributeValueTransformer = templateAttributeValueTransformer; const tsPropertyValueTransformer = (root, j, typeName, oldValue, newValue) => { // 1. Find all class properties with the specified type root .find(j.ClassProperty) .filter(path => { // Check if the property has a type annotation matching the specified type 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 => { // Update the value if it matches the old value if (path.node.value && path.node.value.type === 'StringLiteral' && path.node.value.value === oldValue) { path.node.value.value = newValue; } }); // 2. Find all assignments to variables of the specified type const variablesOfType = new Set(); // First, collect all variables with the specified type 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') { variablesOfType.add(path.node.id.name); // Also update the initial value if it matches if (path.node.init && path.node.init.type === 'StringLiteral' && path.node.init.value === oldValue) { path.node.init.value = newValue; } } }); // 3. Update literals in assignment expressions root .find(j.AssignmentExpression) .filter(path => { // Only process string literals with the old value return path.node.right.type === 'StringLiteral' && path.node.right.value === oldValue; }) .forEach(path => { // Update the value path.node.right.value = newValue; }); // 4. Also look for string literals in attributes within JSX elements 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 htmlAttributeValueTransformer = (fileInfo, tagName, attributeName, oldValue, newValue) => { // Read file content from fileInfo const fileContent = fileInfo.source; // Parse the HTML content const root = (0, node_html_parser_1.default)(fileContent); // Find all elements matching the tagName const elements = root.querySelectorAll(tagName); let modified = false; // Process each element for (const element of elements) { // Handle static attributes (e.g., showText="overflow") const staticAttr = element.getAttribute(attributeName); if (staticAttr === oldValue) { element.setAttribute(attributeName, newValue); modified = true; console.log(`Modified static attribute ${attributeName} from "${oldValue}" to "${newValue}" in element:`, element.toString().substring(0, 100)); } // Handle bound attributes (e.g., [showText]="overflow") const boundAttr = element.getAttribute(`[${attributeName}]`); if (boundAttr) { // For bound literals like [showText]="'overflow'" or [showText]="\"overflow\"" if (boundAttr === `'${oldValue}'` || boundAttr === `"${oldValue}"`) { const updatedValue = boundAttr.replace(oldValue, newValue); element.setAttribute(`[${attributeName}]`, updatedValue); modified = true; console.log(`Modified bound attribute [${attributeName}] from "${boundAttr}" to "${updatedValue}" in element:`, element.toString().substring(0, 100)); } } } // Return modified content if changes were made if (modified) { const updatedContent = root.toString(); return updatedContent; } // Return original content if no changes were made or if there was an error return fileContent; }; exports.htmlAttributeValueTransformer = htmlAttributeValueTransformer; const htmlAttributeRemoval = (fileInfo, tagName, attributeName, propertyToRemove) => { const filePath = fileInfo.path; const fileContent = fileInfo.source; const root = (0, node_html_parser_1.default)(fileContent); // Use the same logic as templateAttributeRemoval const elements = root.querySelectorAll(tagName); for (const element of elements) { // Look for bound attribute (e.g., [scrollable]="...") const boundAttr = element.getAttribute(`[${attributeName}]`); if (boundAttr) { // Check if it's an object literal if (boundAttr.trim().startsWith('{') && boundAttr.trim().endsWith('}')) { // Process object literal like {mouseScrollSpeed: 10000} const objectLiteral = boundAttr.trim(); // Build a regex that matches the property and its value // This handles various formats like {prop: value}, { prop: value }, etc. const propRegex = new RegExp(`\\s*${propertyToRemove}\\s*:\\s*[^,}]+\\s*(,\\s*)?`, 'g'); // Remove the property and any trailing comma let newObjectLiteral = objectLiteral.replace(propRegex, ''); // Fix syntax if we removed the last property with trailing comma newObjectLiteral = newObjectLiteral.replace(/,\s*}$/, '}'); // If the object is now empty, remove the attribute completely if (newObjectLiteral === '{}') { element.removeAttribute(`[${attributeName}]`); } else { element.setAttribute(`[${attributeName}]`, newObjectLiteral); } } // Check if it's a variable reference to an object else { // For variable references, we can't modify them in the template // We should warn the user or handle this case specially console.warn(`Cannot remove property from variable reference: ${boundAttr} in file ${filePath}`); } } } // Return the modified HTML content return root.toString(); }; exports.htmlAttributeRemoval = htmlAttributeRemoval; /** * Removes a specified property from an object binding in HTML templates * * @param root - The HTML root element * @param tagName - The tag to search for (e.g., 'kendo-tabstrip') * @param attributeName - The attribute containing the object binding (e.g., 'scrollable') * @param propertyToRemove - The property to remove from the object (e.g., 'mouseScrollSpeed') */ const templateAttributeRemoval = (root, tagName, attributeName, propertyToRemove) => { const elements = root.querySelectorAll(tagName); for (const element of elements) { // Look for bound attribute (e.g., [scrollable]="...") const boundAttr = element.getAttribute(`[${attributeName}]`); if (boundAttr) { // Check if it's an object literal if (boundAttr.trim().startsWith('{') && boundAttr.trim().endsWith('}')) { // Process object literal like {mouseScrollSpeed: 10000} const objectLiteral = boundAttr.trim(); // Build a regex that matches the property and its value // This handles various formats like {prop: value}, { prop: value }, etc. const propRegex = new RegExp(`\\s*${propertyToRemove}\\s*:\\s*[^,}]+\\s*(,\\s*)?`, 'g'); // Remove the property and any trailing comma let newObjectLiteral = objectLiteral.replace(propRegex, ''); // Fix syntax if we removed the last property with trailing comma newObjectLiteral = newObjectLiteral.replace(/,\s*}$/, '}'); // If the object is now empty, remove the attribute completely if (newObjectLiteral === '{}') { element.removeAttribute(`[${attributeName}]`); } else { element.setAttribute(`[${attributeName}]`, newObjectLiteral); } } // Check if it's a variable reference to an object else { // For variable references, we can't modify them in the template // We should warn the user or handle this case specially console.warn(`Cannot remove property from variable reference: ${boundAttr}`); } } } }; exports.templateAttributeRemoval = templateAttributeRemoval; /** * Removes a property from object literals of a specified type * * @param root - The AST root * @param j - The JSCodeshift instance * @param typeName - The type to target (e.g., 'TabStripScrollableSettings') * @param propertyToRemove - The property to remove (e.g., 'mouseScrollSpeed') */ function tsPropertyRemoval(rootSource, j, typeName, propertyName) { // Find class properties that have the specified type rootSource .find(j.ClassProperty, { typeAnnotation: { typeAnnotation: { typeName: { name: typeName } } } }) .forEach(path => { // Check if there's an object literal initializer if (path.node.value && path.node.value.type === 'ObjectExpression') { const properties = path.node.value.properties; // Find the property we want to remove - safely handle different property types const propIndex = properties.findIndex((p) => p.type === 'ObjectProperty' && p.key && p.key.type === 'Identifier' && p.key.name === propertyName); if (propIndex !== -1) { // If property exists, remove it // Case 1: If it's the only property, remove the entire class property if (properties.length === 1) { j(path).remove(); } // Case 2: If there are other properties, just remove this one property else { properties.splice(propIndex, 1); } } } }); // Also handle property assignments (e.g., in methods like ngOnInit) rootSource .find(j.AssignmentExpression, { left: { type: 'MemberExpression', object: { type: 'MemberExpression' }, property: { name: propertyName } } }) .forEach(path => { j(path).remove(); }); return rootSource; } exports.tsPropertyRemoval = tsPropertyRemoval; /** * Removes assignments to a specific nested property of a component * * @param root - The AST root * @param j - The JSCodeshift instance * @param componentType - The component type to target (e.g., 'TabStripComponent') * @param componentProperty - The component property (e.g., 'scrollable') * @param propertyToRemove - The nested property to remove assignments to (e.g., 'mouseScrollSpeed') */ const tsComponentPropertyRemoval = (root, j, componentType, componentProperty, propertyToRemove) => { // 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 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 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; // Helper function to check if a node is a component of the specified type function isComponentTypeMatch(root, j, node, componentType) { if (!node) return false; // Case 1: Direct match for 'this.propertyName' if (node.type === 'ThisExpression') { return true; // Assuming 'this' refers to the component class } // Case 2: Function parameter if (node.type === 'Identifier') { const paramName = node.name; // Check function parameters return root .find(j.Function) .some(path => { return path.node.params && path.node.params.some((param) => param.type === 'Identifier' && param.name === paramName && param.typeAnnotation?.typeAnnotation?.typeName?.name === componentType); }); } // Case 3: Member expression (obj.prop) if (node.type === 'MemberExpression') { // This would need more complex logic to determine if the object is of the right type // For now, we can check if it's a property that has been declared with the right type if (node.object.type === 'ThisExpression' && node.property.type === 'Identifier') { const propName = node.property.name; return root .find(j.ClassProperty, { key: { name: propName }, typeAnnotation: { typeAnnotation: { typeName: { name: componentType } } } }) .size() > 0; } } return false; }