@eclipse-scout/migrate 23.1.1

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,386 @@
1
+ /*
2
+ * Copyright (c) 2010, 2023 BSI Business Systems Integration AG
3
+ *
4
+ * This program and the accompanying materials are made
5
+ * available under the terms of the Eclipse Public License 2.0
6
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
7
+ *
8
+ * SPDX-License-Identifier: EPL-2.0
9
+ */
10
+
11
+ // noinspection SpellCheckingInspection
12
+ import fs from 'fs';
13
+ import jscodeshift from 'jscodeshift';
14
+ import {crlfToLf, defaultRecastOptions, findParentPath, insertMissingImportsForTypes, mapType, removeEmptyLinesBetweenImports} from './common.js';
15
+
16
+ const j = jscodeshift.withParser('ts');
17
+
18
+ /**
19
+ * @type import('ts-migrate-server').Plugin<unknown>
20
+ */
21
+ const widgetColumnMapPlugin = {
22
+ name: 'widget-column-map-plugin',
23
+
24
+ async run({text, fileName, options}) {
25
+ let className = fileName.substring(fileName.lastIndexOf('/') + 1, fileName.lastIndexOf('.'));
26
+
27
+ if (!className || !className.endsWith('Model')) {
28
+ return text;
29
+ }
30
+
31
+ let root = j(text);
32
+ let widgets = new Map(),
33
+ tables = new Map();
34
+
35
+ // parse model and find all objects containing an id and objectType property
36
+ // noinspection JSCheckFunctionSignatures
37
+ root.find(j.ExportDefaultDeclaration)
38
+ .find(j.ArrowFunctionExpression)
39
+ .find(j.ObjectExpression, node => findObjectProperty(node, 'id') && findObjectProperty(node, 'objectType'))
40
+ .forEach(path => {
41
+ let node = path.node,
42
+ idAndObjectType = getIdAndObjectType(node),
43
+ objectType = idAndObjectType.objectType;
44
+ if (isWidget(objectType)) {
45
+ // remember id and objectType of all widget nodes
46
+ widgets.set(node, idAndObjectType);
47
+ }
48
+ if (isColumn(objectType)) {
49
+ // collect all column infos for one table
50
+ let tablePath = findParentTablePath(path);
51
+ if (!tablePath) {
52
+ return;
53
+ }
54
+ let tableInfo = tables.get(tablePath.node);
55
+ if (!tableInfo) {
56
+ let tableFieldPath = findParentTableFieldPath(tablePath);
57
+ tableInfo = {
58
+ // create a table class name from the id of the table or the id of the tableField
59
+ tableClassName: createTableClassName(getId(tablePath.node), tableFieldPath && getId(tableFieldPath.node)),
60
+ columns: new Map()
61
+ };
62
+ tables.set(tablePath.node, tableInfo);
63
+ }
64
+ // remember id and objectType of all column nodes
65
+ let columns = tableInfo.columns;
66
+ columns.set(node, idAndObjectType);
67
+ }
68
+ });
69
+
70
+ if (widgets.size > 0) { // only check size of widgets, if there are entries in tables then there are entries in widgets
71
+ let body = root.get().node.program.body;
72
+
73
+ // get/create widgetMap
74
+ let widgetName = className.substring(0, className.lastIndexOf('Model')),
75
+ widgetMapName = `${widgetName}WidgetMap`,
76
+ widgetMapType = getOrCreateExportedType(widgetMapName, root, body),
77
+ widgetMapMembers = getMembers(widgetMapType),
78
+ widgetMapProperties = [];
79
+
80
+ // create a property for every entry of widgets
81
+ widgets.forEach(({id, objectType}, node) => {
82
+ let tableInfo = tables.get(node);
83
+ if (tableInfo) {
84
+ // add specific table class if available, will be created later on
85
+ objectType = tableInfo.tableClassName;
86
+ }
87
+ widgetMapProperties.push(createMapProperty(id, objectType));
88
+ });
89
+
90
+ // set/replace properties of widgetMap
91
+ widgetMapMembers.splice(0, widgetMapMembers.length, ...widgetMapProperties);
92
+
93
+ // create table class and columnMap for every table
94
+ tables.forEach((tableInfo, node) => {
95
+ // get/create columnMap
96
+ let columnMapName = `${tableInfo.tableClassName}ColumnMap`,
97
+ columnMapType = getOrCreateExportedType(columnMapName, root, body),
98
+ columnMapMembers = getMembers(columnMapType),
99
+ columnMapProperties = [];
100
+
101
+ // create a property for every entry of tableInfo.columns
102
+ tableInfo.columns.forEach(({id, objectType}) => {
103
+ columnMapProperties.push(createMapProperty(id, objectType));
104
+ });
105
+
106
+ // set/replace properties of columnMap
107
+ columnMapMembers.splice(0, columnMapMembers.length, ...columnMapProperties);
108
+
109
+ // get/create tableClass
110
+ let tableClassName = tableInfo.tableClassName,
111
+ tableClass = getOrCreateExportedClass(tableClassName, root, body),
112
+ tableMembers = tableClass.body.body,
113
+ tableSuperClass = widgets.get(node).objectType,
114
+ columnMapProperty = createClassProperty('columnMap', columnMapName);
115
+
116
+ // set superClass to objectType from model
117
+ tableClass.superClass = j.identifier(tableSuperClass);
118
+
119
+ // declare columnMap property
120
+ columnMapProperty.declare = true;
121
+ tableMembers.splice(0, tableMembers.length, columnMapProperty);
122
+ });
123
+
124
+ let mainWidgetFileNameWithoutFileExtension = fileName.substring(0, fileName.lastIndexOf('Model')),
125
+ mainWidgetFileName, mainWidgetRoot;
126
+
127
+ // look for main widget as .js
128
+ mainWidgetFileName = mainWidgetFileNameWithoutFileExtension + '.js';
129
+ mainWidgetRoot = getMainWidgetRoot(mainWidgetFileName);
130
+
131
+ // look for main widget as .ts
132
+ if (!mainWidgetRoot) {
133
+ mainWidgetFileName = mainWidgetFileNameWithoutFileExtension + '.ts';
134
+ mainWidgetRoot = getMainWidgetRoot(mainWidgetFileName);
135
+ }
136
+
137
+ if (mainWidgetRoot) {
138
+ if (mainWidgetFileName.endsWith('.js')) {
139
+ // add widgetMap property with @type in .js case
140
+ // noinspection JSCheckFunctionSignatures
141
+ mainWidgetRoot
142
+ .find(j.ClassDeclaration, {id: {name: widgetName}})
143
+ .find(j.ClassMethod, {kind: 'constructor'})
144
+ .forEach(/** NodePath<namedTypes.ClassMethod, namedTypes.ClassMethod> */path => {
145
+ let node = path.node,
146
+ constructorMembers = node.body.body,
147
+ widgetMapAssignmentExpression = findConstructorAssignmentExpression(constructorMembers, 'widgetMap');
148
+ if (!widgetMapAssignmentExpression) {
149
+ // create an assignment expression after the super call
150
+ let superCall = findConstructorSuperCall(constructorMembers),
151
+ superCallIndex = constructorMembers.indexOf(superCall) + 1;
152
+ widgetMapAssignmentExpression = createAssignmentExpressionWithNull('widgetMap');
153
+ constructorMembers.splice(superCallIndex, 0, widgetMapAssignmentExpression);
154
+ }
155
+ // add type comment
156
+ widgetMapAssignmentExpression.comments = [createJsDocTypeComment(widgetMapName)];
157
+ });
158
+ }
159
+ if (mainWidgetFileName.endsWith('.ts')) {
160
+ // declare widgetMap property in .ts case
161
+ // noinspection JSCheckFunctionSignatures
162
+ mainWidgetRoot
163
+ .find(j.ClassDeclaration, {id: {name: widgetName}})
164
+ .forEach(/** NodePath<namedTypes.ClassDeclaration, namedTypes.ClassDeclaration> */path => {
165
+ let node = path.node,
166
+ classMembers = node.body.body,
167
+ widgetMapProperty = findClassProperty(classMembers, 'widgetMap');
168
+ if (widgetMapProperty) {
169
+ widgetMapProperty.typeAnnotation = createTypeAnnotation(widgetMapName);
170
+ } else {
171
+ widgetMapProperty = createClassProperty('widgetMap', widgetMapName);
172
+ classMembers.splice(0, 0, widgetMapProperty);
173
+ }
174
+ widgetMapProperty.declare = true;
175
+
176
+ // remove widgetMap assignment from constructor
177
+ let classConstructor = findConstructor(classMembers),
178
+ constructorMembers = classConstructor ? classConstructor.body.body : [],
179
+ widgetMapAssignmentExpression = classConstructor ? findConstructorAssignmentExpression(constructorMembers, 'widgetMap') : null;
180
+ if (widgetMapAssignmentExpression) {
181
+ constructorMembers.splice(constructorMembers.indexOf(widgetMapAssignmentExpression), 1);
182
+ }
183
+ });
184
+
185
+ // insert missing imports
186
+ insertMissingImportsForTypes(j, mainWidgetRoot, [mapType(j, `tempModule.${widgetMapName}`)], {tempModule: `./${className}`}, mainWidgetFileName);
187
+ }
188
+
189
+ // write file
190
+ fs.writeFileSync(mainWidgetFileName, crlfToLf(removeEmptyLinesBetweenImports(mainWidgetRoot.toSource(defaultRecastOptions))));
191
+ }
192
+ }
193
+
194
+ return root.toSource(defaultRecastOptions);
195
+ }
196
+ };
197
+
198
+ function getMainWidgetRoot(mainWidgetFileName) {
199
+ try {
200
+ let mainWidgetBuffer = fs.readFileSync(mainWidgetFileName);
201
+ return j(mainWidgetBuffer.toString());
202
+ } catch (error) {
203
+ // nop
204
+ }
205
+ return null;
206
+ }
207
+
208
+ function findObjectProperty(objectNode, propertyName) {
209
+ return objectNode.properties.find(
210
+ n =>
211
+ n.type === 'ObjectProperty' &&
212
+ n.key.type === 'Identifier' &&
213
+ n.key.name === propertyName
214
+ );
215
+ }
216
+
217
+ function findClassProperty(classMembers, propertyName) {
218
+ return classMembers.find(n =>
219
+ n.type === 'ClassProperty' &&
220
+ n.key.type === 'Identifier' &&
221
+ n.key.name === propertyName);
222
+ }
223
+
224
+ function findConstructor(classMembers) {
225
+ return classMembers.find(n =>
226
+ n.type === 'ClassMethod' &&
227
+ n.kind === 'constructor'
228
+ );
229
+ }
230
+
231
+ function findConstructorAssignmentExpression(constructorMembers, propertyName) {
232
+ return constructorMembers.find(n =>
233
+ n.type === 'ExpressionStatement' &&
234
+ n.expression.type === 'AssignmentExpression' &&
235
+ n.expression.left.type === 'MemberExpression' &&
236
+ n.expression.left.object.type === 'ThisExpression' &&
237
+ n.expression.left.property.type === 'Identifier' &&
238
+ n.expression.left.property.name === propertyName
239
+ );
240
+ }
241
+
242
+ function findConstructorSuperCall(constructorMembers) {
243
+ return constructorMembers.find(n =>
244
+ n.type === 'ExpressionStatement' &&
245
+ n.expression.type === 'CallExpression' &&
246
+ n.expression.callee.type === 'Super'
247
+ );
248
+ }
249
+
250
+ function findParentTablePath(columnPath) {
251
+ return findParentPathByObjectType(columnPath, isTable);
252
+ }
253
+
254
+ function findParentTableFieldPath(tablePath) {
255
+ return findParentPathByObjectType(tablePath, isTableField);
256
+ }
257
+
258
+ function findParentPathByObjectType(path, objectTypePredicate) {
259
+ return findParentPath(path, p => p.node.type === 'ObjectExpression' && objectTypePredicate((findObjectProperty(p.node, 'objectType') || {value: {}}).value.name));
260
+ }
261
+
262
+ function getId(node) {
263
+ let idProperty = findObjectProperty(node, 'id');
264
+ return ((idProperty || {}).value || {}).value;
265
+ }
266
+
267
+ function getObjectType(node) {
268
+ let objectTypeProperty = findObjectProperty(node, 'objectType');
269
+ return ((objectTypeProperty || {}).value || {}).name;
270
+ }
271
+
272
+ function getIdAndObjectType(node) {
273
+ let id = getId(node),
274
+ objectType = getObjectType(node);
275
+ return {id, objectType};
276
+ }
277
+
278
+ function createTableClassName(tableId, tableFieldId) {
279
+ if (tableId && tableId !== 'Table') {
280
+ return tableId.replaceAll('.', '');
281
+ }
282
+ if (tableFieldId) {
283
+ return tableFieldId.replaceAll('.', '') + 'Table';
284
+ }
285
+ throw new Error('At least one of tableId, tableFieldId must be set');
286
+ }
287
+
288
+ function getOrCreateExportedType(name, root, body) {
289
+ let candidates = root
290
+ .find(j.TSTypeAliasDeclaration)
291
+ .filter(/** NodePath<TSTypeAliasDeclaration, TSTypeAliasDeclaration> */path => path.node.id.name === name);
292
+ if (candidates.length) {
293
+ return candidates.get().node;
294
+ }
295
+ let type = j.tsTypeAliasDeclaration(j.identifier(name), j.tsTypeLiteral([]));
296
+ body.push(j.exportNamedDeclaration(type));
297
+ return type;
298
+ }
299
+
300
+ function getMembers(type) {
301
+ if (type.typeAnnotation.type === 'TSIntersectionType') {
302
+ return (type.typeAnnotation.types.find(t => t.type === 'TSTypeLiteral') || {members: []}).members;
303
+ }
304
+ return type.typeAnnotation.members;
305
+ }
306
+
307
+ function getOrCreateExportedClass(name, root, body) {
308
+ let candidates = root
309
+ .find(j.ClassDeclaration)
310
+ .filter(/** NodePath<ClassDeclaration, ClassDeclaration> */path => path.node.id.name === name);
311
+ if (candidates.length) {
312
+ return candidates.get().node;
313
+ }
314
+ let type = j.classDeclaration(j.identifier(name), j.classBody([]), null);
315
+ body.push(j.exportNamedDeclaration(type));
316
+ return type;
317
+ }
318
+
319
+ function createMapProperty(id, objectType) {
320
+ let identifier = j.identifier(`'${id}'`),
321
+ // add trailing ; to type, otherwise there is no ; at the end of the line when you use tsPropertySignature
322
+ typeAnnotation = createTypeAnnotation(`${addGenericIfNecessary(objectType)};`);
323
+ return j.tsPropertySignature(identifier, typeAnnotation);
324
+ }
325
+
326
+ function createClassProperty(id, objectType) {
327
+ let identifier = j.identifier(id),
328
+ typeAnnotation = createTypeAnnotation(addGenericIfNecessary(objectType));
329
+ return j.classProperty(identifier, null, typeAnnotation);
330
+ }
331
+
332
+ function createTypeAnnotation(objectType) {
333
+ return j.tsTypeAnnotation(j.tsTypeReference(j.identifier(objectType)));
334
+ }
335
+
336
+ function createAssignmentExpressionWithNull(propertyName) {
337
+ let identifier = j.identifier(propertyName),
338
+ member = j.memberExpression(j.thisExpression(), identifier),
339
+ assignment = j.assignmentExpression('=', member, j.nullLiteral());
340
+ return j.expressionStatement(assignment);
341
+ }
342
+
343
+ function createJsDocTypeComment(type) {
344
+ return j.commentBlock(`* @type ${type} `);
345
+ }
346
+
347
+ function isWidget(objectType) {
348
+ return objectType && !isColumn(objectType)
349
+ && !objectType.endsWith('TableRow')
350
+ && !objectType.endsWith('TreeNode')
351
+ && !objectType.endsWith('Page')
352
+ && objectType !== 'Status'
353
+ && !objectType.endsWith('CodeType')
354
+ && !objectType.endsWith('LookupCall');
355
+ }
356
+
357
+ function isColumn(objectType) {
358
+ return objectType && objectType.endsWith('Column');
359
+ }
360
+
361
+ function isTable(objectType) {
362
+ return objectType && objectType.endsWith('Table');
363
+ }
364
+
365
+ function isTableField(objectType) {
366
+ return objectType && objectType.endsWith('TableField');
367
+ }
368
+
369
+ function needsGeneric(objectType) {
370
+ return objectType && (objectType === 'SmartField'
371
+ || objectType === 'SmartColumn'
372
+ || objectType === 'ListBox'
373
+ || objectType === 'TreeBox'
374
+ || objectType === 'ModeSelectorField'
375
+ || objectType === 'RadioButtonGroup'
376
+ || objectType === 'RadioButton');
377
+ }
378
+
379
+ function addGenericIfNecessary(objectType) {
380
+ if (needsGeneric(objectType)) {
381
+ return `${objectType}<any>`;
382
+ }
383
+ return objectType;
384
+ }
385
+
386
+ export default widgetColumnMapPlugin;
@@ -0,0 +1,107 @@
1
+ /*
2
+ * Copyright (c) 2010, 2023 BSI Business Systems Integration AG
3
+ *
4
+ * This program and the accompanying materials are made
5
+ * available under the terms of the Eclipse Public License 2.0
6
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
7
+ *
8
+ * SPDX-License-Identifier: EPL-2.0
9
+ */
10
+ import jscodeshift from 'jscodeshift';
11
+ import {defaultModuleMap, defaultRecastOptions, findClassName, findClassProperty, findParentClassBody, insertMissingImportsForTypes, mapType, removeEmptyLinesBetweenImports} from './common.js';
12
+
13
+ const j = jscodeshift.withParser('ts');
14
+ let referencedTypes;
15
+
16
+ /**
17
+ * @type import('ts-migrate-server').Plugin<{moduleMap?: object, defaultParamType?: string}>
18
+ */
19
+ const widgetPropertiesPlugin = {
20
+ name: 'widget-properties-plugin',
21
+
22
+ async run({text, options, sourceFile}) {
23
+ let root = j(text);
24
+ const moduleMap = {...defaultModuleMap, ...options.moduleMap};
25
+ referencedTypes = new Set();
26
+
27
+ let result = new Map();
28
+
29
+ // Find all widget properties for each class in file
30
+ root.find(j.Declaration)
31
+ .filter(path => path.node.type === j.ClassMethod.name && path.node.kind === 'constructor')
32
+ .find(j.Identifier)
33
+ .filter(path => path.node.name === '_addWidgetProperties' || path.node.name === '_addPreserveOnPropertyChangeProperties')
34
+ .forEach(path => {
35
+ let argument = path.parentPath.parentPath.value.arguments[0];
36
+ let className = findClassName(path);
37
+ let widgetProperties = result.get(className);
38
+ if (!widgetProperties) {
39
+ widgetProperties = [];
40
+ result.set(className, widgetProperties);
41
+ }
42
+ if (path.node.name === '_addPreserveOnPropertyChangeProperties') {
43
+ // Don't add resp. remove preserve properties
44
+ if (argument.type === j.StringLiteral.name) {
45
+ removeFromArray(widgetProperties, argument.value);
46
+ } else if (argument.type === j.ArrayExpression.name) {
47
+ for (let prop of argument.elements.map(elem => elem.value)) {
48
+ removeFromArray(widgetProperties, prop);
49
+ }
50
+ }
51
+ } else {
52
+ if (argument.type === j.StringLiteral.name) {
53
+ widgetProperties.push(argument.value);
54
+ } else if (argument.type === j.ArrayExpression.name) {
55
+ widgetProperties.push(...argument.elements.map(element => element.value));
56
+ }
57
+ }
58
+ });
59
+
60
+ // Find all setters, use data type of the property and wrap it with the ChildRef type
61
+ root.find(j.ClassMethod)
62
+ .filter(path => path.node.key.name.startsWith('set'))
63
+ .forEach(path => {
64
+ let className = findClassName(path);
65
+ let widgetProperties = result.get(className);
66
+ if (!widgetProperties) {
67
+ return;
68
+ }
69
+ let setter = path.node;
70
+ let setterName = setter.key.name;
71
+ let propertyName = setterName.substring(3, 4).toLowerCase() + setterName.substring(4);
72
+ if (!widgetProperties.includes(propertyName)) {
73
+ return;
74
+ }
75
+ let property = findClassProperty(findParentClassBody(path), propertyName);
76
+ if (!property) {
77
+ return;
78
+ }
79
+ let typeAnnotation = property.typeAnnotation?.typeAnnotation;
80
+ if (!typeAnnotation) {
81
+ return;
82
+ }
83
+ let newType;
84
+ if (typeAnnotation.type === j.TSArrayType.name) {
85
+ newType = mapType(j, 'scout.ChildRef[]');
86
+ newType.type.elementType.typeParameters = j.tsTypeParameterInstantiation([typeAnnotation.elementType]);
87
+ } else {
88
+ newType = mapType(j, 'scout.ChildRef');
89
+ newType.type.typeParameters = j.tsTypeParameterInstantiation([typeAnnotation]);
90
+ }
91
+ setter.params[0].typeAnnotation = j.tsTypeAnnotation(newType.type);
92
+ referencedTypes.add(newType);
93
+ });
94
+
95
+ insertMissingImportsForTypes(j, root, Array.from(referencedTypes), moduleMap, sourceFile.fileName);
96
+ return removeEmptyLinesBetweenImports(root.toSource(defaultRecastOptions));
97
+ }
98
+ };
99
+
100
+ function removeFromArray(array, elem) {
101
+ let index = array.indexOf(elem);
102
+ if (index > -1) {
103
+ array.splice(index, 1);
104
+ }
105
+ }
106
+
107
+ export default widgetPropertiesPlugin;