@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.
- package/LICENSE +277 -0
- package/README.md +189 -0
- package/bin/scout-migrate.js +166 -0
- package/package.json +51 -0
- package/src/common.js +666 -0
- package/src/convertToCRLFPlugin.js +34 -0
- package/src/convertToLFPlugin.js +31 -0
- package/src/countMethodsPlugin.js +36 -0
- package/src/declareMissingClassPropertiesPlugin.js +139 -0
- package/src/memberAccessModifierPlugin.js +51 -0
- package/src/menuTypesPlugin.js +77 -0
- package/src/methodsPlugin.js +143 -0
- package/src/printEventMapsPlugin.js +78 -0
- package/src/typedObjectTypePlugin.js +64 -0
- package/src/widgetColumnMapPlugin.js +386 -0
- package/src/widgetPropertiesPlugin.js +107 -0
|
@@ -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;
|