From be08af7336b4a3c185dbaed022030ff8a69a3c10 Mon Sep 17 00:00:00 2001 From: ShacharHarshuv Date: Fri, 13 Sep 2024 14:38:59 -0400 Subject: [PATCH 1/2] fix(plugin): support outputs without inline initialization Previously, this script would fail with a cryptic message (`cannot read property of undefined (reading includes)`) for input like: ```ts @Output() myOutput: EventEmitter; ``` The changes support that use case and: - Uses the initialization in the constructor to infer the type (and removes it) - Creates an initialization (`= output()`) if it doesn't exist --- .../__snapshots__/generator.spec.ts.snap | 7 ++++ .../convert-outputs/generator.spec.ts | 8 ++++ .../generators/convert-outputs/generator.ts | 40 +++++++++++++++++-- 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/libs/plugin/src/generators/convert-outputs/__snapshots__/generator.spec.ts.snap b/libs/plugin/src/generators/convert-outputs/__snapshots__/generator.spec.ts.snap index 54c21104..330ad167 100644 --- a/libs/plugin/src/generators/convert-outputs/__snapshots__/generator.spec.ts.snap +++ b/libs/plugin/src/generators/convert-outputs/__snapshots__/generator.spec.ts.snap @@ -28,6 +28,13 @@ export class MyCmp { withObservable = outputFromObservable(this.someObservable$); aliasOutput = output({ alias: 'withAlias' }); + + noInitializer = output(); + + initializedInConstructor = output(); + + constructor() { + } ngOnInit() { let imABoolean = false; diff --git a/libs/plugin/src/generators/convert-outputs/generator.spec.ts b/libs/plugin/src/generators/convert-outputs/generator.spec.ts index 247c8da5..c6de5407 100644 --- a/libs/plugin/src/generators/convert-outputs/generator.spec.ts +++ b/libs/plugin/src/generators/convert-outputs/generator.spec.ts @@ -55,6 +55,14 @@ export class MyCmp { @Output() withObservable = this.someObservable$; @Output('withAlias') aliasOutput = new EventEmitter(); + + @Output() noInitializer: EventEmitter; + + @Output() initializedInConstructor; + + constructor() { + this.initializedInConstructor = new EventEmitter(); + } ngOnInit() { let imABoolean = false; diff --git a/libs/plugin/src/generators/convert-outputs/generator.ts b/libs/plugin/src/generators/convert-outputs/generator.ts index e68c751b..28430faa 100644 --- a/libs/plugin/src/generators/convert-outputs/generator.ts +++ b/libs/plugin/src/generators/convert-outputs/generator.ts @@ -62,8 +62,25 @@ function getOutputInitializer( alias = decoratorArg.getText(); } + const initializerOrType = + initializer ?? (typeof currentType === 'string' ? currentType : undefined); + + if (!initializerOrType) { + logger.error( + `[ngxtension] Unable to find initializer or type for "${propertyName}"`, + ); + return exit(1); + } + // check if the initializer is not an EventEmitter -> means its an observable - if (!initializer.includes('EventEmitter')) { + if (!initializerOrType.includes('EventEmitter')) { + if (!initializer) { + logger.error( + `[ngxtension] Unable to find initializer for "${propertyName}"`, + ); + return exit(1); + } + // if the initializer is a Subject or BehaviorSubject if ( initializer.includes('Subject') || @@ -96,10 +113,11 @@ function getOutputInitializer( } } else { let type = ''; - if (initializer.includes('EventEmitter()')) { + if (initializerOrType.includes('EventEmitter()')) { // there is no type } else { - const genericTypeOnEmitter = initializer.match(/EventEmitter<(.+)>/); + const genericTypeOnEmitter = + initializerOrType.match(/EventEmitter<(.+)>/); if (genericTypeOnEmitter?.length) { type = genericTypeOnEmitter[1]; } @@ -242,7 +260,7 @@ export async function convertOutputsGenerator( if (Node.isPropertyDeclaration(node)) { const outputDecorator = node.getDecorator('Output'); if (outputDecorator) { - const { + let { name, isReadonly, docs, @@ -252,6 +270,20 @@ export async function convertOutputsGenerator( initializer, } = node.getStructure(); + if (!initializer) { + // look for constructor initializer + const constructor = targetClass.getConstructors()[0]; + if (constructor) { + const constructorInitializer = constructor + .getStatements() + .find((stmt) => stmt.getText().includes(`${name} =`)); + if (constructorInitializer) { + initializer = constructorInitializer.getText(); + } + constructorInitializer?.remove(); + } + } + const { needsOutputFromObservableImport, removeOnlyDecorator, From 68589635295c8679baa90279fd9b89f20c741ed4 Mon Sep 17 00:00:00 2001 From: ShacharHarshuv Date: Tue, 8 Oct 2024 15:18:07 -0400 Subject: [PATCH 2/2] Fix lint issue --- .../src/generators/convert-outputs/generator.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/libs/plugin/src/generators/convert-outputs/generator.ts b/libs/plugin/src/generators/convert-outputs/generator.ts index 28430faa..bbcafbb0 100644 --- a/libs/plugin/src/generators/convert-outputs/generator.ts +++ b/libs/plugin/src/generators/convert-outputs/generator.ts @@ -260,15 +260,9 @@ export async function convertOutputsGenerator( if (Node.isPropertyDeclaration(node)) { const outputDecorator = node.getDecorator('Output'); if (outputDecorator) { - let { - name, - isReadonly, - docs, - scope, - type, - hasOverrideKeyword, - initializer, - } = node.getStructure(); + const { name, isReadonly, docs, scope, type, hasOverrideKeyword } = + node.getStructure(); + let { initializer } = node.getStructure(); if (!initializer) { // look for constructor initializer