From 762c9ee50d6d173f73bc5a701d533081155b270f Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Wed, 4 Jan 2023 11:04:53 +0000 Subject: [PATCH] feat(angular): add isStandalone util to check for standalone decorators (#14129) --- .../src/utils/nx-devkit/ast-utils.spec.ts | 109 ++++++++++++++++++ .../angular/src/utils/nx-devkit/ast-utils.ts | 18 ++- 2 files changed, 126 insertions(+), 1 deletion(-) diff --git a/packages/angular/src/utils/nx-devkit/ast-utils.spec.ts b/packages/angular/src/utils/nx-devkit/ast-utils.spec.ts index c26ff6c147..f438acd2cf 100644 --- a/packages/angular/src/utils/nx-devkit/ast-utils.spec.ts +++ b/packages/angular/src/utils/nx-devkit/ast-utils.spec.ts @@ -3,6 +3,7 @@ import { addImportToDirective, addImportToModule, addImportToPipe, + isStandalone, } from './ast-utils'; import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; import { createSourceFile, ScriptTarget } from 'typescript'; @@ -151,4 +152,112 @@ describe('Angular AST Utils', () => { " `); }); + + it('should allow checking if a component is standalone and return true if so', () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(); + const pathToFile = `my.component.ts`; + const originalContents = `import { Component } from '@angular/core'; + + @Component({ + standalone: true + }) + export class MyComponent {} + `; + + tree.write(pathToFile, originalContents); + + const sourceText = tree.read(pathToFile, 'utf-8'); + const tsSourceFile = createSourceFile( + pathToFile, + sourceText, + ScriptTarget.Latest, + true + ); + + // ACT + // ASSERT + expect(isStandalone(tsSourceFile, 'Component')).toBeTruthy(); + }); + + it('should allow checking if a component is standalone and return false if not', () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(); + const pathToFile = `my.component.ts`; + const originalContents = `import { Component } from '@angular/core'; + + @Component({ + standalone: false + }) + export class MyComponent {} + `; + + tree.write(pathToFile, originalContents); + + const sourceText = tree.read(pathToFile, 'utf-8'); + const tsSourceFile = createSourceFile( + pathToFile, + sourceText, + ScriptTarget.Latest, + true + ); + + // ACT + // ASSERT + expect(isStandalone(tsSourceFile, 'Component')).not.toBeTruthy(); + }); + + it('should allow checking if a directive is standalone and return true if so', () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(); + const pathToFile = `my.directive.ts`; + const originalContents = `import { Directive } from '@angular/core'; + + @Directive({ + standalone: true + }) + export class MyDirective {} + `; + + tree.write(pathToFile, originalContents); + + const sourceText = tree.read(pathToFile, 'utf-8'); + const tsSourceFile = createSourceFile( + pathToFile, + sourceText, + ScriptTarget.Latest, + true + ); + + // ACT + // ASSERT + expect(isStandalone(tsSourceFile, 'Directive')).toBeTruthy(); + }); + + it('should allow checking if a pipe is standalone and return true if so', () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(); + const pathToFile = `my.pipe.ts`; + const originalContents = `import { Pipe } from '@angular/core'; + + @Pipe({ + standalone: true + }) + export class MyPipe {} + `; + + tree.write(pathToFile, originalContents); + + const sourceText = tree.read(pathToFile, 'utf-8'); + const tsSourceFile = createSourceFile( + pathToFile, + sourceText, + ScriptTarget.Latest, + true + ); + + // ACT + // ASSERT + expect(isStandalone(tsSourceFile, 'Pipe')).toBeTruthy(); + }); }); diff --git a/packages/angular/src/utils/nx-devkit/ast-utils.ts b/packages/angular/src/utils/nx-devkit/ast-utils.ts index 079cda7a4c..9e4dc05a79 100644 --- a/packages/angular/src/utils/nx-devkit/ast-utils.ts +++ b/packages/angular/src/utils/nx-devkit/ast-utils.ts @@ -10,6 +10,8 @@ import { replaceChange, } from '@nrwl/workspace/src/utilities/ast-utils'; +type DecoratorName = 'Component' | 'Directive' | 'NgModule' | 'Pipe'; + function _angularImportsFromNode( node: ts.ImportDeclaration, _sourceFile: ts.SourceFile @@ -62,6 +64,20 @@ function _angularImportsFromNode( } } +export function isStandalone( + sourceFile: ts.SourceFile, + decoratorName: DecoratorName +) { + const decoratorMetadata = getDecoratorMetadata( + sourceFile, + decoratorName, + '@angular/core' + ); + return decoratorMetadata.some((node) => + node.getText().includes('standalone: true') + ); +} + export function getDecoratorMetadata( source: ts.SourceFile, identifier: string, @@ -134,7 +150,7 @@ function _addSymbolToDecoratorMetadata( filePath: string, metadataField: string, expression: string, - decoratorName: 'Component' | 'Directive' | 'NgModule' | 'Pipe' + decoratorName: DecoratorName ): ts.SourceFile { const nodes = getDecoratorMetadata(source, decoratorName, '@angular/core'); let node: any = nodes[0]; // tslint:disable-line:no-any