diff --git a/src/compiler/transformers/add-component-meta-proxy.ts b/src/compiler/transformers/add-component-meta-proxy.ts index f20e51db071..4f32cf49e66 100644 --- a/src/compiler/transformers/add-component-meta-proxy.ts +++ b/src/compiler/transformers/add-component-meta-proxy.ts @@ -47,24 +47,27 @@ export const createComponentMetadataProxy = (compilerMeta: d.ComponentCompilerMe }; /** - * Create a call expression for wrapping a component represented as an anonymous class in a proxy. This call expression - * takes a form: + * Create a call expression for wrapping a component represented as a class + * expression in a proxy. This call expression takes the form: + * * ```ts * PROXY_CUSTOM_ELEMENT(Clazz, Metadata); * ``` + * * where - * - `PROXY_CUSTOM_ELEMENT` is a Stencil internal identifier that will be replaced with the name of the actual function - * name at compile name - * - `Clazz` is an anonymous class to be proxied + * - `PROXY_CUSTOM_ELEMENT` is a Stencil internal identifier that will be + * replaced with the name of the actual function name at compile name + * - `Clazz` is a class expression to be proxied * - `Metadata` is the compiler metadata associated with the Stencil component * - * @param compilerMeta compiler metadata associated with the component to be wrapped in a proxy - * @param clazz the anonymous class to proxy + * @param compilerMeta compiler metadata associated with the component to be + * wrapped in a proxy + * @param clazz the class expression to proxy * @returns the generated call expression */ -export const createAnonymousClassMetadataProxy = ( +export const createClassMetadataProxy = ( compilerMeta: d.ComponentCompilerMeta, - clazz: ts.Expression + clazz: ts.ClassExpression ): ts.CallExpression => { const compactMeta: d.ComponentRuntimeMetaCompact = formatComponentRuntimeMeta(compilerMeta, true); const literalMeta = convertValueToLiteral(compactMeta); diff --git a/src/compiler/transformers/component-native/proxy-custom-element-function.ts b/src/compiler/transformers/component-native/proxy-custom-element-function.ts index e97a187a9ec..26b12de5a89 100644 --- a/src/compiler/transformers/component-native/proxy-custom-element-function.ts +++ b/src/compiler/transformers/component-native/proxy-custom-element-function.ts @@ -1,7 +1,7 @@ import ts from 'typescript'; import type * as d from '../../../declarations'; -import { createAnonymousClassMetadataProxy } from '../add-component-meta-proxy'; +import { createClassMetadataProxy } from '../add-component-meta-proxy'; import { addImports } from '../add-imports'; import { RUNTIME_APIS } from '../core-runtime-apis'; import { getModuleFromSourceFile } from '../transform-utils'; @@ -47,8 +47,22 @@ export const proxyCustomElement = ( continue; } + // to narrow the type of `declaration.initializer` to `ts.ClassExpression` + if (!ts.isClassExpression(declaration.initializer)) { + continue; + } + + const renamedClassExpression = ts.factory.updateClassExpression( + declaration.initializer, + ts.getModifiers(declaration.initializer), + ts.factory.createIdentifier(principalComponent.componentClassName), + declaration.initializer.typeParameters, + declaration.initializer.heritageClauses, + declaration.initializer.members + ); + // wrap the Stencil component's class declaration in a component proxy - const proxyCreationCall = createAnonymousClassMetadataProxy(principalComponent, declaration.initializer); + const proxyCreationCall = createClassMetadataProxy(principalComponent, renamedClassExpression); ts.addSyntheticLeadingComment(proxyCreationCall, ts.SyntaxKind.MultiLineCommentTrivia, '@__PURE__', false); // update the component's variable declaration to use the new initializer diff --git a/src/compiler/transformers/test/add-component-meta-proxy.spec.ts b/src/compiler/transformers/test/add-component-meta-proxy.spec.ts index 1a4c032eb5b..8f6228456c0 100644 --- a/src/compiler/transformers/test/add-component-meta-proxy.spec.ts +++ b/src/compiler/transformers/test/add-component-meta-proxy.spec.ts @@ -3,12 +3,12 @@ import ts from 'typescript'; import { stubComponentCompilerMeta } from '../../../compiler/types/tests/ComponentCompilerMeta.stub'; import type * as d from '../../../declarations'; import * as FormatComponentRuntimeMeta from '../../../utils/format-component-runtime-meta'; -import { createAnonymousClassMetadataProxy } from '../add-component-meta-proxy'; +import { createClassMetadataProxy } from '../add-component-meta-proxy'; import { HTML_ELEMENT } from '../core-runtime-apis'; import * as TransformUtils from '../transform-utils'; describe('add-component-meta-proxy', () => { - describe('createAnonymousClassMetadataProxy()', () => { + describe('createClassMetadataProxy()', () => { let classExpr: ts.ClassExpression; let htmlElementHeritageClause: ts.HeritageClause; let literalMetadata: ts.StringLiteral; @@ -51,25 +51,25 @@ describe('add-component-meta-proxy', () => { }); it('returns a call expression', () => { - const result: ts.CallExpression = createAnonymousClassMetadataProxy(stubComponentCompilerMeta(), classExpr); + const result: ts.CallExpression = createClassMetadataProxy(stubComponentCompilerMeta(), classExpr); expect(ts.isCallExpression(result)).toBe(true); }); it('wraps the initializer in PROXY_CUSTOM_ELEMENT', () => { - const result: ts.CallExpression = createAnonymousClassMetadataProxy(stubComponentCompilerMeta(), classExpr); + const result: ts.CallExpression = createClassMetadataProxy(stubComponentCompilerMeta(), classExpr); expect((result.expression as ts.Identifier).escapedText).toBe('___stencil_proxyCustomElement'); }); it("doesn't add any type arguments to the call", () => { - const result: ts.CallExpression = createAnonymousClassMetadataProxy(stubComponentCompilerMeta(), classExpr); + const result: ts.CallExpression = createClassMetadataProxy(stubComponentCompilerMeta(), classExpr); expect(result.typeArguments).toHaveLength(0); }); it('adds the correct arguments to the PROXY_CUSTOM_ELEMENT call', () => { - const result: ts.CallExpression = createAnonymousClassMetadataProxy(stubComponentCompilerMeta(), classExpr); + const result: ts.CallExpression = createClassMetadataProxy(stubComponentCompilerMeta(), classExpr); expect(result.arguments).toHaveLength(2); expect(result.arguments[0]).toBe(classExpr); @@ -77,7 +77,7 @@ describe('add-component-meta-proxy', () => { }); it('includes the heritage clause', () => { - const result: ts.CallExpression = createAnonymousClassMetadataProxy(stubComponentCompilerMeta(), classExpr); + const result: ts.CallExpression = createClassMetadataProxy(stubComponentCompilerMeta(), classExpr); expect(result.arguments.length).toBeGreaterThanOrEqual(1); const createdClassExpression = result.arguments[0]; diff --git a/src/compiler/transformers/test/proxy-custom-element-function.spec.ts b/src/compiler/transformers/test/proxy-custom-element-function.spec.ts index 62681482a2b..f6ff2bc62d5 100644 --- a/src/compiler/transformers/test/proxy-custom-element-function.spec.ts +++ b/src/compiler/transformers/test/proxy-custom-element-function.spec.ts @@ -17,9 +17,9 @@ describe('proxy-custom-element-function', () => { ReturnType, Parameters >; - let createAnonymousClassMetadataProxySpy: jest.SpyInstance< - ReturnType, - Parameters + let createClassMetadataProxySpy: jest.SpyInstance< + ReturnType, + Parameters >; beforeEach(() => { @@ -47,20 +47,19 @@ describe('proxy-custom-element-function', () => { } as d.Module; }); - createAnonymousClassMetadataProxySpy = jest.spyOn(AddComponentMetaProxy, 'createAnonymousClassMetadataProxy'); - createAnonymousClassMetadataProxySpy.mockImplementation( - (_compilerMeta: d.ComponentCompilerMeta, clazz: ts.Expression) => - ts.factory.createCallExpression( - ts.factory.createIdentifier(PROXY_CUSTOM_ELEMENT), - [], - [clazz, ts.factory.createTrue()] - ) + createClassMetadataProxySpy = jest.spyOn(AddComponentMetaProxy, 'createClassMetadataProxy'); + createClassMetadataProxySpy.mockImplementation((_compilerMeta: d.ComponentCompilerMeta, clazz: ts.Expression) => + ts.factory.createCallExpression( + ts.factory.createIdentifier(PROXY_CUSTOM_ELEMENT), + [], + [clazz, ts.factory.createTrue()] + ) ); }); afterEach(() => { getModuleFromSourceFileSpy.mockRestore(); - createAnonymousClassMetadataProxySpy.mockRestore(); + createClassMetadataProxySpy.mockRestore(); }); describe('proxyCustomElement()', () => { @@ -82,7 +81,7 @@ describe('proxy-custom-element-function', () => { const transpiledModule = transpileModule(code, null, compilerCtx, [], [transformer]); expect(transpiledModule.outputText).toContain( - `export const ${componentClassName} = /*@__PURE__*/ __stencil_proxyCustomElement(class extends HTMLElement {}, true);` + `export const ${componentClassName} = /*@__PURE__*/ __stencil_proxyCustomElement(class ${componentClassName} extends HTMLElement {}, true);` ); }); @@ -94,7 +93,7 @@ describe('proxy-custom-element-function', () => { const transpiledModule = transpileModule(code, null, compilerCtx, [], [transformer]); expect(transpiledModule.outputText).toContain( - `export const foo = 'hello world!', ${componentClassName} = /*@__PURE__*/ __stencil_proxyCustomElement(class extends HTMLElement {}, true);` + `export const foo = 'hello world!', ${componentClassName} = /*@__PURE__*/ __stencil_proxyCustomElement(class ${componentClassName} extends HTMLElement {}, true);` ); }); @@ -105,18 +104,18 @@ describe('proxy-custom-element-function', () => { const transpiledModule = transpileModule(code, null, compilerCtx, [], [transformer]); expect(transpiledModule.outputText).toContain( - `export const ${componentClassName} = /*@__PURE__*/ __stencil_proxyCustomElement(class extends HTMLElement {}, true), foo = 'hello world!';` + `export const ${componentClassName} = /*@__PURE__*/ __stencil_proxyCustomElement(class ${componentClassName} extends HTMLElement {}, true), foo = 'hello world!';` ); }); it('wraps a class initializer properly in the middle of multiple variable declarations', () => { - const code = `const foo = 'hello world!', ${componentClassName} = class extends HTMLElement {}, bar = 'goodbye?'`; + const code = `const foo = 'hello world!', ${componentClassName} = class ${componentClassName} extends HTMLElement {}, bar = 'goodbye?'`; const transformer = proxyCustomElement(compilerCtx, transformOpts); const transpiledModule = transpileModule(code, null, compilerCtx, [], [transformer]); expect(transpiledModule.outputText).toContain( - `export const foo = 'hello world!', ${componentClassName} = /*@__PURE__*/ __stencil_proxyCustomElement(class extends HTMLElement {}, true), bar = 'goodbye?';` + `export const foo = 'hello world!', ${componentClassName} = /*@__PURE__*/ __stencil_proxyCustomElement(class ${componentClassName} extends HTMLElement {}, true), bar = 'goodbye?';` ); }); });