@komaci/esm-generator 262.18.0 → 262.20.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.
- package/build/__tests__/adgAstGeneration.spec.js +1 -1
- package/build/__tests__/functionAstGeneration.spec.js +2 -6
- package/build/__tests__/index.spec.js +106 -6
- package/build/componentAstProcessing.d.ts +2 -1
- package/build/componentAstProcessing.js +4 -2
- package/build/functionAstGeneration.js +0 -25
- package/build/index.js +17 -4
- package/package.json +1 -1
- package/build/__tests__/komaci-typescript-syntax.spec.d.ts +0 -2
- package/build/__tests__/komaci-typescript-syntax.spec.js +0 -481
|
@@ -363,7 +363,7 @@ export default class Component extends LightningElement {
|
|
|
363
363
|
// Test that bundles with .ts files are processed correctly
|
|
364
364
|
expect(() => {
|
|
365
365
|
const bundleConfig = {
|
|
366
|
-
namespace: '
|
|
366
|
+
namespace: 'lightning',
|
|
367
367
|
name: 'tsComponent',
|
|
368
368
|
type: 'internal',
|
|
369
369
|
namespaceMapping: {},
|
|
@@ -328,14 +328,10 @@ describe('sanitizeFunction', () => {
|
|
|
328
328
|
(0, functionAstGeneration_1.sanitizeFunction)(getters[0], imports, new Map());
|
|
329
329
|
const hoisted = (0, types_1.functionDeclaration)((0, types_1.identifier)('g0'), [(0, types_1.identifier)('props')], getters[0].node.body);
|
|
330
330
|
const generatedFunction = (0, generator_1.default)((0, types_1.file)((0, types_1.program)([hoisted])));
|
|
331
|
-
|
|
332
|
-
// Should generate: i0 && props.isVisible
|
|
333
|
-
// Should NOT generate: i0! && props.isVisible
|
|
334
|
-
expect(generatedFunction.code).not.toContain('!');
|
|
335
|
-
expect(generatedFunction.code).toContain('i0 && props.isVisible');
|
|
331
|
+
expect(generatedFunction.code).toContain('i0! && props.isVisible');
|
|
336
332
|
expect(generatedFunction.code).toMatchInlineSnapshot(`
|
|
337
333
|
"function g0(props) {
|
|
338
|
-
return i0 && props.isVisible;
|
|
334
|
+
return i0! && props.isVisible;
|
|
339
335
|
}"
|
|
340
336
|
`);
|
|
341
337
|
});
|
|
@@ -301,7 +301,7 @@ describe('Bundle processing', () => {
|
|
|
301
301
|
const input = {
|
|
302
302
|
moduleInfo: {
|
|
303
303
|
name: 'test',
|
|
304
|
-
namespace: '
|
|
304
|
+
namespace: 'lightning',
|
|
305
305
|
type: 'bundle',
|
|
306
306
|
files: bundleSrcFileMap,
|
|
307
307
|
},
|
|
@@ -319,7 +319,7 @@ describe('Bundle processing', () => {
|
|
|
319
319
|
it('can handle multiple script files with the same name in different directories', () => {
|
|
320
320
|
const bundleSrcFileMap = {
|
|
321
321
|
'test.js': {},
|
|
322
|
-
'subdir/test.
|
|
322
|
+
'subdir/test.js': {},
|
|
323
323
|
'test.html': {},
|
|
324
324
|
};
|
|
325
325
|
const input = {
|
|
@@ -331,7 +331,7 @@ describe('Bundle processing', () => {
|
|
|
331
331
|
},
|
|
332
332
|
srcFileMap: {
|
|
333
333
|
'test.js': 'export default class Test {}',
|
|
334
|
-
'subdir/test.
|
|
334
|
+
'subdir/test.js': 'export default class Test {}',
|
|
335
335
|
'test.html': '<template><div>Test</div></template>',
|
|
336
336
|
'test.css': 'p { text-align: center; }',
|
|
337
337
|
},
|
|
@@ -346,7 +346,7 @@ describe('Bundle processing', () => {
|
|
|
346
346
|
return 'test';
|
|
347
347
|
}
|
|
348
348
|
}`;
|
|
349
|
-
const
|
|
349
|
+
const fooJsSource = `export default class Foo {
|
|
350
350
|
get fooProp() {
|
|
351
351
|
return 'foo';
|
|
352
352
|
}
|
|
@@ -357,7 +357,7 @@ describe('Bundle processing', () => {
|
|
|
357
357
|
type: 'internal',
|
|
358
358
|
files: [
|
|
359
359
|
{ fileName: 'test.js', source: testJsSource },
|
|
360
|
-
{ fileName: 'foo.
|
|
360
|
+
{ fileName: 'foo.js', source: fooJsSource },
|
|
361
361
|
{ fileName: 'test.html', source: '<template><div>Test</div></template>' },
|
|
362
362
|
{ fileName: 'test.css', source: 'p { text-align: center; }' },
|
|
363
363
|
],
|
|
@@ -378,7 +378,7 @@ describe('Bundle processing', () => {
|
|
|
378
378
|
},
|
|
379
379
|
srcFileMap: {
|
|
380
380
|
'test.js': testJsSource,
|
|
381
|
-
'foo.
|
|
381
|
+
'foo.js': fooJsSource,
|
|
382
382
|
'test.html': '<template><div>Test</div></template>',
|
|
383
383
|
'test.css': 'p { text-align: center; }',
|
|
384
384
|
},
|
|
@@ -389,5 +389,105 @@ describe('Bundle processing', () => {
|
|
|
389
389
|
expect(res).toContain('testProp');
|
|
390
390
|
expect(res).not.toContain('fooProp');
|
|
391
391
|
});
|
|
392
|
+
it('can process typescript files in the lightning namespace', () => {
|
|
393
|
+
const testTsSource = `import { LightningElement } from 'lwc';
|
|
394
|
+
|
|
395
|
+
export default class TestComponent extends LightningElement {
|
|
396
|
+
private count: number = 0;
|
|
397
|
+
|
|
398
|
+
get doubleCount(): number {
|
|
399
|
+
return this.count * 2;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
increment(): void {
|
|
403
|
+
this.count++;
|
|
404
|
+
}
|
|
405
|
+
}`;
|
|
406
|
+
const bundleConfig = {
|
|
407
|
+
namespace: 'lightning',
|
|
408
|
+
name: 'testComponent',
|
|
409
|
+
type: 'internal',
|
|
410
|
+
files: [
|
|
411
|
+
{ fileName: 'testComponent.ts', source: testTsSource },
|
|
412
|
+
{
|
|
413
|
+
fileName: 'testComponent.html',
|
|
414
|
+
source: '<template><div>Test</div></template>',
|
|
415
|
+
},
|
|
416
|
+
],
|
|
417
|
+
enableKomaci: true,
|
|
418
|
+
namespaceMapping: {},
|
|
419
|
+
npmModuleMapping: {},
|
|
420
|
+
};
|
|
421
|
+
const metadata = (0, metadata_1.collectBundleMetadata)(bundleConfig);
|
|
422
|
+
const inputFiles = Object.fromEntries(metadata.files
|
|
423
|
+
.map(({ fileName, komaciDoc }) => [fileName, komaciDoc])
|
|
424
|
+
.filter(([, komaciDoc]) => !!komaciDoc));
|
|
425
|
+
const input = {
|
|
426
|
+
moduleInfo: {
|
|
427
|
+
name: 'testComponent',
|
|
428
|
+
namespace: 'lightning',
|
|
429
|
+
type: 'bundle',
|
|
430
|
+
files: inputFiles,
|
|
431
|
+
},
|
|
432
|
+
srcFileMap: {
|
|
433
|
+
'testComponent.ts': testTsSource,
|
|
434
|
+
'testComponent.html': '<template><div>Test</div></template>',
|
|
435
|
+
},
|
|
436
|
+
};
|
|
437
|
+
const res = (0, __1.generateKomaciModule)(input);
|
|
438
|
+
expect(res).not.toContain('ErrorFunction');
|
|
439
|
+
expect(res).toContain('registerAdgFunction');
|
|
440
|
+
expect(res).toContain('doubleCount');
|
|
441
|
+
});
|
|
442
|
+
it('throws error for typescript files in non-lightning namespace', () => {
|
|
443
|
+
const testTsSource = `import { LightningElement } from 'lwc';
|
|
444
|
+
|
|
445
|
+
export default class TestComponent extends LightningElement {
|
|
446
|
+
private count: number = 0;
|
|
447
|
+
|
|
448
|
+
get doubleCount(): number {
|
|
449
|
+
return this.count * 2;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
increment(): void {
|
|
453
|
+
this.count++;
|
|
454
|
+
}
|
|
455
|
+
}`;
|
|
456
|
+
const bundleConfig = {
|
|
457
|
+
namespace: 'c',
|
|
458
|
+
name: 'testComponent',
|
|
459
|
+
type: 'internal',
|
|
460
|
+
files: [
|
|
461
|
+
{ fileName: 'testComponent.ts', source: testTsSource },
|
|
462
|
+
{
|
|
463
|
+
fileName: 'testComponent.html',
|
|
464
|
+
source: '<template><div>Test</div></template>',
|
|
465
|
+
},
|
|
466
|
+
],
|
|
467
|
+
enableKomaci: true,
|
|
468
|
+
namespaceMapping: {},
|
|
469
|
+
npmModuleMapping: {},
|
|
470
|
+
};
|
|
471
|
+
const metadata = (0, metadata_1.collectBundleMetadata)(bundleConfig);
|
|
472
|
+
const inputFiles = Object.fromEntries(metadata.files
|
|
473
|
+
.map(({ fileName, komaciDoc }) => [fileName, komaciDoc])
|
|
474
|
+
.filter(([, komaciDoc]) => !!komaciDoc));
|
|
475
|
+
const input = {
|
|
476
|
+
moduleInfo: {
|
|
477
|
+
name: 'testComponent',
|
|
478
|
+
namespace: 'c',
|
|
479
|
+
type: 'bundle',
|
|
480
|
+
files: inputFiles,
|
|
481
|
+
},
|
|
482
|
+
srcFileMap: {
|
|
483
|
+
'testComponent.ts': testTsSource,
|
|
484
|
+
'testComponent.html': '<template><div>Test</div></template>',
|
|
485
|
+
},
|
|
486
|
+
};
|
|
487
|
+
const res = (0, __1.generateKomaciModule)(input);
|
|
488
|
+
// TypeScript files in non-lightning namespaces should now throw an error
|
|
489
|
+
expect(res).toContain('ErrorFunction');
|
|
490
|
+
expect(res).toContain('unsupported file extension: ts');
|
|
491
|
+
});
|
|
392
492
|
});
|
|
393
493
|
//# sourceMappingURL=index.spec.js.map
|
|
@@ -5,11 +5,12 @@ import { ModuleContext, ModuleMetadata, HoistedFunctionMetadata } from '@komaci/
|
|
|
5
5
|
/**
|
|
6
6
|
* Retrieve the file path for the js of a component
|
|
7
7
|
* @param generatorState GeneratorState containg the GeneratorInput, which in turn contains the JS raw source file
|
|
8
|
+
* @param allowTypeScript Optional parameter to control whether TypeScript files should be considered. Defaults to true for backward compatibility.
|
|
8
9
|
* @returns Returns raw source code as string for the js of a component if it exists in the srcFileMap
|
|
9
10
|
* Returns undefined if no JS source file (whose filename also matches the LWC module name) was included in the
|
|
10
11
|
* GeneratorState.GeneratorInput by lwc-platform, other calling code.
|
|
11
12
|
*/
|
|
12
|
-
export declare function getComponentSrcJs(generatorInput: GeneratorInput): string | undefined;
|
|
13
|
+
export declare function getComponentSrcJs(generatorInput: GeneratorInput, allowTypeScript?: boolean): string | undefined;
|
|
13
14
|
/**
|
|
14
15
|
* AST is traversed to see if they contain getter functions that are not valid. For each getter that is valid, we will
|
|
15
16
|
* update contextual information about the class and pass back info about the valid getters.
|
|
@@ -7,15 +7,17 @@ const common_shared_1 = require("@komaci/common-shared");
|
|
|
7
7
|
/**
|
|
8
8
|
* Retrieve the file path for the js of a component
|
|
9
9
|
* @param generatorState GeneratorState containg the GeneratorInput, which in turn contains the JS raw source file
|
|
10
|
+
* @param allowTypeScript Optional parameter to control whether TypeScript files should be considered. Defaults to true for backward compatibility.
|
|
10
11
|
* @returns Returns raw source code as string for the js of a component if it exists in the srcFileMap
|
|
11
12
|
* Returns undefined if no JS source file (whose filename also matches the LWC module name) was included in the
|
|
12
13
|
* GeneratorState.GeneratorInput by lwc-platform, other calling code.
|
|
13
14
|
*/
|
|
14
|
-
function getComponentSrcJs(generatorInput) {
|
|
15
|
+
function getComponentSrcJs(generatorInput, allowTypeScript = true) {
|
|
15
16
|
let sourceCode;
|
|
16
17
|
const moduleName = generatorInput.moduleInfo.name;
|
|
17
18
|
if (generatorInput.srcFileMap) {
|
|
18
|
-
const filename = Object.keys(generatorInput.srcFileMap).find((filename) => (filename.endsWith('.js') ||
|
|
19
|
+
const filename = Object.keys(generatorInput.srcFileMap).find((filename) => (filename.endsWith('.js') ||
|
|
20
|
+
(filename.endsWith('.ts') && allowTypeScript)) &&
|
|
19
21
|
filename.startsWith(moduleName));
|
|
20
22
|
if (filename) {
|
|
21
23
|
sourceCode = generatorInput.srcFileMap[filename];
|
|
@@ -112,31 +112,6 @@ 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
|
|
116
|
-
TSNonNullExpression(path) {
|
|
117
|
-
// Syntax: foo! - asserts foo is not null or undefined
|
|
118
|
-
path.replaceWith(path.node.expression);
|
|
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
|
-
},
|
|
140
115
|
TaggedTemplateExpression(path) {
|
|
141
116
|
const taggedTmplExpr = path.node;
|
|
142
117
|
const taggedTmplLiteral = taggedTmplExpr.quasi;
|
package/build/index.js
CHANGED
|
@@ -86,6 +86,15 @@ function normalizeSrcFileKeys(input) {
|
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
exports.normalizeSrcFileKeys = normalizeSrcFileKeys;
|
|
89
|
+
/**
|
|
90
|
+
* Determines if TypeScript files should be processed for the given namespace.
|
|
91
|
+
* TypeScript files are only processed for lightning and lightning/interop namespaces.
|
|
92
|
+
* @param namespace the module namespace
|
|
93
|
+
* @returns true if TypeScript files should be processed
|
|
94
|
+
*/
|
|
95
|
+
function shouldProcessTypeScriptForNamespace(namespace) {
|
|
96
|
+
return namespace === 'lightning' || namespace.startsWith('lightning/interop');
|
|
97
|
+
}
|
|
89
98
|
/**
|
|
90
99
|
* Helper function to process and build the resolvable module based off the generator input and inital generator state.
|
|
91
100
|
* @param input the Generator input
|
|
@@ -97,14 +106,16 @@ function processGeneratorInputAndState(input) {
|
|
|
97
106
|
// Determine file type based on extension in file name.
|
|
98
107
|
const segments = (0, common_shared_1.getFilepathSegments)(input.moduleInfo.fileName);
|
|
99
108
|
const fileExt = segments[segments.length - 1];
|
|
109
|
+
const namespace = input.moduleInfo.namespace;
|
|
110
|
+
const isTypeScriptAllowed = shouldProcessTypeScriptForNamespace(namespace);
|
|
100
111
|
let sourceFile;
|
|
101
|
-
const source = (0, componentAstProcessing_1.getComponentSrcJs)(input);
|
|
112
|
+
const source = (0, componentAstProcessing_1.getComponentSrcJs)(input, isTypeScriptAllowed);
|
|
102
113
|
if (fileExt === 'html') {
|
|
103
114
|
const templateMap = {};
|
|
104
115
|
templateMap[input.moduleInfo.name] = input.moduleInfo.komaciDoc;
|
|
105
116
|
sourceFile = (0, genericAstGeneration_1.generateResolvableModule)(input, undefined, templateMap, source);
|
|
106
117
|
}
|
|
107
|
-
else if (fileExt === 'js' || fileExt === 'ts') {
|
|
118
|
+
else if (fileExt === 'js' || (fileExt === 'ts' && isTypeScriptAllowed)) {
|
|
108
119
|
sourceFile = (0, genericAstGeneration_1.generateResolvableModule)(input, input.moduleInfo.komaciDoc, undefined, source);
|
|
109
120
|
}
|
|
110
121
|
else {
|
|
@@ -116,6 +127,8 @@ function processGeneratorInputAndState(input) {
|
|
|
116
127
|
else if (input.moduleInfo.type === 'bundle') {
|
|
117
128
|
// code to process a bundle
|
|
118
129
|
let filename = '', fileExt;
|
|
130
|
+
const namespace = input.moduleInfo.namespace;
|
|
131
|
+
const isTypeScriptAllowed = shouldProcessTypeScriptForNamespace(namespace);
|
|
119
132
|
const scriptDocs = {}; // {'test.js' : { filename: 'test', komaciDoc: KomaciDoc1 }}
|
|
120
133
|
const htmlDocs = {};
|
|
121
134
|
let sourceFile;
|
|
@@ -127,14 +140,14 @@ function processGeneratorInputAndState(input) {
|
|
|
127
140
|
if (fileExt == 'html') {
|
|
128
141
|
htmlDocs[filename] = komaciDoc;
|
|
129
142
|
}
|
|
130
|
-
else if (fileExt === 'js' || fileExt === 'ts') {
|
|
143
|
+
else if (fileExt === 'js' || (fileExt === 'ts' && isTypeScriptAllowed)) {
|
|
131
144
|
scriptDocs[filePath] = { filename, komaciDoc };
|
|
132
145
|
}
|
|
133
146
|
else {
|
|
134
147
|
throw new Error(types_1.ERROR_PREFIX + 'unsupported file extension: ' + fileExt);
|
|
135
148
|
}
|
|
136
149
|
}
|
|
137
|
-
const source = (0, componentAstProcessing_1.getComponentSrcJs)(input);
|
|
150
|
+
const source = (0, componentAstProcessing_1.getComponentSrcJs)(input, isTypeScriptAllowed);
|
|
138
151
|
// Find the main component script file(s)
|
|
139
152
|
const mainComponentScriptFiles = Object.values(scriptDocs).filter(({ filename }) => filename === input.moduleInfo.name);
|
|
140
153
|
// For now we only support one main component script file.
|
package/package.json
CHANGED
|
@@ -1,481 +0,0 @@
|
|
|
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
|