feat(angular): add ast utils for standalone decorators (#14108)

This commit is contained in:
Colum Ferry 2023-01-03 15:05:41 +00:00 committed by GitHub
parent 666e057a50
commit 64d7ceb447
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 226 additions and 6 deletions

View File

@ -0,0 +1,154 @@
import {
addImportToComponent,
addImportToDirective,
addImportToModule,
addImportToPipe,
} from './ast-utils';
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import { createSourceFile, ScriptTarget } from 'typescript';
describe('Angular AST Utils', () => {
it('should correctly add the imported symbol to the NgModule', () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace();
const pathToModule = `my.module.ts`;
const originalContents = `import { NgModule } from '@angular/core';
@NgModule({})
export class MyModule {}
`;
tree.write(pathToModule, originalContents);
const symbolToAdd = `CommonModule`;
const sourceText = tree.read(pathToModule, 'utf-8');
const tsSourceFile = createSourceFile(
pathToModule,
sourceText,
ScriptTarget.Latest,
true
);
// ACT
addImportToModule(tree, tsSourceFile, pathToModule, symbolToAdd);
// ASSERT
expect(tree.read(pathToModule, 'utf-8')).toMatchInlineSnapshot(`
"import { NgModule } from '@angular/core';
@NgModule({ imports: [CommonModule]
})
export class MyModule {}
"
`);
});
it('should correctly add the imported symbol to the Component', () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace();
const pathToFile = `my.component.ts`;
const originalContents = `import { Component } from '@angular/core';
@Component({})
export class MyComponent {}
`;
tree.write(pathToFile, originalContents);
const symbolToAdd = `CommonModule`;
const sourceText = tree.read(pathToFile, 'utf-8');
const tsSourceFile = createSourceFile(
pathToFile,
sourceText,
ScriptTarget.Latest,
true
);
// ACT
addImportToComponent(tree, tsSourceFile, pathToFile, symbolToAdd);
// ASSERT
expect(tree.read(pathToFile, 'utf-8')).toMatchInlineSnapshot(`
"import { Component } from '@angular/core';
@Component({ imports: [CommonModule]
})
export class MyComponent {}
"
`);
});
it('should correctly add the imported symbol to the Directive', () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace();
const pathToFile = `my.directive.ts`;
const originalContents = `import { Directive } from '@angular/core';
@Directive({})
export class MyDirective {}
`;
tree.write(pathToFile, originalContents);
const symbolToAdd = `CommonModule`;
const sourceText = tree.read(pathToFile, 'utf-8');
const tsSourceFile = createSourceFile(
pathToFile,
sourceText,
ScriptTarget.Latest,
true
);
// ACT
addImportToDirective(tree, tsSourceFile, pathToFile, symbolToAdd);
// ASSERT
expect(tree.read(pathToFile, 'utf-8')).toMatchInlineSnapshot(`
"import { Directive } from '@angular/core';
@Directive({ imports: [CommonModule]
})
export class MyDirective {}
"
`);
});
it('should correctly add the imported symbol to the Pipe', () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace();
const pathToFile = `my.pipe.ts`;
const originalContents = `import { Pipe } from '@angular/core';
@Pipe({})
export class MyPipe {}
`;
tree.write(pathToFile, originalContents);
const symbolToAdd = `CommonModule`;
const sourceText = tree.read(pathToFile, 'utf-8');
const tsSourceFile = createSourceFile(
pathToFile,
sourceText,
ScriptTarget.Latest,
true
);
// ACT
addImportToPipe(tree, tsSourceFile, pathToFile, symbolToAdd);
// ASSERT
expect(tree.read(pathToFile, 'utf-8')).toMatchInlineSnapshot(`
"import { Pipe } from '@angular/core';
@Pipe({ imports: [CommonModule]
})
export class MyPipe {}
"
`);
});
});

View File

@ -128,14 +128,15 @@ export function getDecoratorMetadata(
.map((expr) => expr.arguments[0] as ts.ObjectLiteralExpression);
}
function _addSymbolToNgModuleMetadata(
function _addSymbolToDecoratorMetadata(
host: Tree,
source: ts.SourceFile,
ngModulePath: string,
filePath: string,
metadataField: string,
expression: string
expression: string,
decoratorName: 'Component' | 'Directive' | 'NgModule' | 'Pipe'
): ts.SourceFile {
const nodes = getDecoratorMetadata(source, 'NgModule', '@angular/core');
const nodes = getDecoratorMetadata(source, decoratorName, '@angular/core');
let node: any = nodes[0]; // tslint:disable-line:no-any
// Find the decorator declaration.
@ -187,7 +188,7 @@ function _addSymbolToNgModuleMetadata(
}
}
return insertChange(host, source, ngModulePath, position, toInsert);
return insertChange(host, source, filePath, position, toInsert);
}
const assignment = matchingProperties[0] as ts.PropertyAssignment;
@ -259,7 +260,24 @@ function _addSymbolToNgModuleMetadata(
toInsert = `, ${expression}`;
}
}
return insertChange(host, source, ngModulePath, position, toInsert);
return insertChange(host, source, filePath, position, toInsert);
}
function _addSymbolToNgModuleMetadata(
host: Tree,
source: ts.SourceFile,
ngModulePath: string,
metadataField: string,
expression: string
): ts.SourceFile {
return _addSymbolToDecoratorMetadata(
host,
source,
ngModulePath,
metadataField,
expression,
'NgModule'
);
}
export function removeFromNgModule(
@ -294,6 +312,54 @@ export function removeFromNgModule(
}
}
export function addImportToComponent(
host: Tree,
source: ts.SourceFile,
componentPath: string,
symbolName: string
): ts.SourceFile {
return _addSymbolToDecoratorMetadata(
host,
source,
componentPath,
'imports',
symbolName,
'Component'
);
}
export function addImportToDirective(
host: Tree,
source: ts.SourceFile,
directivePath: string,
symbolName: string
): ts.SourceFile {
return _addSymbolToDecoratorMetadata(
host,
source,
directivePath,
'imports',
symbolName,
'Directive'
);
}
export function addImportToPipe(
host: Tree,
source: ts.SourceFile,
pipePath: string,
symbolName: string
): ts.SourceFile {
return _addSymbolToDecoratorMetadata(
host,
source,
pipePath,
'imports',
symbolName,
'Pipe'
);
}
export function addImportToModule(
host: Tree,
source: ts.SourceFile,