@progress/kendo-angular-common 24.0.0-develop.1 → 24.0.0-develop.3

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.
@@ -0,0 +1,1453 @@
1
+ /**-----------------------------------------------------------------------------------------
2
+ * Copyright © 2026 Progress Software Corporation. All rights reserved.
3
+ * Licensed under commercial license. See LICENSE.md in the project root for more information
4
+ *-------------------------------------------------------------------------------------------*/
5
+ "use strict";
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.tsInterfaceTransformer = exports.tsPropertyValueTransformer = exports.tsPropertyTransformer = exports.tsComponentPropertyRemoval = exports.attributeRemoval = exports.attributeValueUpdate = exports.attributeNameValueUpdate = exports.attributeNameUpdate = exports.eventUpdate = exports.htmlTransformer = exports.blockTextElements = void 0;
8
+ exports.hasKendoInTemplate = hasKendoInTemplate;
9
+ exports.isImportedFromPackage = isImportedFromPackage;
10
+ exports.tsPropertyRemoval = tsPropertyRemoval;
11
+ exports.makePattern = makePattern;
12
+ exports.writeInstructionMarker = writeInstructionMarker;
13
+ exports.isApiChangeTarget = isApiChangeTarget;
14
+ exports.isRenderingChangeTarget = isRenderingChangeTarget;
15
+ exports.executeCodemodTest = executeCodemodTest;
16
+ const tslib_1 = require("tslib");
17
+ /// <reference types="node" />
18
+ const fs = tslib_1.__importStar(require("fs"));
19
+ const os = tslib_1.__importStar(require("os"));
20
+ const path = tslib_1.__importStar(require("path"));
21
+ exports.blockTextElements = {
22
+ script: true,
23
+ noscript: true,
24
+ style: true,
25
+ pre: true,
26
+ };
27
+ function hasKendoInTemplate(source) {
28
+ const kendoPattern = /(<kendo-[^>\s]+|<[^>]*\s+kendo[A-Z][^>\s]*)/g;
29
+ return kendoPattern.test(source);
30
+ }
31
+ /**
32
+ * Checks if a specific type/interface is imported from the given package
33
+ * @param root - The JSCodeshift collection
34
+ * @param j - JSCodeshift instance
35
+ * @param packageName - The package name to check (e.g., '@progress/kendo-angular-dateinputs')
36
+ * @param typeName - The type/interface name to check (e.g., 'DatePickerComponent')
37
+ * @returns true if the type is imported from the package, false otherwise
38
+ */
39
+ function isImportedFromPackage(root, j, packageName, typeName) {
40
+ let isImported = false;
41
+ root.find(j.ImportDeclaration).forEach((path) => {
42
+ if (path.node.source &&
43
+ path.node.source.value === packageName &&
44
+ path.node.specifiers) {
45
+ path.node.specifiers.forEach((specifier) => {
46
+ if (specifier.type === 'ImportSpecifier' &&
47
+ specifier.imported.type === 'Identifier' &&
48
+ specifier.imported.name === typeName) {
49
+ isImported = true;
50
+ }
51
+ });
52
+ }
53
+ });
54
+ return isImported;
55
+ }
56
+ /**
57
+ * Transforms HTML files and inline templates using a provided transformer function
58
+ *
59
+ * @param fileInfo - The file info containing path and source
60
+ * @param api - JSCodeshift API
61
+ * @param transformerFn - The transformer function to apply to template content
62
+ * @returns The transformed source code or undefined if no changes
63
+ */
64
+ const htmlTransformer = (fileInfo, api, transformerFn) => {
65
+ const filePath = fileInfo.path;
66
+ if (filePath.endsWith('.html')) {
67
+ if (hasKendoInTemplate(fileInfo.source)) {
68
+ let updatedContent = fileInfo.source;
69
+ updatedContent = transformerFn(updatedContent);
70
+ return updatedContent;
71
+ }
72
+ return fileInfo.source;
73
+ }
74
+ const j = api.jscodeshift;
75
+ const rootSource = j(fileInfo.source);
76
+ // Transform inline templates using the provided transformer function
77
+ rootSource
78
+ .find(j.ClassDeclaration)
79
+ .forEach(classPath => {
80
+ const classNode = classPath.node;
81
+ if (!classNode.decorators || !classNode.decorators.length)
82
+ return;
83
+ const componentDecorator = classNode.decorators.find((decorator) => {
84
+ if (decorator.expression && decorator.expression.type === 'CallExpression') {
85
+ const callee = decorator.expression.callee;
86
+ if (callee.type === 'Identifier' && callee.name === 'Component') {
87
+ return true;
88
+ }
89
+ if (callee.type === 'MemberExpression' &&
90
+ callee.property &&
91
+ callee.property.type === 'Identifier' &&
92
+ callee.property.name === 'Component') {
93
+ return true;
94
+ }
95
+ }
96
+ return false;
97
+ });
98
+ if (!componentDecorator || !componentDecorator.expression)
99
+ return;
100
+ const expression = componentDecorator.expression;
101
+ if (expression.type !== 'CallExpression' || !expression.arguments.length)
102
+ return;
103
+ const componentOptions = expression.arguments[0];
104
+ if (componentOptions.type !== 'ObjectExpression')
105
+ return;
106
+ const props = componentOptions.properties || [];
107
+ const templateProp = props.find((prop) => (prop.key.type === 'Identifier' && prop.key.name === 'template') ||
108
+ (prop.key.type === 'StringLiteral' && prop.key.value === 'template'));
109
+ if (templateProp) {
110
+ let originalTemplate;
111
+ if (templateProp.value.type === 'StringLiteral' || templateProp.value.type === 'Literal') {
112
+ originalTemplate = templateProp.value.value;
113
+ }
114
+ else if (templateProp.value.type === 'TemplateLiteral') {
115
+ if (templateProp.value.quasis && templateProp.value.quasis.length) {
116
+ originalTemplate = templateProp.value.quasis
117
+ .map((q) => q.value.cooked || q.value.raw)
118
+ .join('');
119
+ }
120
+ else {
121
+ return;
122
+ }
123
+ }
124
+ else {
125
+ return;
126
+ }
127
+ if (hasKendoInTemplate(originalTemplate)) {
128
+ // Apply the provided transformer function
129
+ const transformedTemplate = transformerFn(originalTemplate);
130
+ if (transformedTemplate !== originalTemplate) {
131
+ if (templateProp.value.type === 'TemplateLiteral') {
132
+ templateProp.value = j.templateLiteral([j.templateElement({ cooked: transformedTemplate, raw: transformedTemplate }, true)], []);
133
+ }
134
+ else {
135
+ templateProp.value.value = transformedTemplate;
136
+ }
137
+ }
138
+ }
139
+ }
140
+ });
141
+ return rootSource.toSource();
142
+ };
143
+ exports.htmlTransformer = htmlTransformer;
144
+ const eventUpdate = (templateContent, tagName, oldEventName, newEventName) => {
145
+ // Escape special regex characters in tag name
146
+ const escapedTagName = tagName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
147
+ // Create regex pattern to match the tag with the old event binding
148
+ // Pattern matches: <tagName ...attributes... (oldEventName)="handler" ...>
149
+ const eventPattern = new RegExp(`(<${escapedTagName}[^>]*\\()${oldEventName}(\\)=)`, 'g');
150
+ // Replace old event name with new event name
151
+ const updatedContent = templateContent.replace(eventPattern, `$1${newEventName}$2`);
152
+ return updatedContent;
153
+ };
154
+ exports.eventUpdate = eventUpdate;
155
+ /**
156
+ * Transforms attributes in inline templates using regex patterns.
157
+ * This function handles bound ([attribute]), static (attribute="value"),
158
+ * two-way binding ([(attribute)]), and boolean (attribute) attributes
159
+ * within a specific tag using regular expressions.
160
+ *
161
+ * @param templateContent - The template string content to transform
162
+ * @param tagName - The HTML tag name to target (e.g., 'kendo-datepicker')
163
+ * @param attributeName - The current attribute name to replace
164
+ * @param newAttributeName - The new attribute name
165
+ * @returns The transformed template content
166
+ */
167
+ const attributeNameUpdate = (templateContent, tagName, attributeName, newAttributeName) => {
168
+ // Escape special regex characters in tag and attribute names
169
+ const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
170
+ const escapedTag = escapeRegex(tagName);
171
+ const escapedAttr = escapeRegex(attributeName);
172
+ const escapedNewAttr = escapeRegex(newAttributeName);
173
+ // Pattern to match the opening tag with the attribute
174
+ // This pattern matches: <tagName ... attribute="value" ... > or <tagName ... [attribute]="value" ... >
175
+ // It captures the tag opening, everything before the attribute, the attribute binding type, and everything after
176
+ const boundAttributePattern = new RegExp(`(<${escapedTag}[^>]*?\\s+)\\[${escapedAttr}\\]\\s*=\\s*("(?:[^"\\\\]|\\\\.)*?"|'(?:[^'\\\\]|\\\\.)*?')([^>]*?>)`, 'gi');
177
+ const staticAttributePattern = new RegExp(`(<${escapedTag}[^>]*?\\s+)${escapedAttr}\\s*=\\s*("(?:[^"\\\\]|\\\\.)*?"|'(?:[^'\\\\]|\\\\.)*?')([^>]*?>)`, 'gi');
178
+ // Pattern for two-way data binding [(attribute)]="value"
179
+ const twoWayBindingPattern = new RegExp(`(<${escapedTag}[^>]*?\\s+)\\[\\(${escapedAttr}\\)\\]\\s*=\\s*("(?:[^"\\\\]|\\\\.)*?"|'(?:[^'\\\\]|\\\\.)*?')([^>]*?>)`, 'gi');
180
+ // Pattern for boolean attributes without values (e.g., <tag attribute>)
181
+ const booleanAttributePattern = new RegExp(`(<${escapedTag}[^>]*?\\s+)${escapedAttr}(\\s+[^>=]|\\s*[/>])`, 'gi');
182
+ // Replace two-way data binding [(attribute)]="value"
183
+ let result = templateContent.replace(twoWayBindingPattern, `$1[(${escapedNewAttr})]=$2$3`);
184
+ // Replace bound attributes [attribute]="value"
185
+ result = result.replace(boundAttributePattern, `$1[${escapedNewAttr}]=$2$3`);
186
+ // Replace static attributes attribute="value"
187
+ result = result.replace(staticAttributePattern, `$1${escapedNewAttr}=$2$3`);
188
+ // Replace boolean attributes attribute (without value)
189
+ result = result.replace(booleanAttributePattern, `$1${escapedNewAttr}$2`);
190
+ return result;
191
+ };
192
+ exports.attributeNameUpdate = attributeNameUpdate;
193
+ /**
194
+ * Transforms bound attributes with value property extraction using regex patterns.
195
+ * This function handles bound attributes and extracts a specific property from object literals
196
+ * or appends the property to variable references.
197
+ *
198
+ * @param templateContent - The template string content to transform
199
+ * @param tagName - The HTML tag name to target (e.g., 'kendo-chat')
200
+ * @param attributeName - The current attribute name to replace
201
+ * @param newAttributeName - The new attribute name
202
+ * @param valueProperty - The property to extract (e.g., 'id')
203
+ * @returns The transformed template content
204
+ */
205
+ const attributeNameValueUpdate = (templateContent, tagName, attributeName, newAttributeName, valueProperty) => {
206
+ // Escape special regex characters in tag and attribute names
207
+ const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
208
+ const escapedTag = escapeRegex(tagName);
209
+ const escapedAttr = escapeRegex(attributeName);
210
+ const escapedNewAttr = escapeRegex(newAttributeName);
211
+ // Pattern to match bound attributes [attribute]="value"
212
+ // This captures: opening tag, bound attribute, and closing tag
213
+ const boundAttributePattern = new RegExp(`(<${escapedTag}[^>]*?\\s+)\\[${escapedAttr}\\]\\s*=\\s*("(?:[^"\\\\]|\\\\.)*?"|'(?:[^'\\\\]|\\\\.)*?'|[^\\s>]+)([^>]*?>)`, 'gi');
214
+ return templateContent.replace(boundAttributePattern, (match, beforeAttr, value, afterAttr) => {
215
+ // Remove quotes from value to analyze it
216
+ const trimmedValue = value.replace(/^["']|["']$/g, '');
217
+ // Check if it's an object literal (starts with { and ends with })
218
+ if (trimmedValue.trim().startsWith('{') && trimmedValue.trim().endsWith('}')) {
219
+ // For object literals like {id: 'foo'} or {id: 'foo', bar: 'baz'}
220
+ // We need to append .id inside the quotes if the value is quoted
221
+ if (value.match(/^["']/)) {
222
+ // Extract quote type and content, then add .id before the closing quote
223
+ const quoteType = value.charAt(0);
224
+ const content = value.slice(1, -1); // Remove quotes
225
+ const newValue = `${quoteType}${content}.${valueProperty}${quoteType}`;
226
+ return `${beforeAttr}[${escapedNewAttr}]=${newValue}${afterAttr}`;
227
+ }
228
+ else {
229
+ // Unquoted object literal
230
+ const newValue = `${value}.${valueProperty}`;
231
+ return `${beforeAttr}[${escapedNewAttr}]=${newValue}${afterAttr}`;
232
+ }
233
+ }
234
+ else {
235
+ // For variable references like "user"
236
+ // We append .id to the variable name
237
+ const newValue = value.replace(/^["'](.*)["']$/, (_match, content) => {
238
+ return `"${content}.${valueProperty}"`;
239
+ });
240
+ // If it's not quoted (bare variable), add quotes and property
241
+ if (!value.match(/^["']/)) {
242
+ return `${beforeAttr}[${escapedNewAttr}]="${trimmedValue}.${valueProperty}"${afterAttr}`;
243
+ }
244
+ return `${beforeAttr}[${escapedNewAttr}]=${newValue}${afterAttr}`;
245
+ }
246
+ });
247
+ };
248
+ exports.attributeNameValueUpdate = attributeNameValueUpdate;
249
+ /**
250
+ * Updates attribute values in HTML templates using regex patterns.
251
+ * This function handles both bound ([attribute]="'value'") and static (attribute="value") attributes
252
+ * and replaces old values with new values within a specific tag.
253
+ *
254
+ * @param templateContent - The template string content to transform
255
+ * @param tagName - The HTML tag name to target (e.g., 'kendo-toolbar')
256
+ * @param attributeName - The attribute name to target (e.g., 'showIcon')
257
+ * @param oldValue - The old attribute value to replace (e.g., 'overflow')
258
+ * @param newValue - The new attribute value (e.g., 'menu')
259
+ * @returns The transformed template content
260
+ */
261
+ const attributeValueUpdate = (templateContent, tagName, attributeName, oldValue, newValue) => {
262
+ // Escape special regex characters in tag, attribute names, and values
263
+ const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
264
+ const escapedTag = escapeRegex(tagName);
265
+ const escapedAttr = escapeRegex(attributeName);
266
+ const escapedOldValue = escapeRegex(oldValue);
267
+ // Pattern for bound attributes [attribute]="'value'" (double quotes containing single quotes)
268
+ const boundDoubleSinglePattern = new RegExp(`(<${escapedTag}[^>]*?\\s+\\[${escapedAttr}\\]\\s*=\\s*")'${escapedOldValue}'("\\s*[^>]*?>)`, 'gi');
269
+ // Pattern for bound attributes [attribute]="\"value\"" (double quotes containing double quotes)
270
+ const boundDoubleDoublePattern = new RegExp(`(<${escapedTag}[^>]*?\\s+\\[${escapedAttr}\\]\\s*=\\s*")\\\\"${escapedOldValue}\\\\"("\\s*[^>]*?>)`, 'gi');
271
+ // Pattern for bound attributes [attribute]='value' (single quotes containing value)
272
+ const boundSinglePattern = new RegExp(`(<${escapedTag}[^>]*?\\s+\\[${escapedAttr}\\]\\s*=\\s*)'${escapedOldValue}'([^>]*?>)`, 'gi');
273
+ // Pattern for static attributes attribute="value"
274
+ const staticAttributePattern = new RegExp(`(<${escapedTag}[^>]*?\\s+${escapedAttr}\\s*=\\s*["'])${escapedOldValue}(["'][^>]*?>)`, 'gi');
275
+ // Replace bound attributes [attribute]="'overflow'" -> [attribute]="'menu'"
276
+ let result = templateContent.replace(boundDoubleSinglePattern, `$1'${newValue}'$2`);
277
+ // Replace bound attributes [attribute]="\"overflow\"" -> [attribute]="\"menu\""
278
+ result = result.replace(boundDoubleDoublePattern, `$1\\"${newValue}\\"$2`);
279
+ // Replace bound attributes [attribute]='overflow' -> [attribute]='menu'
280
+ result = result.replace(boundSinglePattern, `$1'${newValue}'$2`);
281
+ // Replace static attributes showIcon="overflow" -> showIcon="menu"
282
+ result = result.replace(staticAttributePattern, `$1${newValue}$2`);
283
+ return result;
284
+ };
285
+ exports.attributeValueUpdate = attributeValueUpdate;
286
+ /**
287
+ * Removes attributes from HTML templates using regex patterns.
288
+ * This function can remove entire attributes or specific properties from object literal attributes.
289
+ *
290
+ * @param templateContent - The template string content to transform
291
+ * @param tagName - The HTML tag name to target (e.g., 'kendo-chat')
292
+ * @param attributeName - The attribute name to remove or modify
293
+ * @param propertyToRemove - Optional: specific property to remove from object literal attributes
294
+ * @returns The transformed template content
295
+ */
296
+ const attributeRemoval = (templateContent, tagName, attributeName, propertyToRemove) => {
297
+ // Escape special regex characters in tag and attribute names
298
+ const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
299
+ const escapedTag = escapeRegex(tagName);
300
+ const escapedAttr = escapeRegex(attributeName);
301
+ // If no propertyToRemove is specified, remove the entire attribute
302
+ if (!propertyToRemove) {
303
+ // Remove bound attributes [attribute]="value"
304
+ const boundAttributePattern = new RegExp(`(<${escapedTag}[^>]*?)\\s+\\[${escapedAttr}\\]\\s*=\\s*("(?:[^"\\\\]|\\\\.)*?"|'(?:[^'\\\\]|\\\\.)*?'|[^\\s>]+)([^>]*?>)`, 'gi');
305
+ // Remove static attributes attribute="value"
306
+ const staticAttributePattern = new RegExp(`(<${escapedTag}[^>]*?)\\s+${escapedAttr}\\s*=\\s*("(?:[^"\\\\]|\\\\.)*?"|'(?:[^'\\\\]|\\\\.)*?'|[^\\s>]+)([^>]*?>)`, 'gi');
307
+ // Apply removals - keep tag prefix and suffix, remove the attribute
308
+ let result = templateContent.replace(boundAttributePattern, '$1$3');
309
+ result = result.replace(staticAttributePattern, '$1$3');
310
+ return result;
311
+ }
312
+ // Remove specific property from object literal attributes
313
+ const boundAttributePattern = new RegExp(`(<${escapedTag}[^>]*?\\s+\\[${escapedAttr}\\]\\s*=\\s*)("(?:[^"\\\\]|\\\\.)*?"|'(?:[^'\\\\]|\\\\.)*?'|[^\\s>]+)([^>]*?>)`, 'gi');
314
+ return templateContent.replace(boundAttributePattern, (match, beforeAttr, value, afterAttr) => {
315
+ // Remove quotes from value to analyze it
316
+ const trimmedValue = value.replace(/^["']|["']$/g, '');
317
+ // Check if it's an object literal (starts with { and ends with })
318
+ if (trimmedValue.trim().startsWith('{') && trimmedValue.trim().endsWith('}')) {
319
+ const objectLiteral = trimmedValue.trim();
320
+ // Create regex to remove the specific property
321
+ const propRegex = new RegExp(`\\s*${escapeRegex(propertyToRemove)}\\s*:\\s*[^,}]+\\s*(,\\s*)?`, 'g');
322
+ let newObjectLiteral = objectLiteral.replace(propRegex, '');
323
+ // Clean up trailing comma before closing brace
324
+ newObjectLiteral = newObjectLiteral.replace(/,\s*}$/, '}');
325
+ // If the object is now empty, remove the entire attribute
326
+ if (newObjectLiteral === '{}') {
327
+ return beforeAttr.replace(/\s+\[[^\]]+\]\s*=\s*$/, '') + afterAttr;
328
+ }
329
+ else {
330
+ // Restore quotes if the original value was quoted
331
+ if (value.match(/^["']/)) {
332
+ const quoteType = value.charAt(0);
333
+ return `${beforeAttr}${quoteType}${newObjectLiteral}${quoteType}${afterAttr}`;
334
+ }
335
+ else {
336
+ return `${beforeAttr}${newObjectLiteral}${afterAttr}`;
337
+ }
338
+ }
339
+ }
340
+ else {
341
+ // For non-object literals, we can't remove a specific property
342
+ console.warn(`Cannot remove property '${propertyToRemove}' from non-object literal: ${value}`);
343
+ return match; // Return unchanged
344
+ }
345
+ });
346
+ };
347
+ exports.attributeRemoval = attributeRemoval;
348
+ function tsPropertyRemoval(source, rootSource, j, packageName, typeName, propertyName) {
349
+ if (source.includes(typeName)) {
350
+ if (!isImportedFromPackage(rootSource, j, packageName, typeName)) {
351
+ return;
352
+ }
353
+ const typeVariables = new Set();
354
+ // Find function parameters of target type
355
+ rootSource.find(j.Function).forEach((path) => {
356
+ if (path.node.params) {
357
+ path.node.params.forEach((param) => {
358
+ if (param.type === 'Identifier' &&
359
+ param.typeAnnotation &&
360
+ param.typeAnnotation.typeAnnotation?.type === 'TSTypeReference' &&
361
+ param.typeAnnotation.typeAnnotation.typeName?.type === 'Identifier' &&
362
+ param.typeAnnotation.typeAnnotation.typeName.name === typeName) {
363
+ typeVariables.add(param.name);
364
+ }
365
+ });
366
+ }
367
+ });
368
+ // Find local variables of target type
369
+ rootSource.find(j.VariableDeclarator).forEach((path) => {
370
+ if (path.node.id.type === 'Identifier' &&
371
+ path.node.id.typeAnnotation &&
372
+ path.node.id.typeAnnotation.typeAnnotation?.type === 'TSTypeReference' &&
373
+ path.node.id.typeAnnotation.typeAnnotation.typeName?.type === 'Identifier' &&
374
+ path.node.id.typeAnnotation.typeAnnotation.typeName.name === typeName) {
375
+ typeVariables.add(path.node.id.name);
376
+ }
377
+ });
378
+ // Find class properties of target type
379
+ rootSource.find(j.ClassProperty).forEach((path) => {
380
+ if (path.node.key.type === 'Identifier' &&
381
+ path.node.typeAnnotation &&
382
+ path.node.typeAnnotation.typeAnnotation &&
383
+ path.node.typeAnnotation.typeAnnotation.type === 'TSTypeReference' &&
384
+ path.node.typeAnnotation.typeAnnotation.typeName &&
385
+ path.node.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' &&
386
+ path.node.typeAnnotation.typeAnnotation.typeName.name === typeName) {
387
+ typeVariables.add(path.node.key.name);
388
+ }
389
+ });
390
+ // Handle class properties with object expressions
391
+ rootSource
392
+ .find(j.ClassProperty)
393
+ .filter((path) => {
394
+ // Check if the property has the correct type annotation
395
+ return !!(path.node.typeAnnotation &&
396
+ path.node.typeAnnotation.typeAnnotation &&
397
+ path.node.typeAnnotation.typeAnnotation.type === 'TSTypeReference' &&
398
+ path.node.typeAnnotation.typeAnnotation.typeName &&
399
+ path.node.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' &&
400
+ path.node.typeAnnotation.typeAnnotation.typeName.name === typeName);
401
+ })
402
+ .forEach((path) => {
403
+ if (path.node.value && path.node.value.type === 'ObjectExpression') {
404
+ const properties = path.node.value.properties;
405
+ const propIndex = properties.findIndex((p) => p.type === 'ObjectProperty' &&
406
+ p.key &&
407
+ p.key.type === 'Identifier' &&
408
+ p.key.name === propertyName);
409
+ if (propIndex !== -1) {
410
+ // Just remove the property, leaving an empty object if it was the only one
411
+ properties.splice(propIndex, 1);
412
+ }
413
+ }
414
+ });
415
+ // Handle variable declarations with object literal initializers
416
+ // e.g., const modelFields: ConversationalUIModelFields = { pinnedByField: 'value', id: 'bar' };
417
+ rootSource
418
+ .find(j.VariableDeclarator, {
419
+ id: {
420
+ type: 'Identifier',
421
+ typeAnnotation: {
422
+ typeAnnotation: {
423
+ type: 'TSTypeReference',
424
+ typeName: {
425
+ name: typeName,
426
+ },
427
+ },
428
+ },
429
+ },
430
+ })
431
+ .forEach((path) => {
432
+ if (path.node.init && path.node.init.type === 'ObjectExpression') {
433
+ const properties = path.node.init.properties;
434
+ const propIndex = properties.findIndex((p) => p.type === 'ObjectProperty' &&
435
+ p.key &&
436
+ p.key.type === 'Identifier' &&
437
+ p.key.name === propertyName);
438
+ if (propIndex !== -1) {
439
+ // Just remove the property, leaving an empty object if it was the only one
440
+ properties.splice(propIndex, 1);
441
+ }
442
+ }
443
+ });
444
+ // Handle return statements with object literals
445
+ rootSource.find(j.ReturnStatement).forEach((path) => {
446
+ if (path.node.argument && path.node.argument.type === 'ObjectExpression') {
447
+ const properties = path.node.argument.properties;
448
+ const propIndex = properties.findIndex((p) => p.type === 'ObjectProperty' &&
449
+ p.key &&
450
+ p.key.type === 'Identifier' &&
451
+ p.key.name === propertyName);
452
+ if (propIndex !== -1) {
453
+ properties.splice(propIndex, 1);
454
+ }
455
+ }
456
+ });
457
+ // Handle direct member expression assignments to properties of variables/parameters
458
+ // e.g., fields.pinnedByField = value; or this.chatModelFields.pinnedByField = value;
459
+ rootSource
460
+ .find(j.AssignmentExpression)
461
+ .filter((path) => {
462
+ const { left } = path.node;
463
+ if (left.type === 'MemberExpression' &&
464
+ left.property.type === 'Identifier' &&
465
+ left.property.name === propertyName) {
466
+ // Check if the object is a variable/parameter of our type
467
+ if (left.object.type === 'Identifier' && typeVariables.has(left.object.name)) {
468
+ return true;
469
+ }
470
+ // Check if it's this.property where property is of our type
471
+ if (left.object.type === 'MemberExpression' &&
472
+ left.object.object.type === 'ThisExpression' &&
473
+ left.object.property.type === 'Identifier' &&
474
+ typeVariables.has(left.object.property.name)) {
475
+ return true;
476
+ }
477
+ // Check for type assertions: (expr as Type).property
478
+ if (left.object.type === 'TSAsExpression' &&
479
+ left.object.typeAnnotation.type === 'TSTypeReference' &&
480
+ left.object.typeAnnotation.typeName?.type === 'Identifier' &&
481
+ left.object.typeAnnotation.typeName.name === typeName) {
482
+ return true;
483
+ }
484
+ }
485
+ return false;
486
+ })
487
+ .forEach((path) => {
488
+ // Remove the entire expression statement
489
+ const statement = j(path).closest(j.ExpressionStatement);
490
+ if (statement.length > 0) {
491
+ statement.remove();
492
+ }
493
+ });
494
+ // Handle nested member expressions like chatConfig.chat.modelFields.pinnedByField
495
+ rootSource
496
+ .find(j.AssignmentExpression, {
497
+ left: {
498
+ type: 'MemberExpression',
499
+ object: {
500
+ type: 'MemberExpression',
501
+ },
502
+ property: {
503
+ name: propertyName,
504
+ },
505
+ },
506
+ })
507
+ .forEach((path) => {
508
+ j(path).closest(j.ExpressionStatement).remove();
509
+ });
510
+ return rootSource;
511
+ }
512
+ }
513
+ const tsComponentPropertyRemoval = (source, root, j, packageName, componentType, componentProperty, propertyToRemove) => {
514
+ if (source.includes(componentType)) {
515
+ // Check if componentType is imported from the specified package
516
+ if (!isImportedFromPackage(root, j, packageName, componentType)) {
517
+ return;
518
+ }
519
+ // Find all class properties that are of type componentType
520
+ const properties = new Set();
521
+ root.find(j.ClassProperty, {
522
+ typeAnnotation: {
523
+ typeAnnotation: {
524
+ typeName: {
525
+ name: componentType,
526
+ },
527
+ },
528
+ },
529
+ }).forEach((path) => {
530
+ if (path.node.key.type === 'Identifier') {
531
+ properties.add(path.node.key.name);
532
+ }
533
+ });
534
+ // Find function parameters of type componentType
535
+ const parameters = new Set();
536
+ root.find(j.FunctionDeclaration).forEach((path) => {
537
+ if (path.node.params) {
538
+ path.node.params.forEach((param) => {
539
+ if (param.type === 'Identifier' &&
540
+ param.typeAnnotation &&
541
+ param.typeAnnotation.typeAnnotation?.type === 'TSTypeReference' &&
542
+ param.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' &&
543
+ param.typeAnnotation.typeAnnotation.typeName.name === componentType) {
544
+ parameters.add(param.name);
545
+ }
546
+ });
547
+ }
548
+ });
549
+ // Also check method declarations in classes
550
+ root.find(j.ClassMethod).forEach((path) => {
551
+ if (path.node.params) {
552
+ path.node.params.forEach((param) => {
553
+ if (param.type === 'Identifier' &&
554
+ param.typeAnnotation &&
555
+ param.typeAnnotation.typeAnnotation?.type === 'TSTypeReference' &&
556
+ param.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' &&
557
+ param.typeAnnotation.typeAnnotation.typeName.name === componentType) {
558
+ parameters.add(param.name);
559
+ }
560
+ });
561
+ }
562
+ });
563
+ // Also check arrow functions
564
+ root.find(j.ArrowFunctionExpression).forEach((path) => {
565
+ if (path.node.params) {
566
+ path.node.params.forEach((param) => {
567
+ if (param.type === 'Identifier' &&
568
+ param.typeAnnotation &&
569
+ param.typeAnnotation.typeAnnotation?.type === 'TSTypeReference' &&
570
+ param.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' &&
571
+ param.typeAnnotation.typeAnnotation.typeName.name === componentType) {
572
+ parameters.add(param.name);
573
+ }
574
+ });
575
+ }
576
+ });
577
+ // Find local variable declarations of type componentType
578
+ const localVariables = new Set();
579
+ root.find(j.VariableDeclarator).forEach((path) => {
580
+ if (path.node.id.type === 'Identifier' &&
581
+ path.node.id.typeAnnotation &&
582
+ path.node.id.typeAnnotation.typeAnnotation?.type === 'TSTypeReference' &&
583
+ path.node.id.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' &&
584
+ path.node.id.typeAnnotation.typeAnnotation.typeName.name === componentType) {
585
+ localVariables.add(path.node.id.name);
586
+ }
587
+ });
588
+ // Find array variables of type componentType[]
589
+ // This handles cases like: const arr: ChatComponent[] = [...]; arr[0].property = value;
590
+ const arrayVariables = new Set();
591
+ root.find(j.VariableDeclarator).forEach((path) => {
592
+ if (path.node.id.type === 'Identifier' &&
593
+ path.node.id.typeAnnotation &&
594
+ path.node.id.typeAnnotation.typeAnnotation?.type === 'TSArrayType' &&
595
+ path.node.id.typeAnnotation.typeAnnotation.elementType?.type === 'TSTypeReference' &&
596
+ path.node.id.typeAnnotation.typeAnnotation.elementType.typeName?.type === 'Identifier' &&
597
+ path.node.id.typeAnnotation.typeAnnotation.elementType.typeName.name === componentType) {
598
+ arrayVariables.add(path.node.id.name);
599
+ }
600
+ });
601
+ // Find object properties that have componentType (e.g., {chat: ChatComponent})
602
+ // This handles cases like: const config: {chat: ChatComponent} = {...}; config.chat.property = value;
603
+ const objectProperties = new Map(); // Maps variable name to property name
604
+ root.find(j.VariableDeclarator).forEach((path) => {
605
+ if (path.node.id.type === 'Identifier' &&
606
+ path.node.id.typeAnnotation &&
607
+ path.node.id.typeAnnotation.typeAnnotation?.type === 'TSTypeLiteral') {
608
+ const varName = path.node.id.name;
609
+ const members = path.node.id.typeAnnotation.typeAnnotation.members;
610
+ members.forEach((member) => {
611
+ if (member.type === 'TSPropertySignature' &&
612
+ member.key &&
613
+ member.key.type === 'Identifier' &&
614
+ member.typeAnnotation &&
615
+ member.typeAnnotation.typeAnnotation?.type === 'TSTypeReference' &&
616
+ member.typeAnnotation.typeAnnotation.typeName?.type === 'Identifier' &&
617
+ member.typeAnnotation.typeAnnotation.typeName.name === componentType) {
618
+ if (!objectProperties.has(varName)) {
619
+ objectProperties.set(varName, []);
620
+ }
621
+ objectProperties.get(varName).push(member.key.name);
622
+ }
623
+ });
624
+ }
625
+ });
626
+ // If no propertyToRemove is specified, remove the entire componentProperty
627
+ if (!propertyToRemove) {
628
+ // Handle direct property assignments like: foo.scrollable = value;
629
+ root.find(j.AssignmentExpression)
630
+ .filter((path) => {
631
+ const { left } = path.value;
632
+ // Check if this assigns to component.componentProperty
633
+ if (left &&
634
+ left.type === 'MemberExpression' &&
635
+ left.property &&
636
+ left.property.name === componentProperty) {
637
+ // Check if the base object is our component type
638
+ return isComponentTypeMatch(root, j, left.object, componentType);
639
+ }
640
+ return false;
641
+ })
642
+ .forEach((path) => {
643
+ // Remove the entire statement
644
+ j(path).closest(j.ExpressionStatement).remove();
645
+ });
646
+ return root;
647
+ }
648
+ // CASE 1: Handle direct property assignments like: foo.scrollable.mouseScrollSpeed = 3000;
649
+ root.find(j.AssignmentExpression)
650
+ .filter((path) => {
651
+ const { left } = path.value;
652
+ // Check if this is a member expression assignment
653
+ if (left && left.type === 'MemberExpression') {
654
+ // Check if we're accessing the property to remove
655
+ if (left.property && left.property.name === propertyToRemove) {
656
+ // Check if we're accessing it from component.componentProperty
657
+ const obj = left.object;
658
+ if (obj &&
659
+ obj.type === 'MemberExpression' &&
660
+ obj.property &&
661
+ obj.property.name === componentProperty) {
662
+ // Now check if the base object is our component type (includes all cases: this, parameters, variables, casts)
663
+ return isComponentTypeMatch(root, j, obj.object, componentType);
664
+ }
665
+ }
666
+ }
667
+ return false;
668
+ })
669
+ .forEach((path) => {
670
+ // Remove the entire statement
671
+ j(path).closest(j.ExpressionStatement).remove();
672
+ });
673
+ // CASE 2 & 3: Handle object assignments like: foo.scrollable = { mouseScrollSpeed: 3000, ... };
674
+ root.find(j.AssignmentExpression)
675
+ .filter((path) => {
676
+ const { left, right } = path.value;
677
+ // Check if this assigns to component.componentProperty
678
+ if (left &&
679
+ left.type === 'MemberExpression' &&
680
+ left.property &&
681
+ left.property.name === componentProperty &&
682
+ right &&
683
+ right.type === 'ObjectExpression') {
684
+ // Check if the base object is our component type (includes all cases: this, parameters, variables, casts)
685
+ return isComponentTypeMatch(root, j, left.object, componentType);
686
+ }
687
+ return false;
688
+ })
689
+ .forEach((path) => {
690
+ const properties = path.value.right.properties;
691
+ // Find the property we want to remove
692
+ const propIndex = properties.findIndex((p) => p &&
693
+ p.type === 'ObjectProperty' &&
694
+ p.key &&
695
+ p.key.type === 'Identifier' &&
696
+ p.key.name === propertyToRemove);
697
+ if (propIndex !== -1) {
698
+ // Case 2: If it's the only property, remove the entire statement
699
+ if (properties.length === 1) {
700
+ j(path).closest(j.ExpressionStatement).remove();
701
+ }
702
+ // Case 3: If there are other properties, just remove this one property
703
+ else {
704
+ properties.splice(propIndex, 1);
705
+ }
706
+ }
707
+ });
708
+ return root;
709
+ }
710
+ };
711
+ exports.tsComponentPropertyRemoval = tsComponentPropertyRemoval;
712
+ const tsPropertyTransformer = (source, root, j, packageName, componentType, propertyName, newPropertyName, valueProperty) => {
713
+ if (source.includes(componentType)) {
714
+ // Check if componentType is imported from the specified package
715
+ if (!isImportedFromPackage(root, j, packageName, componentType)) {
716
+ return;
717
+ }
718
+ // Find all class properties that are of type componentType
719
+ const properties = new Set();
720
+ // Find properties with type annotations
721
+ root.find(j.ClassProperty, {
722
+ typeAnnotation: {
723
+ typeAnnotation: {
724
+ typeName: {
725
+ name: componentType,
726
+ },
727
+ },
728
+ },
729
+ }).forEach((path) => {
730
+ if (path.node.key.type === 'Identifier') {
731
+ properties.add(path.node.key.name);
732
+ }
733
+ });
734
+ // Find function parameters of type componentType
735
+ const parameters = new Set();
736
+ root.find(j.FunctionDeclaration).forEach((path) => {
737
+ if (path.node.params) {
738
+ path.node.params.forEach((param) => {
739
+ if (param.type === 'Identifier' &&
740
+ param.typeAnnotation &&
741
+ param.typeAnnotation.typeAnnotation?.type === 'TSTypeReference' &&
742
+ param.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' &&
743
+ param.typeAnnotation.typeAnnotation.typeName.name === componentType) {
744
+ parameters.add(param.name);
745
+ }
746
+ });
747
+ }
748
+ });
749
+ // Also check method declarations in classes
750
+ root.find(j.ClassMethod).forEach((path) => {
751
+ if (path.node.params) {
752
+ path.node.params.forEach((param) => {
753
+ if (param.type === 'Identifier' &&
754
+ param.typeAnnotation &&
755
+ param.typeAnnotation.typeAnnotation?.type === 'TSTypeReference' &&
756
+ param.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' &&
757
+ param.typeAnnotation.typeAnnotation.typeName.name === componentType) {
758
+ parameters.add(param.name);
759
+ }
760
+ });
761
+ }
762
+ });
763
+ // Also check arrow functions
764
+ root.find(j.ArrowFunctionExpression).forEach((path) => {
765
+ if (path.node.params) {
766
+ path.node.params.forEach((param) => {
767
+ if (param.type === 'Identifier' &&
768
+ param.typeAnnotation &&
769
+ param.typeAnnotation.typeAnnotation?.type === 'TSTypeReference' &&
770
+ param.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' &&
771
+ param.typeAnnotation.typeAnnotation.typeName.name === componentType) {
772
+ parameters.add(param.name);
773
+ }
774
+ });
775
+ }
776
+ });
777
+ // Find local variable declarations of type componentType
778
+ const localVariables = new Set();
779
+ root.find(j.VariableDeclarator).forEach((path) => {
780
+ if (path.node.id.type === 'Identifier' &&
781
+ path.node.id.typeAnnotation &&
782
+ path.node.id.typeAnnotation.typeAnnotation?.type === 'TSTypeReference' &&
783
+ path.node.id.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' &&
784
+ path.node.id.typeAnnotation.typeAnnotation.typeName.name === componentType) {
785
+ localVariables.add(path.node.id.name);
786
+ }
787
+ });
788
+ // Find array variables of type componentType[]
789
+ // This handles cases like: const arr: ChatComponent[] = [...]; arr[0].property = value;
790
+ const arrayVariables = new Set();
791
+ root.find(j.VariableDeclarator).forEach((path) => {
792
+ if (path.node.id.type === 'Identifier' &&
793
+ path.node.id.typeAnnotation &&
794
+ path.node.id.typeAnnotation.typeAnnotation?.type === 'TSArrayType' &&
795
+ path.node.id.typeAnnotation.typeAnnotation.elementType?.type === 'TSTypeReference' &&
796
+ path.node.id.typeAnnotation.typeAnnotation.elementType.typeName?.type === 'Identifier' &&
797
+ path.node.id.typeAnnotation.typeAnnotation.elementType.typeName.name === componentType) {
798
+ arrayVariables.add(path.node.id.name);
799
+ }
800
+ });
801
+ // Find object properties that have componentType (e.g., {chat: ChatComponent})
802
+ // This handles cases like: const config: {chat: ChatComponent} = {...}; config.chat.property = value;
803
+ const objectProperties = new Map(); // Maps variable name to property names
804
+ root.find(j.VariableDeclarator).forEach((path) => {
805
+ if (path.node.id.type === 'Identifier' &&
806
+ path.node.id.typeAnnotation &&
807
+ path.node.id.typeAnnotation.typeAnnotation?.type === 'TSTypeLiteral') {
808
+ const varName = path.node.id.name;
809
+ const members = path.node.id.typeAnnotation.typeAnnotation.members;
810
+ members.forEach((member) => {
811
+ if (member.type === 'TSPropertySignature' &&
812
+ member.key &&
813
+ member.key.type === 'Identifier' &&
814
+ member.typeAnnotation &&
815
+ member.typeAnnotation.typeAnnotation?.type === 'TSTypeReference' &&
816
+ member.typeAnnotation.typeAnnotation.typeName?.type === 'Identifier' &&
817
+ member.typeAnnotation.typeAnnotation.typeName.name === componentType) {
818
+ if (!objectProperties.has(varName)) {
819
+ objectProperties.set(varName, []);
820
+ }
821
+ objectProperties.get(varName).push(member.key.name);
822
+ }
823
+ });
824
+ }
825
+ });
826
+ // Helper function to check if a node is a componentType instance
827
+ const isComponentTypeInstance = (node) => {
828
+ // Direct identifier (parameter or local variable)
829
+ if (node.type === 'Identifier') {
830
+ return parameters.has(node.name) || localVariables.has(node.name);
831
+ }
832
+ // this.property where property is of componentType
833
+ if (node.type === 'MemberExpression' && node.property.type === 'Identifier') {
834
+ if (node.object.type === 'ThisExpression' && properties.has(node.property.name)) {
835
+ return true;
836
+ }
837
+ // Handle nested object properties: objectVar.propertyName where propertyName is of componentType
838
+ if (node.object.type === 'Identifier') {
839
+ const objName = node.object.name;
840
+ const propName = node.property.name;
841
+ if (objectProperties.has(objName)) {
842
+ const props = objectProperties.get(objName);
843
+ return props.includes(propName);
844
+ }
845
+ }
846
+ }
847
+ // Array element access: arrayVar[index]
848
+ if (node.type === 'MemberExpression' &&
849
+ node.object.type === 'Identifier' &&
850
+ arrayVariables.has(node.object.name)) {
851
+ return true;
852
+ }
853
+ // TypeScript type assertions like (this.componentProperty as ComponentType)
854
+ if (node.type === 'TSAsExpression') {
855
+ if (node.typeAnnotation &&
856
+ node.typeAnnotation.type === 'TSTypeReference' &&
857
+ node.typeAnnotation.typeName &&
858
+ node.typeAnnotation.typeName.type === 'Identifier' &&
859
+ node.typeAnnotation.typeName.name === componentType) {
860
+ return true;
861
+ }
862
+ }
863
+ return false;
864
+ };
865
+ // Find all member expressions where propertyName is accessed on any componentType instance
866
+ root.find(j.MemberExpression, {
867
+ property: {
868
+ type: 'Identifier',
869
+ name: propertyName,
870
+ },
871
+ })
872
+ .filter((path) => {
873
+ return isComponentTypeInstance(path.node.object);
874
+ })
875
+ .forEach((path) => {
876
+ // Replace old property name with new property name
877
+ if (path.node.property.type === 'Identifier') {
878
+ path.node.property.name = newPropertyName;
879
+ }
880
+ // If valueProperty is specified and this is part of an assignment,
881
+ // we need to also modify the right-hand side of the assignment
882
+ if (valueProperty) {
883
+ const assignmentExpression = path.parent;
884
+ if (assignmentExpression &&
885
+ assignmentExpression.value &&
886
+ assignmentExpression.value.type === 'AssignmentExpression' &&
887
+ assignmentExpression.value.left === path.node) {
888
+ const rightSide = assignmentExpression.value.right;
889
+ // Case 1: Right side is a member expression (e.g., this.user, obj.user) -> transform to this.user.id, obj.user.id
890
+ // Case 2: Right side is an identifier (e.g., user, foo) -> transform to user.id, foo.id
891
+ if (rightSide.type === 'MemberExpression' || rightSide.type === 'Identifier') {
892
+ const newRightSide = j.memberExpression(rightSide, j.identifier(valueProperty));
893
+ assignmentExpression.value.right = newRightSide;
894
+ }
895
+ // Case 3: Right side is object literal -> extract the valueProperty value
896
+ else if (rightSide.type === 'ObjectExpression') {
897
+ // Find the property that matches valueProperty
898
+ const targetProperty = rightSide.properties.find((prop) => prop.type === 'ObjectProperty' &&
899
+ prop.key &&
900
+ prop.key.type === 'Identifier' &&
901
+ prop.key.name === valueProperty);
902
+ if (targetProperty) {
903
+ // Replace the entire object literal with just the value of the target property
904
+ assignmentExpression.value.right = targetProperty.value;
905
+ }
906
+ }
907
+ }
908
+ }
909
+ });
910
+ // Transform object literal properties in variable declarations and assignments
911
+ // This handles cases like: const obj: ComponentType = { oldProperty: 'value' }
912
+ root.find(j.VariableDeclarator)
913
+ .filter((path) => {
914
+ // Check if the variable has the componentType type annotation
915
+ return Boolean(path.node.id.type === 'Identifier' &&
916
+ path.node.id.typeAnnotation &&
917
+ path.node.id.typeAnnotation.typeAnnotation?.type === 'TSTypeReference' &&
918
+ path.node.id.typeAnnotation.typeAnnotation.typeName?.type === 'Identifier' &&
919
+ path.node.id.typeAnnotation.typeAnnotation.typeName.name === componentType);
920
+ })
921
+ .forEach((path) => {
922
+ // Check if the initializer is an object expression
923
+ if (path.node.init && path.node.init.type === 'ObjectExpression') {
924
+ path.node.init.properties.forEach((prop) => {
925
+ // Rename the property if it matches
926
+ if (prop.type === 'ObjectProperty' &&
927
+ prop.key &&
928
+ prop.key.type === 'Identifier' &&
929
+ prop.key.name === propertyName) {
930
+ prop.key.name = newPropertyName;
931
+ }
932
+ });
933
+ }
934
+ });
935
+ // Transform object literal properties in class properties
936
+ // This handles cases like: customMessages: ComponentType = { oldProperty: 'value' }
937
+ root.find(j.ClassProperty)
938
+ .filter((path) => {
939
+ // Check if the property has the componentType type annotation
940
+ return Boolean(path.node.typeAnnotation &&
941
+ path.node.typeAnnotation.typeAnnotation?.type === 'TSTypeReference' &&
942
+ path.node.typeAnnotation.typeAnnotation.typeName?.type === 'Identifier' &&
943
+ path.node.typeAnnotation.typeAnnotation.typeName.name === componentType);
944
+ })
945
+ .forEach((path) => {
946
+ // Check if the value is an object expression
947
+ if (path.node.value && path.node.value.type === 'ObjectExpression') {
948
+ path.node.value.properties.forEach((prop) => {
949
+ // Rename the property if it matches
950
+ if (prop.type === 'ObjectProperty' &&
951
+ prop.key &&
952
+ prop.key.type === 'Identifier' &&
953
+ prop.key.name === propertyName) {
954
+ prop.key.name = newPropertyName;
955
+ }
956
+ });
957
+ }
958
+ });
959
+ // Transform object literal properties in assignment expressions
960
+ // This handles cases like: this.obj = { oldProperty: 'value' }
961
+ root.find(j.AssignmentExpression)
962
+ .filter((path) => {
963
+ // Check if we're assigning an object literal
964
+ return path.node.right.type === 'ObjectExpression';
965
+ })
966
+ .forEach((path) => {
967
+ // We need to determine if the left side is of componentType
968
+ // This is more complex as we need to check the type of the assignment target
969
+ const leftSide = path.node.left;
970
+ let isTargetComponentType = false;
971
+ // Check if it's a property that we know is of componentType
972
+ if (leftSide.type === 'MemberExpression' && leftSide.property.type === 'Identifier') {
973
+ if (leftSide.object.type === 'ThisExpression' && properties.has(leftSide.property.name)) {
974
+ isTargetComponentType = true;
975
+ }
976
+ else if (leftSide.object.type === 'Identifier' && localVariables.has(leftSide.object.name)) {
977
+ isTargetComponentType = true;
978
+ }
979
+ }
980
+ else if (leftSide.type === 'Identifier') {
981
+ isTargetComponentType = localVariables.has(leftSide.name) || parameters.has(leftSide.name);
982
+ }
983
+ if (isTargetComponentType && path.node.right.type === 'ObjectExpression') {
984
+ path.node.right.properties.forEach((prop) => {
985
+ // Rename the property if it matches
986
+ if (prop.type === 'ObjectProperty' &&
987
+ prop.key &&
988
+ prop.key.type === 'Identifier' &&
989
+ prop.key.name === propertyName) {
990
+ prop.key.name = newPropertyName;
991
+ }
992
+ });
993
+ }
994
+ });
995
+ }
996
+ };
997
+ exports.tsPropertyTransformer = tsPropertyTransformer;
998
+ const tsPropertyValueTransformer = (source, root, j, packageName, typeName, oldValue, newValue) => {
999
+ if (source.includes(typeName)) {
1000
+ // Check if typeName is imported from the specified package
1001
+ if (!isImportedFromPackage(root, j, packageName, typeName)) {
1002
+ return;
1003
+ }
1004
+ root.find(j.ClassProperty)
1005
+ .filter((path) => {
1006
+ if (path.node.typeAnnotation?.typeAnnotation &&
1007
+ path.node.typeAnnotation.typeAnnotation.type === 'TSTypeReference' &&
1008
+ path.node.typeAnnotation.typeAnnotation.typeName &&
1009
+ path.node.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' &&
1010
+ path.node.typeAnnotation.typeAnnotation.typeName.name === typeName) {
1011
+ return true;
1012
+ }
1013
+ return false;
1014
+ })
1015
+ .forEach((path) => {
1016
+ if (path.node.value && path.node.value.type === 'StringLiteral' && path.node.value.value === oldValue) {
1017
+ path.node.value.value = newValue;
1018
+ }
1019
+ });
1020
+ root.find(j.VariableDeclarator)
1021
+ .filter((path) => {
1022
+ if (path.node.id.type === 'Identifier' &&
1023
+ path.node.id.typeAnnotation?.typeAnnotation &&
1024
+ path.node.id.typeAnnotation.typeAnnotation.type === 'TSTypeReference' &&
1025
+ path.node.id.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' &&
1026
+ path.node.id.typeAnnotation.typeAnnotation.typeName.name === typeName) {
1027
+ return true;
1028
+ }
1029
+ return false;
1030
+ })
1031
+ .forEach((path) => {
1032
+ if (path.node.id.type === 'Identifier') {
1033
+ if (path.node.init && path.node.init.type === 'StringLiteral' && path.node.init.value === oldValue) {
1034
+ path.node.init.value = newValue;
1035
+ }
1036
+ }
1037
+ });
1038
+ root.find(j.AssignmentExpression)
1039
+ .filter((path) => {
1040
+ return path.node.right.type === 'StringLiteral' && path.node.right.value === oldValue;
1041
+ })
1042
+ .forEach((path) => {
1043
+ path.node.right.value = newValue;
1044
+ });
1045
+ root.find(j.JSXAttribute, {
1046
+ value: {
1047
+ type: 'StringLiteral',
1048
+ value: oldValue,
1049
+ },
1050
+ }).forEach((path) => {
1051
+ if (path.node.value?.type === 'StringLiteral') {
1052
+ path.node.value.value = newValue;
1053
+ }
1054
+ });
1055
+ }
1056
+ };
1057
+ exports.tsPropertyValueTransformer = tsPropertyValueTransformer;
1058
+ const tsInterfaceTransformer = (fileInfo, rootSource, j, packageName, interfaceName, newName) => {
1059
+ const source = fileInfo.source;
1060
+ if (source.includes(interfaceName)) {
1061
+ // Check if interface is imported from the specified package and rename it
1062
+ let isImported = false;
1063
+ rootSource.find(j.ImportDeclaration).forEach((path) => {
1064
+ if (path.node.source &&
1065
+ path.node.source.value === packageName &&
1066
+ path.node.specifiers) {
1067
+ path.node.specifiers.forEach((specifier) => {
1068
+ if (specifier.type === 'ImportSpecifier' &&
1069
+ specifier.imported.type === 'Identifier' &&
1070
+ specifier.imported.name === interfaceName) {
1071
+ isImported = true;
1072
+ specifier.imported.name = newName;
1073
+ }
1074
+ });
1075
+ }
1076
+ });
1077
+ if (!isImported) {
1078
+ return;
1079
+ }
1080
+ rootSource.find(j.ClassProperty).forEach((path) => {
1081
+ if (path.node.typeAnnotation &&
1082
+ path.node.typeAnnotation.typeAnnotation &&
1083
+ path.node.typeAnnotation.typeAnnotation.type === 'TSTypeReference' &&
1084
+ path.node.typeAnnotation.typeAnnotation.typeName &&
1085
+ path.node.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' &&
1086
+ path.node.typeAnnotation.typeAnnotation.typeName.name === interfaceName) {
1087
+ path.node.typeAnnotation.typeAnnotation.typeName.name = newName;
1088
+ }
1089
+ });
1090
+ rootSource.find(j.VariableDeclarator).forEach((path) => {
1091
+ if (path.node.id.type === 'Identifier' &&
1092
+ path.node.id.typeAnnotation &&
1093
+ path.node.id.typeAnnotation.typeAnnotation &&
1094
+ path.node.id.typeAnnotation.typeAnnotation.type === 'TSTypeReference' &&
1095
+ path.node.id.typeAnnotation.typeAnnotation.typeName &&
1096
+ path.node.id.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' &&
1097
+ path.node.id.typeAnnotation.typeAnnotation.typeName.name === interfaceName) {
1098
+ path.node.id.typeAnnotation.typeAnnotation.typeName.name = newName;
1099
+ }
1100
+ });
1101
+ rootSource.find(j.FunctionDeclaration).forEach((path) => {
1102
+ if (path.node.params) {
1103
+ path.node.params.forEach((param) => {
1104
+ if (param.type === 'Identifier' &&
1105
+ param.typeAnnotation &&
1106
+ param.typeAnnotation.typeAnnotation &&
1107
+ param.typeAnnotation.typeAnnotation.type === 'TSTypeReference' &&
1108
+ param.typeAnnotation.typeAnnotation.typeName &&
1109
+ param.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' &&
1110
+ param.typeAnnotation.typeAnnotation.typeName.name === interfaceName) {
1111
+ param.typeAnnotation.typeAnnotation.typeName.name = newName;
1112
+ }
1113
+ });
1114
+ }
1115
+ });
1116
+ rootSource.find(j.ArrowFunctionExpression).forEach((path) => {
1117
+ if (path.node.params) {
1118
+ path.node.params.forEach((param) => {
1119
+ if (param.type === 'Identifier' &&
1120
+ param.typeAnnotation &&
1121
+ param.typeAnnotation.typeAnnotation &&
1122
+ param.typeAnnotation.typeAnnotation.type === 'TSTypeReference' &&
1123
+ param.typeAnnotation.typeAnnotation.typeName &&
1124
+ param.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' &&
1125
+ param.typeAnnotation.typeAnnotation.typeName.name === interfaceName) {
1126
+ param.typeAnnotation.typeAnnotation.typeName.name = newName;
1127
+ }
1128
+ });
1129
+ }
1130
+ });
1131
+ rootSource.find(j.ClassMethod).forEach((path) => {
1132
+ if (path.node.params) {
1133
+ path.node.params.forEach((param) => {
1134
+ if (param.type === 'Identifier' &&
1135
+ param.typeAnnotation &&
1136
+ param.typeAnnotation.typeAnnotation &&
1137
+ param.typeAnnotation.typeAnnotation.type === 'TSTypeReference' &&
1138
+ param.typeAnnotation.typeAnnotation.typeName &&
1139
+ param.typeAnnotation.typeAnnotation.typeName.type === 'Identifier' &&
1140
+ param.typeAnnotation.typeAnnotation.typeName.name === interfaceName) {
1141
+ param.typeAnnotation.typeAnnotation.typeName.name = newName;
1142
+ }
1143
+ });
1144
+ }
1145
+ });
1146
+ rootSource.find(j.Function).forEach((path) => {
1147
+ if (path.node.returnType &&
1148
+ path.node.returnType.typeAnnotation &&
1149
+ path.node.returnType.typeAnnotation.type === 'TSTypeReference' &&
1150
+ path.node.returnType.typeAnnotation.typeName &&
1151
+ path.node.returnType.typeAnnotation.typeName.type === 'Identifier' &&
1152
+ path.node.returnType.typeAnnotation.typeName.name === interfaceName) {
1153
+ path.node.returnType.typeAnnotation.typeName.name = newName;
1154
+ }
1155
+ });
1156
+ rootSource.find(j.TSAsExpression).forEach((path) => {
1157
+ if (path.node.typeAnnotation &&
1158
+ path.node.typeAnnotation.type === 'TSTypeReference' &&
1159
+ path.node.typeAnnotation.typeName &&
1160
+ path.node.typeAnnotation.typeName.type === 'Identifier' &&
1161
+ path.node.typeAnnotation.typeName.name === interfaceName) {
1162
+ path.node.typeAnnotation.typeName.name = newName;
1163
+ }
1164
+ });
1165
+ // Handle constructor calls with 'new' keyword
1166
+ rootSource.find(j.NewExpression).forEach((path) => {
1167
+ if (path.node.callee &&
1168
+ path.node.callee.type === 'Identifier' &&
1169
+ path.node.callee.name === interfaceName) {
1170
+ path.node.callee.name = newName;
1171
+ }
1172
+ });
1173
+ // Helper function to recursively transform type references
1174
+ const transformTypeReference = (typeNode) => {
1175
+ if (!typeNode)
1176
+ return;
1177
+ // Handle TSTypeReference (e.g., FileSelectSettings)
1178
+ if (typeNode.type === 'TSTypeReference' &&
1179
+ typeNode.typeName &&
1180
+ typeNode.typeName.type === 'Identifier' &&
1181
+ typeNode.typeName.name === interfaceName) {
1182
+ typeNode.typeName.name = newName;
1183
+ }
1184
+ // Handle TSArrayType (e.g., FileSelectSettings[])
1185
+ if (typeNode.type === 'TSArrayType' && typeNode.elementType) {
1186
+ transformTypeReference(typeNode.elementType);
1187
+ }
1188
+ // Handle generic Array<T> (e.g., Array<FileSelectSettings>)
1189
+ if (typeNode.type === 'TSTypeReference' &&
1190
+ typeNode.typeParameters &&
1191
+ typeNode.typeParameters.params) {
1192
+ typeNode.typeParameters.params.forEach((param) => {
1193
+ transformTypeReference(param);
1194
+ });
1195
+ }
1196
+ // Handle TSTypeLiteral (e.g., { primary: FileSelectSettings })
1197
+ if (typeNode.type === 'TSTypeLiteral' && typeNode.members) {
1198
+ typeNode.members.forEach((member) => {
1199
+ if (member.typeAnnotation && member.typeAnnotation.typeAnnotation) {
1200
+ transformTypeReference(member.typeAnnotation.typeAnnotation);
1201
+ }
1202
+ });
1203
+ }
1204
+ // Handle TSFunctionType (e.g., (settings: FileSelectSettings) => void)
1205
+ if (typeNode.type === 'TSFunctionType') {
1206
+ if (typeNode.parameters) {
1207
+ typeNode.parameters.forEach((param) => {
1208
+ if (param.typeAnnotation && param.typeAnnotation.typeAnnotation) {
1209
+ transformTypeReference(param.typeAnnotation.typeAnnotation);
1210
+ }
1211
+ });
1212
+ }
1213
+ if (typeNode.typeAnnotation && typeNode.typeAnnotation.typeAnnotation) {
1214
+ transformTypeReference(typeNode.typeAnnotation.typeAnnotation);
1215
+ }
1216
+ }
1217
+ };
1218
+ // Apply recursive transformation to all type annotations
1219
+ rootSource.find(j.VariableDeclarator).forEach((path) => {
1220
+ if (path.node.id.type === 'Identifier' &&
1221
+ path.node.id.typeAnnotation &&
1222
+ path.node.id.typeAnnotation.typeAnnotation) {
1223
+ transformTypeReference(path.node.id.typeAnnotation.typeAnnotation);
1224
+ }
1225
+ });
1226
+ rootSource.find(j.ClassProperty).forEach((path) => {
1227
+ if (path.node.typeAnnotation && path.node.typeAnnotation.typeAnnotation) {
1228
+ transformTypeReference(path.node.typeAnnotation.typeAnnotation);
1229
+ }
1230
+ });
1231
+ rootSource.find(j.ClassMethod).forEach((path) => {
1232
+ if (path.node.returnType && path.node.returnType.typeAnnotation) {
1233
+ transformTypeReference(path.node.returnType.typeAnnotation);
1234
+ }
1235
+ // Transform parameter types recursively
1236
+ if (path.node.params) {
1237
+ path.node.params.forEach((param) => {
1238
+ if (param.typeAnnotation && param.typeAnnotation.typeAnnotation) {
1239
+ transformTypeReference(param.typeAnnotation.typeAnnotation);
1240
+ }
1241
+ });
1242
+ }
1243
+ });
1244
+ rootSource.find(j.FunctionDeclaration).forEach((path) => {
1245
+ if (path.node.returnType && path.node.returnType.typeAnnotation) {
1246
+ transformTypeReference(path.node.returnType.typeAnnotation);
1247
+ }
1248
+ // Transform parameter types recursively
1249
+ if (path.node.params) {
1250
+ path.node.params.forEach((param) => {
1251
+ if (param.typeAnnotation && param.typeAnnotation.typeAnnotation) {
1252
+ transformTypeReference(param.typeAnnotation.typeAnnotation);
1253
+ }
1254
+ });
1255
+ }
1256
+ });
1257
+ rootSource.find(j.ArrowFunctionExpression).forEach((path) => {
1258
+ if (path.node.returnType && path.node.returnType.typeAnnotation) {
1259
+ transformTypeReference(path.node.returnType.typeAnnotation);
1260
+ }
1261
+ // Transform parameter types recursively
1262
+ if (path.node.params) {
1263
+ path.node.params.forEach((param) => {
1264
+ if (param.typeAnnotation && param.typeAnnotation.typeAnnotation) {
1265
+ transformTypeReference(param.typeAnnotation.typeAnnotation);
1266
+ }
1267
+ });
1268
+ }
1269
+ });
1270
+ }
1271
+ };
1272
+ exports.tsInterfaceTransformer = tsInterfaceTransformer;
1273
+ // Helper function to check if a node is a component of the specified type
1274
+ function isComponentTypeMatch(root, j, node, componentType) {
1275
+ if (!node)
1276
+ return false;
1277
+ if (node.type === 'ThisExpression') {
1278
+ return true;
1279
+ }
1280
+ if (node.type === 'Identifier') {
1281
+ const paramName = node.name;
1282
+ // Check function parameters
1283
+ let isParameter = false;
1284
+ root.find(j.Function).forEach((path) => {
1285
+ if (path.node.params && path.node.params.some((param) => param.type === 'Identifier' &&
1286
+ param.name === paramName &&
1287
+ param.typeAnnotation?.typeAnnotation?.typeName?.name === componentType)) {
1288
+ isParameter = true;
1289
+ }
1290
+ });
1291
+ if (isParameter)
1292
+ return true;
1293
+ // Check local variable declarations
1294
+ let isLocalVariable = false;
1295
+ root.find(j.VariableDeclarator).forEach((path) => {
1296
+ if (path.node.id.type === 'Identifier' &&
1297
+ path.node.id.name === paramName &&
1298
+ path.node.id.typeAnnotation?.typeAnnotation?.type === 'TSTypeReference' &&
1299
+ path.node.id.typeAnnotation.typeAnnotation.typeName?.type === 'Identifier' &&
1300
+ path.node.id.typeAnnotation.typeAnnotation.typeName.name === componentType) {
1301
+ isLocalVariable = true;
1302
+ }
1303
+ });
1304
+ return isLocalVariable;
1305
+ }
1306
+ if (node.type === 'MemberExpression') {
1307
+ if (node.object.type === 'ThisExpression' && node.property.type === 'Identifier') {
1308
+ const propName = node.property.name;
1309
+ return (root
1310
+ .find(j.ClassProperty, {
1311
+ key: { name: propName },
1312
+ typeAnnotation: {
1313
+ typeAnnotation: {
1314
+ typeName: { name: componentType },
1315
+ },
1316
+ },
1317
+ })
1318
+ .size() > 0);
1319
+ }
1320
+ // Handle nested member expressions like chatConfig.chat where chat is ChatComponent
1321
+ if (node.object.type === 'Identifier' && node.property.type === 'Identifier') {
1322
+ const varName = node.object.name;
1323
+ const propName = node.property.name;
1324
+ // Check if this is an object with a property of componentType
1325
+ let hasMatchingProperty = false;
1326
+ root.find(j.VariableDeclarator).forEach((path) => {
1327
+ if (path.node.id.type === 'Identifier' &&
1328
+ path.node.id.name === varName &&
1329
+ path.node.id.typeAnnotation &&
1330
+ path.node.id.typeAnnotation.typeAnnotation?.type === 'TSTypeLiteral') {
1331
+ const members = path.node.id.typeAnnotation.typeAnnotation.members;
1332
+ const found = members.some((member) => {
1333
+ return (member.type === 'TSPropertySignature' &&
1334
+ member.key?.type === 'Identifier' &&
1335
+ member.key.name === propName &&
1336
+ member.typeAnnotation?.typeAnnotation?.type === 'TSTypeReference' &&
1337
+ member.typeAnnotation.typeAnnotation.typeName?.type === 'Identifier' &&
1338
+ member.typeAnnotation.typeAnnotation.typeName.name === componentType);
1339
+ });
1340
+ if (found) {
1341
+ hasMatchingProperty = true;
1342
+ }
1343
+ }
1344
+ });
1345
+ return hasMatchingProperty;
1346
+ }
1347
+ }
1348
+ // Handle array element access like chatComponents[0] where chatComponents is ChatComponent[]
1349
+ // When we have chatComponents[0].property, node is chatComponents[0] which is a MemberExpression with computed: true
1350
+ if (node.type === 'MemberExpression' && node.computed === true && node.object.type === 'Identifier') {
1351
+ const arrayName = node.object.name;
1352
+ // Check if this array is of type ChatComponent[]
1353
+ let isComponentArray = false;
1354
+ root.find(j.VariableDeclarator).forEach((path) => {
1355
+ if (path.node.id.type === 'Identifier' &&
1356
+ path.node.id.name === arrayName &&
1357
+ path.node.id.typeAnnotation?.typeAnnotation?.type === 'TSArrayType' &&
1358
+ path.node.id.typeAnnotation.typeAnnotation.elementType?.type === 'TSTypeReference' &&
1359
+ path.node.id.typeAnnotation.typeAnnotation.elementType.typeName?.type === 'Identifier' &&
1360
+ path.node.id.typeAnnotation.typeAnnotation.elementType.typeName.name === componentType) {
1361
+ isComponentArray = true;
1362
+ }
1363
+ });
1364
+ return isComponentArray;
1365
+ }
1366
+ // Handle TypeScript type assertions like (this.componentProperty as ComponentType)
1367
+ if (node.type === 'TSAsExpression') {
1368
+ // Check if the type assertion is casting to our componentType
1369
+ if (node.typeAnnotation &&
1370
+ node.typeAnnotation.type === 'TSTypeReference' &&
1371
+ node.typeAnnotation.typeName &&
1372
+ node.typeAnnotation.typeName.type === 'Identifier' &&
1373
+ node.typeAnnotation.typeName.name === componentType) {
1374
+ return true;
1375
+ }
1376
+ }
1377
+ return false;
1378
+ }
1379
+ // Matches CSS class names in CSS selectors (.foo) and as whitespace/quote-delimited
1380
+ // tokens within attribute values, covering both single-class ("foo") and multi-class ("foo bar") cases.
1381
+ function makePattern(classes) {
1382
+ return new RegExp(classes.map(c => String.raw `\.${c}\b|(?<=["'\s])${c}(?=["'\s])`).join('|'));
1383
+ }
1384
+ function writeInstructionMarker(instruction, codemodFilename, affectedFile) {
1385
+ // Write to node_modules/.kendo/migration/<basename(codemodFilename)>
1386
+ // kendo-cli reads the marker by looking up basename(codemod.file) in that directory
1387
+ const markerDir = path.join(process.cwd(), 'node_modules', '.kendo', 'migration');
1388
+ const markerPath = path.join(markerDir, path.basename(codemodFilename));
1389
+ try {
1390
+ fs.mkdirSync(markerDir, { recursive: true });
1391
+ const existing = fs.existsSync(markerPath) ? fs.readFileSync(markerPath, 'utf8') : '';
1392
+ let content = existing;
1393
+ if (!existing.includes(instruction)) {
1394
+ content += instruction + '\n';
1395
+ }
1396
+ const fileLine = ` - ${path.relative(process.cwd(), affectedFile)}`;
1397
+ if (!content.includes(fileLine)) {
1398
+ content += fileLine + '\n';
1399
+ }
1400
+ fs.writeFileSync(markerPath, content);
1401
+ }
1402
+ catch { /* suppress error */ }
1403
+ }
1404
+ function isApiChangeTarget(filePath) {
1405
+ const ext = path.extname(filePath);
1406
+ return ext === '.ts' || ext === '.html';
1407
+ }
1408
+ function isRenderingChangeTarget(filePath) {
1409
+ const ext = path.extname(filePath);
1410
+ return ext === '.ts' || ext === '.html' || ext === '.css' || ext === '.scss' || ext === '.sass' || ext === '.less';
1411
+ }
1412
+ /**
1413
+ * Executes a codemod transformation and compares the result with expected output
1414
+ *
1415
+ * @param codemod - The codemod function to execute
1416
+ * @param testDir - Directory containing the test files (__dirname from test file)
1417
+ * @param exampleFileName - Name of the input file (default: 'example.ts')
1418
+ * @param expectedFileName - Name of the expected output file (default: 'expected.ts')
1419
+ * @returns Object containing the transformation result and expected content
1420
+ */
1421
+ function executeCodemodTest(codemod, testDir, exampleFileName = 'example.ts', expectedFileName = 'expected.ts') {
1422
+ const exampleFile = path.join(testDir, exampleFileName);
1423
+ const expectedFile = path.join(testDir, expectedFileName);
1424
+ const sourceCode = fs.readFileSync(exampleFile, 'utf-8');
1425
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codemod-test-'));
1426
+ const tmpFile = path.join(tmpDir, path.basename(exampleFile));
1427
+ fs.writeFileSync(tmpFile, sourceCode, 'utf-8');
1428
+ try {
1429
+ const fileInfo = {
1430
+ path: tmpFile,
1431
+ source: sourceCode
1432
+ };
1433
+ const jscodeshift = require('jscodeshift').withParser('tsx');
1434
+ const api = {
1435
+ jscodeshift,
1436
+ j: jscodeshift
1437
+ };
1438
+ const result = codemod(fileInfo, api);
1439
+ const expectedContent = fs.readFileSync(expectedFile, 'utf-8').trim();
1440
+ return {
1441
+ result: result?.trim(),
1442
+ expected: expectedContent,
1443
+ sourceCode,
1444
+ transformedSuccessfully: result !== undefined
1445
+ };
1446
+ }
1447
+ finally {
1448
+ try {
1449
+ fs.rmSync(tmpDir, { recursive: true });
1450
+ }
1451
+ catch { }
1452
+ }
1453
+ }