@komaci/esm-generator 262.16.0 → 262.18.0
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.
|
@@ -25,6 +25,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
26
|
const __1 = require("..");
|
|
27
27
|
const modgen = __importStar(require("../genericAstGeneration"));
|
|
28
|
+
const metadata_1 = require("@lwc/metadata");
|
|
28
29
|
describe('Handling errors that throw inside a call to generateKomaciModule', () => {
|
|
29
30
|
it('should return error adg when an error is produced', () => {
|
|
30
31
|
const input = {
|
|
@@ -290,4 +291,103 @@ describe('normalizeSrcFileKeys', () => {
|
|
|
290
291
|
expect(input.srcFileMap['templates/test2.html']).not.toBeUndefined();
|
|
291
292
|
});
|
|
292
293
|
});
|
|
294
|
+
describe('Bundle processing', () => {
|
|
295
|
+
it('when both .js and .ts files exist with for the component name', () => {
|
|
296
|
+
const bundleSrcFileMap = {
|
|
297
|
+
'test.js': {},
|
|
298
|
+
'test.ts': {},
|
|
299
|
+
'test.html': {},
|
|
300
|
+
};
|
|
301
|
+
const input = {
|
|
302
|
+
moduleInfo: {
|
|
303
|
+
name: 'test',
|
|
304
|
+
namespace: 'c',
|
|
305
|
+
type: 'bundle',
|
|
306
|
+
files: bundleSrcFileMap,
|
|
307
|
+
},
|
|
308
|
+
srcFileMap: {
|
|
309
|
+
'test.js': 'export default class Test {}',
|
|
310
|
+
'test.ts': 'export default class Test {}',
|
|
311
|
+
'test.html': '<template><div>Test</div></template>',
|
|
312
|
+
'test.css': 'p { text-align: center; }',
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
const res = (0, __1.generateKomaciModule)(input);
|
|
316
|
+
expect(res).toContain('ErrorFunction');
|
|
317
|
+
expect(res).toContain('[komaci esm-generator] multiple component script files with the same name found in bundle: test');
|
|
318
|
+
});
|
|
319
|
+
it('can handle multiple script files with the same name in different directories', () => {
|
|
320
|
+
const bundleSrcFileMap = {
|
|
321
|
+
'test.js': {},
|
|
322
|
+
'subdir/test.ts': {},
|
|
323
|
+
'test.html': {},
|
|
324
|
+
};
|
|
325
|
+
const input = {
|
|
326
|
+
moduleInfo: {
|
|
327
|
+
name: 'test',
|
|
328
|
+
namespace: 'c',
|
|
329
|
+
type: 'bundle',
|
|
330
|
+
files: bundleSrcFileMap,
|
|
331
|
+
},
|
|
332
|
+
srcFileMap: {
|
|
333
|
+
'test.js': 'export default class Test {}',
|
|
334
|
+
'subdir/test.ts': 'export default class Test {}',
|
|
335
|
+
'test.html': '<template><div>Test</div></template>',
|
|
336
|
+
'test.css': 'p { text-align: center; }',
|
|
337
|
+
},
|
|
338
|
+
};
|
|
339
|
+
const res = (0, __1.generateKomaciModule)(input);
|
|
340
|
+
expect(res).not.toContain('ErrorFunction');
|
|
341
|
+
expect(res).toContain('registerAdgFunction');
|
|
342
|
+
});
|
|
343
|
+
it('can handle multiple script files with different names in same directory', () => {
|
|
344
|
+
const testJsSource = `export default class Test extends LightningElement {
|
|
345
|
+
get testProp() {
|
|
346
|
+
return 'test';
|
|
347
|
+
}
|
|
348
|
+
}`;
|
|
349
|
+
const fooTsSource = `export default class Foo {
|
|
350
|
+
get fooProp() {
|
|
351
|
+
return 'foo';
|
|
352
|
+
}
|
|
353
|
+
}`;
|
|
354
|
+
const bundleConfig = {
|
|
355
|
+
namespace: 'c',
|
|
356
|
+
name: 'test',
|
|
357
|
+
type: 'internal',
|
|
358
|
+
files: [
|
|
359
|
+
{ fileName: 'test.js', source: testJsSource },
|
|
360
|
+
{ fileName: 'foo.ts', source: fooTsSource },
|
|
361
|
+
{ fileName: 'test.html', source: '<template><div>Test</div></template>' },
|
|
362
|
+
{ fileName: 'test.css', source: 'p { text-align: center; }' },
|
|
363
|
+
],
|
|
364
|
+
enableKomaci: true,
|
|
365
|
+
namespaceMapping: {},
|
|
366
|
+
npmModuleMapping: {},
|
|
367
|
+
};
|
|
368
|
+
const metadata = (0, metadata_1.collectBundleMetadata)(bundleConfig);
|
|
369
|
+
const inputFiles = Object.fromEntries(metadata.files
|
|
370
|
+
.map(({ fileName, komaciDoc }) => [fileName, komaciDoc])
|
|
371
|
+
.filter(([, komaciDoc]) => !!komaciDoc));
|
|
372
|
+
const input = {
|
|
373
|
+
moduleInfo: {
|
|
374
|
+
name: 'test',
|
|
375
|
+
namespace: 'c',
|
|
376
|
+
type: 'bundle',
|
|
377
|
+
files: inputFiles,
|
|
378
|
+
},
|
|
379
|
+
srcFileMap: {
|
|
380
|
+
'test.js': testJsSource,
|
|
381
|
+
'foo.ts': fooTsSource,
|
|
382
|
+
'test.html': '<template><div>Test</div></template>',
|
|
383
|
+
'test.css': 'p { text-align: center; }',
|
|
384
|
+
},
|
|
385
|
+
};
|
|
386
|
+
const res = (0, __1.generateKomaciModule)(input);
|
|
387
|
+
expect(res).not.toContain('ErrorFunction');
|
|
388
|
+
expect(res).toContain('registerAdgFunction');
|
|
389
|
+
expect(res).toContain('testProp');
|
|
390
|
+
expect(res).not.toContain('fooProp');
|
|
391
|
+
});
|
|
392
|
+
});
|
|
293
393
|
//# sourceMappingURL=index.spec.js.map
|
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
7
|
+
const parser_1 = require("@babel/parser");
|
|
8
|
+
const traverse_1 = __importDefault(require("@babel/traverse"));
|
|
9
|
+
const generator_1 = __importDefault(require("@babel/generator"));
|
|
10
|
+
const functionAstGeneration_1 = require("../functionAstGeneration");
|
|
11
|
+
/**
|
|
12
|
+
* Helper function to parse TypeScript code and extract a class method/property
|
|
13
|
+
* for testing sanitizeFunction
|
|
14
|
+
*/
|
|
15
|
+
function parseAndExtractClassMember(code) {
|
|
16
|
+
const ast = (0, parser_1.parse)(code, {
|
|
17
|
+
sourceType: 'module',
|
|
18
|
+
plugins: ['typescript', 'classProperties'],
|
|
19
|
+
});
|
|
20
|
+
let targetPath = null;
|
|
21
|
+
(0, traverse_1.default)(ast, {
|
|
22
|
+
ClassMethod(path) {
|
|
23
|
+
if (!targetPath) {
|
|
24
|
+
targetPath = path;
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
ClassProperty(path) {
|
|
28
|
+
if (!targetPath) {
|
|
29
|
+
targetPath = path;
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
return targetPath;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Helper to sanitize and generate code from a TypeScript class member
|
|
37
|
+
*/
|
|
38
|
+
function sanitizeAndGenerate(code) {
|
|
39
|
+
const memberPath = parseAndExtractClassMember(code);
|
|
40
|
+
if (!memberPath) {
|
|
41
|
+
throw new Error('No class member found in code');
|
|
42
|
+
}
|
|
43
|
+
const importMetadata = new Map();
|
|
44
|
+
const gqlMetadata = new Map();
|
|
45
|
+
(0, functionAstGeneration_1.sanitizeFunction)(memberPath, importMetadata, gqlMetadata);
|
|
46
|
+
return (0, generator_1.default)(memberPath.node).code;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Helper to assert that code is valid JavaScript (no TypeScript syntax).
|
|
50
|
+
* Wraps method code in a minimal class structure for parsing.
|
|
51
|
+
* Throws if the code cannot be parsed as plain JavaScript.
|
|
52
|
+
*/
|
|
53
|
+
function assertValidJavaScript(methodCode) {
|
|
54
|
+
// Wrap the method in a class so it's parseable as standalone JS
|
|
55
|
+
const wrappedCode = `class TestClass { ${methodCode} }`;
|
|
56
|
+
try {
|
|
57
|
+
(0, parser_1.parse)(wrappedCode, {
|
|
58
|
+
sourceType: 'module',
|
|
59
|
+
plugins: [], // No TypeScript plugin - must be valid JS
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
throw new Error(`Generated code is not valid JavaScript:\n${methodCode}\n\nParse error: ${error instanceof Error ? error.message : String(error)}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
describe('sanitizeFunction - Direct TypeScript Syntax Stripping Tests', () => {
|
|
67
|
+
describe('TSNonNullExpression - Non-null assertion (!)', () => {
|
|
68
|
+
it('should remove non-null assertion from return statement', () => {
|
|
69
|
+
const code = `
|
|
70
|
+
class Test {
|
|
71
|
+
get value() {
|
|
72
|
+
return this.data!;
|
|
73
|
+
}
|
|
74
|
+
}`;
|
|
75
|
+
const result = sanitizeAndGenerate(code);
|
|
76
|
+
expect(result).toContain('props.data');
|
|
77
|
+
expect(result).not.toContain('!');
|
|
78
|
+
assertValidJavaScript(result);
|
|
79
|
+
});
|
|
80
|
+
it('should remove chained non-null assertions', () => {
|
|
81
|
+
const code = `
|
|
82
|
+
class Test {
|
|
83
|
+
get value() {
|
|
84
|
+
return this.data!.name!.value;
|
|
85
|
+
}
|
|
86
|
+
}`;
|
|
87
|
+
const result = sanitizeAndGenerate(code);
|
|
88
|
+
expect(result).not.toContain('!');
|
|
89
|
+
expect(result).toContain('props.data.name.value');
|
|
90
|
+
assertValidJavaScript(result);
|
|
91
|
+
});
|
|
92
|
+
it('should remove non-null in array access', () => {
|
|
93
|
+
const code = `
|
|
94
|
+
class Test {
|
|
95
|
+
get first() {
|
|
96
|
+
return this.items![0];
|
|
97
|
+
}
|
|
98
|
+
}`;
|
|
99
|
+
const result = sanitizeAndGenerate(code);
|
|
100
|
+
expect(result).not.toContain('!');
|
|
101
|
+
expect(result).toContain('props.items[0]');
|
|
102
|
+
assertValidJavaScript(result);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
describe('TSAsExpression - Type assertions (as)', () => {
|
|
106
|
+
it('should remove simple as expression', () => {
|
|
107
|
+
const code = `
|
|
108
|
+
class Test {
|
|
109
|
+
get stringValue() {
|
|
110
|
+
return this.value as string;
|
|
111
|
+
}
|
|
112
|
+
}`;
|
|
113
|
+
const result = sanitizeAndGenerate(code);
|
|
114
|
+
expect(result).not.toContain(' as ');
|
|
115
|
+
expect(result).toContain('props.value');
|
|
116
|
+
});
|
|
117
|
+
it('should remove double as cast (as unknown as Type)', () => {
|
|
118
|
+
const code = `
|
|
119
|
+
class Test {
|
|
120
|
+
get boolValue() {
|
|
121
|
+
return (this.value as unknown) as boolean;
|
|
122
|
+
}
|
|
123
|
+
}`;
|
|
124
|
+
const result = sanitizeAndGenerate(code);
|
|
125
|
+
expect(result).not.toContain(' as ');
|
|
126
|
+
expect(result).toContain('props.value');
|
|
127
|
+
});
|
|
128
|
+
it('should remove as with complex object type', () => {
|
|
129
|
+
const code = `
|
|
130
|
+
class Test {
|
|
131
|
+
get config() {
|
|
132
|
+
return this.data as { name: string; value: number };
|
|
133
|
+
}
|
|
134
|
+
}`;
|
|
135
|
+
const result = sanitizeAndGenerate(code);
|
|
136
|
+
expect(result).not.toContain(' as ');
|
|
137
|
+
expect(result).toContain('props.data');
|
|
138
|
+
});
|
|
139
|
+
it('should remove as in arrow function parameters', () => {
|
|
140
|
+
const code = `
|
|
141
|
+
class Test {
|
|
142
|
+
get mapped() {
|
|
143
|
+
return this.items.map((item: any, index: number) => (item as string));
|
|
144
|
+
}
|
|
145
|
+
}`;
|
|
146
|
+
const result = sanitizeAndGenerate(code);
|
|
147
|
+
expect(result).not.toContain(' as string');
|
|
148
|
+
expect(result).not.toContain(': any');
|
|
149
|
+
expect(result).not.toContain(': number');
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
describe('TSTypeAssertion - Angle-bracket assertions (<Type>)', () => {
|
|
153
|
+
it('should remove angle-bracket type assertion', () => {
|
|
154
|
+
const code = `
|
|
155
|
+
class Test {
|
|
156
|
+
get value() {
|
|
157
|
+
return (<string>this.data);
|
|
158
|
+
}
|
|
159
|
+
}`;
|
|
160
|
+
const result = sanitizeAndGenerate(code);
|
|
161
|
+
expect(result).not.toContain('<string>');
|
|
162
|
+
expect(result).toContain('props.data');
|
|
163
|
+
});
|
|
164
|
+
it('should remove nested angle-bracket assertions', () => {
|
|
165
|
+
const code = `
|
|
166
|
+
class Test {
|
|
167
|
+
get value() {
|
|
168
|
+
return (<any>(<string>this.data));
|
|
169
|
+
}
|
|
170
|
+
}`;
|
|
171
|
+
const result = sanitizeAndGenerate(code);
|
|
172
|
+
expect(result).not.toContain('<any>');
|
|
173
|
+
expect(result).not.toContain('<string>');
|
|
174
|
+
expect(result).toContain('props.data');
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
describe('TSTypeAnnotation - Type annotations', () => {
|
|
178
|
+
it('should remove type annotation from variable', () => {
|
|
179
|
+
const code = `
|
|
180
|
+
class Test {
|
|
181
|
+
get items() {
|
|
182
|
+
const list: string[] = [];
|
|
183
|
+
return list;
|
|
184
|
+
}
|
|
185
|
+
}`;
|
|
186
|
+
const result = sanitizeAndGenerate(code);
|
|
187
|
+
expect(result).not.toContain(': string[]');
|
|
188
|
+
expect(result).toContain('const list = []');
|
|
189
|
+
});
|
|
190
|
+
it('should remove type annotations from function parameters', () => {
|
|
191
|
+
const code = `
|
|
192
|
+
class Test {
|
|
193
|
+
get transformed() {
|
|
194
|
+
return this.data.map((item: string, index: number) => item + index);
|
|
195
|
+
}
|
|
196
|
+
}`;
|
|
197
|
+
const result = sanitizeAndGenerate(code);
|
|
198
|
+
expect(result).not.toContain(': string');
|
|
199
|
+
expect(result).not.toContain(': number');
|
|
200
|
+
expect(result).toContain('(item, index)');
|
|
201
|
+
});
|
|
202
|
+
it('should remove complex type annotations', () => {
|
|
203
|
+
const code = `
|
|
204
|
+
class Test {
|
|
205
|
+
get complexData() {
|
|
206
|
+
const data: { name: string; values: number[] } = this.config;
|
|
207
|
+
return data;
|
|
208
|
+
}
|
|
209
|
+
}`;
|
|
210
|
+
const result = sanitizeAndGenerate(code);
|
|
211
|
+
expect(result).not.toContain(': {');
|
|
212
|
+
expect(result).toContain('const data = props.config');
|
|
213
|
+
});
|
|
214
|
+
it('should remove type annotation with typeof', () => {
|
|
215
|
+
const code = `
|
|
216
|
+
class Test {
|
|
217
|
+
get mapped() {
|
|
218
|
+
return this.items.map((item: typeof this.config) => item);
|
|
219
|
+
}
|
|
220
|
+
}`;
|
|
221
|
+
const result = sanitizeAndGenerate(code);
|
|
222
|
+
expect(result).not.toContain('typeof');
|
|
223
|
+
expect(result).not.toContain(': typeof');
|
|
224
|
+
expect(result).toContain('item => item');
|
|
225
|
+
});
|
|
226
|
+
it('should remove type annotation with keyof', () => {
|
|
227
|
+
const code = `
|
|
228
|
+
class Test {
|
|
229
|
+
get keys() {
|
|
230
|
+
return this.list.map((key: keyof typeof this.data) => key);
|
|
231
|
+
}
|
|
232
|
+
}`;
|
|
233
|
+
const result = sanitizeAndGenerate(code);
|
|
234
|
+
expect(result).not.toContain('keyof');
|
|
235
|
+
expect(result).not.toContain(': keyof');
|
|
236
|
+
expect(result).toContain('key => key');
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
describe('TSTypeParameterInstantiation - Generic type arguments', () => {
|
|
240
|
+
it('should remove generic type arguments from Array', () => {
|
|
241
|
+
const code = `
|
|
242
|
+
class Test {
|
|
243
|
+
get items() {
|
|
244
|
+
return Array<string>();
|
|
245
|
+
}
|
|
246
|
+
}`;
|
|
247
|
+
const result = sanitizeAndGenerate(code);
|
|
248
|
+
expect(result).not.toContain('<string>');
|
|
249
|
+
expect(result).toContain('Array()');
|
|
250
|
+
});
|
|
251
|
+
it('should remove generic type arguments from function calls', () => {
|
|
252
|
+
const code = `
|
|
253
|
+
class Test {
|
|
254
|
+
get converted() {
|
|
255
|
+
return this.convert<number, string>(this.value);
|
|
256
|
+
}
|
|
257
|
+
}`;
|
|
258
|
+
const result = sanitizeAndGenerate(code);
|
|
259
|
+
expect(result).not.toContain('<number');
|
|
260
|
+
expect(result).not.toContain('<string>');
|
|
261
|
+
expect(result).toContain('props.convert(props.value)');
|
|
262
|
+
});
|
|
263
|
+
it('should remove nested generic types', () => {
|
|
264
|
+
const code = `
|
|
265
|
+
class Test {
|
|
266
|
+
get matrix() {
|
|
267
|
+
return Array<Array<number>>();
|
|
268
|
+
}
|
|
269
|
+
}`;
|
|
270
|
+
const result = sanitizeAndGenerate(code);
|
|
271
|
+
expect(result).not.toContain('<Array<number>>');
|
|
272
|
+
expect(result).toContain('Array()');
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
describe('TSTypeParameterDeclaration - Generic type parameters', () => {
|
|
276
|
+
it('should remove generic type parameter from method', () => {
|
|
277
|
+
const code = `
|
|
278
|
+
class Test {
|
|
279
|
+
transform<T>(value: T): T {
|
|
280
|
+
return value;
|
|
281
|
+
}
|
|
282
|
+
}`;
|
|
283
|
+
const result = sanitizeAndGenerate(code);
|
|
284
|
+
expect(result).not.toContain('<T>');
|
|
285
|
+
expect(result).not.toContain(': T');
|
|
286
|
+
expect(result).toContain('transform(value)');
|
|
287
|
+
});
|
|
288
|
+
it('should remove multiple generic type parameters', () => {
|
|
289
|
+
const code = `
|
|
290
|
+
class Test {
|
|
291
|
+
map<T, U>(input: T, fn: (x: T) => U): U {
|
|
292
|
+
return fn(input);
|
|
293
|
+
}
|
|
294
|
+
}`;
|
|
295
|
+
const result = sanitizeAndGenerate(code);
|
|
296
|
+
expect(result).not.toContain('<T, U>');
|
|
297
|
+
expect(result).not.toContain(': T');
|
|
298
|
+
expect(result).not.toContain(': U');
|
|
299
|
+
expect(result).toContain('map(input, fn)');
|
|
300
|
+
});
|
|
301
|
+
it('should remove generic with extends constraint', () => {
|
|
302
|
+
const code = `
|
|
303
|
+
class Test {
|
|
304
|
+
process<T extends { id: string }>(item: T) {
|
|
305
|
+
return item.id;
|
|
306
|
+
}
|
|
307
|
+
}`;
|
|
308
|
+
const result = sanitizeAndGenerate(code);
|
|
309
|
+
expect(result).not.toContain('<T extends');
|
|
310
|
+
expect(result).not.toContain(': T');
|
|
311
|
+
expect(result).toContain('process(item)');
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
describe('Real-world patterns from failing modules', () => {
|
|
315
|
+
it('should handle pattern from lightning/formattedPhone', () => {
|
|
316
|
+
const code = `
|
|
317
|
+
class Test {
|
|
318
|
+
get hasValue() {
|
|
319
|
+
return this.value != null && ((this.value as unknown) as string) !== '';
|
|
320
|
+
}
|
|
321
|
+
}`;
|
|
322
|
+
const result = sanitizeAndGenerate(code);
|
|
323
|
+
expect(result).not.toContain(' as ');
|
|
324
|
+
expect(result).toContain('props.value != null');
|
|
325
|
+
expect(result).toContain("!== ''");
|
|
326
|
+
assertValidJavaScript(result);
|
|
327
|
+
});
|
|
328
|
+
it('should handle pattern from appexchange/agenticContent', () => {
|
|
329
|
+
const code = `
|
|
330
|
+
class Test {
|
|
331
|
+
get formattedList() {
|
|
332
|
+
const list = this.items || [];
|
|
333
|
+
return list.map((item: any, index: number) => ({
|
|
334
|
+
...item,
|
|
335
|
+
accordionId: \`accordion-\${index}\`,
|
|
336
|
+
key: \`item-\${index}\`
|
|
337
|
+
}));
|
|
338
|
+
}
|
|
339
|
+
}`;
|
|
340
|
+
const result = sanitizeAndGenerate(code);
|
|
341
|
+
expect(result).not.toContain(': any');
|
|
342
|
+
expect(result).not.toContain(': number');
|
|
343
|
+
expect(result).toContain('(item, index)');
|
|
344
|
+
assertValidJavaScript(result);
|
|
345
|
+
});
|
|
346
|
+
it('should handle pattern from lightning/tabBar', () => {
|
|
347
|
+
const code = `
|
|
348
|
+
class Test {
|
|
349
|
+
get hiddenTargets() {
|
|
350
|
+
const hiddenTargets: string[] = [];
|
|
351
|
+
this.allTabs.forEach(tab => {
|
|
352
|
+
if (!tab.visible && tab.targetSelectionName) {
|
|
353
|
+
hiddenTargets.push(tab.targetSelectionName);
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
return hiddenTargets;
|
|
357
|
+
}
|
|
358
|
+
}`;
|
|
359
|
+
const result = sanitizeAndGenerate(code);
|
|
360
|
+
expect(result).not.toContain(': string[]');
|
|
361
|
+
expect(result).toContain('const hiddenTargets = []');
|
|
362
|
+
assertValidJavaScript(result);
|
|
363
|
+
});
|
|
364
|
+
it('should handle pattern from lightning/modalBase with destructuring', () => {
|
|
365
|
+
const code = `
|
|
366
|
+
class Test {
|
|
367
|
+
get dimensions() {
|
|
368
|
+
const {
|
|
369
|
+
height,
|
|
370
|
+
width
|
|
371
|
+
} = (this.backdropRect as {
|
|
372
|
+
height?: number;
|
|
373
|
+
width?: number;
|
|
374
|
+
});
|
|
375
|
+
return { height, width };
|
|
376
|
+
}
|
|
377
|
+
}`;
|
|
378
|
+
const result = sanitizeAndGenerate(code);
|
|
379
|
+
expect(result).not.toContain(' as ');
|
|
380
|
+
expect(result).not.toContain('height?:');
|
|
381
|
+
expect(result).not.toContain('width?:');
|
|
382
|
+
expect(result).toContain('props.backdropRect');
|
|
383
|
+
assertValidJavaScript(result);
|
|
384
|
+
});
|
|
385
|
+
it('should handle pattern from lightning/outputField', () => {
|
|
386
|
+
const code = `
|
|
387
|
+
class Test {
|
|
388
|
+
get latitude() {
|
|
389
|
+
return (this.uiField.value as {
|
|
390
|
+
latitude?: number | string;
|
|
391
|
+
}).latitude;
|
|
392
|
+
}
|
|
393
|
+
}`;
|
|
394
|
+
const result = sanitizeAndGenerate(code);
|
|
395
|
+
expect(result).not.toContain(' as ');
|
|
396
|
+
expect(result).not.toContain('latitude?:');
|
|
397
|
+
expect(result).toContain('props.uiField.value');
|
|
398
|
+
expect(result).toContain('.latitude');
|
|
399
|
+
assertValidJavaScript(result);
|
|
400
|
+
});
|
|
401
|
+
it('should handle pattern from schedulingPolicyObjectHome with as const', () => {
|
|
402
|
+
const code = `
|
|
403
|
+
class Test {
|
|
404
|
+
get config() {
|
|
405
|
+
return ({
|
|
406
|
+
apiVersion: "60.0",
|
|
407
|
+
scope: "LIST",
|
|
408
|
+
regionWidth: "LARGE"
|
|
409
|
+
} as const);
|
|
410
|
+
}
|
|
411
|
+
}`;
|
|
412
|
+
const result = sanitizeAndGenerate(code);
|
|
413
|
+
expect(result).not.toContain('as const');
|
|
414
|
+
expect(result).toContain('apiVersion: "60.0"');
|
|
415
|
+
assertValidJavaScript(result);
|
|
416
|
+
});
|
|
417
|
+
it('should handle pattern from analytics_share/shareModal with optional chaining', () => {
|
|
418
|
+
const code = `
|
|
419
|
+
class Test {
|
|
420
|
+
get platform() {
|
|
421
|
+
return (this.componentData as any)?.platform || 'Tableau';
|
|
422
|
+
}
|
|
423
|
+
}`;
|
|
424
|
+
const result = sanitizeAndGenerate(code);
|
|
425
|
+
expect(result).not.toContain(' as any');
|
|
426
|
+
expect(result).toContain('props.componentData');
|
|
427
|
+
expect(result).toContain('?.platform');
|
|
428
|
+
assertValidJavaScript(result);
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
describe('Combined and nested TypeScript constructs', () => {
|
|
432
|
+
it('should handle multiple TypeScript features in one expression', () => {
|
|
433
|
+
const code = `
|
|
434
|
+
class Test {
|
|
435
|
+
get complex() {
|
|
436
|
+
const items: string[] = this.data!;
|
|
437
|
+
return items.map((item: any, index: number) => ({
|
|
438
|
+
...item,
|
|
439
|
+
id: (index as number)
|
|
440
|
+
} as const));
|
|
441
|
+
}
|
|
442
|
+
}`;
|
|
443
|
+
const result = sanitizeAndGenerate(code);
|
|
444
|
+
expect(result).not.toContain(': string[]');
|
|
445
|
+
expect(result).not.toContain('!');
|
|
446
|
+
expect(result).not.toContain(': any');
|
|
447
|
+
expect(result).not.toContain(': number');
|
|
448
|
+
expect(result).not.toContain(' as number');
|
|
449
|
+
expect(result).not.toContain('as const');
|
|
450
|
+
assertValidJavaScript(result);
|
|
451
|
+
});
|
|
452
|
+
it('should handle deeply nested TypeScript syntax', () => {
|
|
453
|
+
const code = `
|
|
454
|
+
class Test {
|
|
455
|
+
get nested() {
|
|
456
|
+
return (this.data! as Array<string>).map((x: string) => (x as any));
|
|
457
|
+
}
|
|
458
|
+
}`;
|
|
459
|
+
const result = sanitizeAndGenerate(code);
|
|
460
|
+
expect(result).not.toContain('!');
|
|
461
|
+
expect(result).not.toContain(' as Array');
|
|
462
|
+
expect(result).not.toContain(': string');
|
|
463
|
+
expect(result).not.toContain(' as any');
|
|
464
|
+
assertValidJavaScript(result);
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
describe('Edge cases', () => {
|
|
468
|
+
it('should handle return type annotation on arrow function', () => {
|
|
469
|
+
const code = `
|
|
470
|
+
class Test {
|
|
471
|
+
get transformed() {
|
|
472
|
+
return this.items.map((x: string): string => x.toUpperCase());
|
|
473
|
+
}
|
|
474
|
+
}`;
|
|
475
|
+
const result = sanitizeAndGenerate(code);
|
|
476
|
+
expect(result).not.toContain(': string');
|
|
477
|
+
expect(result).toContain('x => x.toUpperCase()');
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
});
|
|
481
|
+
//# sourceMappingURL=komaci-typescript-syntax.spec.js.map
|
|
@@ -112,11 +112,31 @@ function sanitizeFunction(nodePath, importMetadata, gqlMetadataMap) {
|
|
|
112
112
|
path.replaceWith(t.memberExpression(t.identifier(common_shared_1.PROPS), path.node.property)); // replace this.<var> with props.<var>
|
|
113
113
|
}
|
|
114
114
|
},
|
|
115
|
+
// TypeScript type assertion and annotation removal
|
|
115
116
|
TSNonNullExpression(path) {
|
|
116
|
-
//
|
|
117
|
-
// we need to remove the ! to make the code valid JavaScript
|
|
117
|
+
// Syntax: foo! - asserts foo is not null or undefined
|
|
118
118
|
path.replaceWith(path.node.expression);
|
|
119
119
|
},
|
|
120
|
+
TSAsExpression(path) {
|
|
121
|
+
// Syntax: (foo as string) - type assertion
|
|
122
|
+
path.replaceWith(path.node.expression);
|
|
123
|
+
},
|
|
124
|
+
TSTypeAssertion(path) {
|
|
125
|
+
// Syntax: (<string>foo) - angle-bracket type assertion
|
|
126
|
+
path.replaceWith(path.node.expression);
|
|
127
|
+
},
|
|
128
|
+
TSTypeAnnotation(path) {
|
|
129
|
+
// Syntax: const x: string or (param: string) - type annotations
|
|
130
|
+
path.remove();
|
|
131
|
+
},
|
|
132
|
+
TSTypeParameterInstantiation(path) {
|
|
133
|
+
// Syntax: Array<string>, foo<T>() - type parameter usage
|
|
134
|
+
path.remove();
|
|
135
|
+
},
|
|
136
|
+
TSTypeParameterDeclaration(path) {
|
|
137
|
+
// Syntax: function foo<T>(), class Bar<T> - type parameter declarations
|
|
138
|
+
path.remove();
|
|
139
|
+
},
|
|
120
140
|
TaggedTemplateExpression(path) {
|
|
121
141
|
const taggedTmplExpr = path.node;
|
|
122
142
|
const taggedTmplLiteral = taggedTmplExpr.quasi;
|
package/build/index.js
CHANGED
|
@@ -116,38 +116,36 @@ function processGeneratorInputAndState(input) {
|
|
|
116
116
|
else if (input.moduleInfo.type === 'bundle') {
|
|
117
117
|
// code to process a bundle
|
|
118
118
|
let filename = '', fileExt;
|
|
119
|
-
const
|
|
120
|
-
const
|
|
119
|
+
const scriptDocs = {}; // {'test.js' : { filename: 'test', komaciDoc: KomaciDoc1 }}
|
|
120
|
+
const htmlDocs = {};
|
|
121
121
|
let sourceFile;
|
|
122
|
-
for (const [
|
|
122
|
+
for (const [filePath, komaciDoc] of Object.entries(input.moduleInfo.files)) {
|
|
123
123
|
// Determine file type based on extension in file name.
|
|
124
|
-
const segments = (0, common_shared_1.getFilepathSegments)(
|
|
124
|
+
const segments = (0, common_shared_1.getFilepathSegments)(filePath);
|
|
125
125
|
filename = segments[0]; // the filename, up to but not including, the file separator character
|
|
126
126
|
fileExt = segments[segments.length - 1]; // filename extension, not including the separator
|
|
127
|
-
let filesObj = {};
|
|
128
|
-
// check for existing map entry in order to append to it
|
|
129
|
-
// to support multiple files per filetype in the future
|
|
130
|
-
if (filesMap[fileExt]) {
|
|
131
|
-
filesObj = filesMap[fileExt];
|
|
132
|
-
}
|
|
133
|
-
filesObj[filename] = komaciDoc;
|
|
134
127
|
if (fileExt == 'html') {
|
|
135
|
-
|
|
128
|
+
htmlDocs[filename] = komaciDoc;
|
|
136
129
|
}
|
|
137
130
|
else if (fileExt === 'js' || fileExt === 'ts') {
|
|
138
|
-
|
|
131
|
+
scriptDocs[filePath] = { filename, komaciDoc };
|
|
139
132
|
}
|
|
140
133
|
else {
|
|
141
134
|
throw new Error(types_1.ERROR_PREFIX + 'unsupported file extension: ' + fileExt);
|
|
142
135
|
}
|
|
143
136
|
}
|
|
144
137
|
const source = (0, componentAstProcessing_1.getComponentSrcJs)(input);
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
138
|
+
// Find the main component script file(s)
|
|
139
|
+
const mainComponentScriptFiles = Object.values(scriptDocs).filter(({ filename }) => filename === input.moduleInfo.name);
|
|
140
|
+
// For now we only support one main component script file.
|
|
141
|
+
if (mainComponentScriptFiles.length > 1) {
|
|
142
|
+
throw new Error(types_1.ERROR_PREFIX +
|
|
143
|
+
'multiple component script files with the same name found in bundle: ' +
|
|
144
|
+
input.moduleInfo.name);
|
|
145
|
+
}
|
|
146
|
+
const uniqueMainComponentScriptDoc = mainComponentScriptFiles[0]?.komaciDoc ?? {};
|
|
149
147
|
// eslint-disable-next-line prefer-const
|
|
150
|
-
sourceFile = (0, genericAstGeneration_1.generateResolvableModule)(input,
|
|
148
|
+
sourceFile = (0, genericAstGeneration_1.generateResolvableModule)(input, uniqueMainComponentScriptDoc, htmlDocs, source);
|
|
151
149
|
// Generate source from constructed AST.
|
|
152
150
|
const babelOutput = (0, generator_1.default)(sourceFile);
|
|
153
151
|
return babelOutput.code;
|