@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
JavaScript
/**-----------------------------------------------------------------------------------------
* Copyright © 2025 Progress Software Corporation. All rights reserved.
* Licensed under commercial license. See LICENSE.md in the project root for more information
*-------------------------------------------------------------------------------------------*/
;
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;
}