diff --git a/server/src/utilities/generateComponent/helpers/__mocks__/ngModuleInstrumented.ts b/server/src/utilities/generateComponent/helpers/__mocks__/ngModuleInstrumented.ts new file mode 100644 index 0000000..f3d9c50 --- /dev/null +++ b/server/src/utilities/generateComponent/helpers/__mocks__/ngModuleInstrumented.ts @@ -0,0 +1,19 @@ +// This file is being ignored by typescript because it is only used in tests. + +// @ts-ignore +import { AngularPerfModule, RoutingService } from '@microsoft/mezzurite-angular'; + +// @ts-ignore +@NgModule({ + imports: [ + AngularPerfModule.forRoot() + ] +}) +// @ts-ignore +export class InstrumentedModule { + // @ts-ignore + constructor (@Inject(RoutingService) private router: typeof RoutingService) { + // @ts-ignore + router.start(); + } +} diff --git a/server/src/utilities/generateComponent/helpers/__mocks__/ngModuleNotInstrumented.ts b/server/src/utilities/generateComponent/helpers/__mocks__/ngModuleNotInstrumented.ts new file mode 100644 index 0000000..6a80706 --- /dev/null +++ b/server/src/utilities/generateComponent/helpers/__mocks__/ngModuleNotInstrumented.ts @@ -0,0 +1,19 @@ +// This file is being ignored by typescript because it is only used in tests. + +// @ts-ignore +import { RoutingService } from 'not-Mezzurite'; + +// @ts-ignore +@NgModule({ + imports: [ + // @ts-ignore + nothing.forRoot() + ] +}) +// @ts-ignore +export class NotInstrumentedModule { + constructor () { + // @ts-ignore + dummy.blah(); + } +} diff --git a/server/src/utilities/generateComponent/helpers/generateNgModule.test.ts b/server/src/utilities/generateComponent/helpers/generateNgModule.test.ts new file mode 100644 index 0000000..fe51cc3 --- /dev/null +++ b/server/src/utilities/generateComponent/helpers/generateNgModule.test.ts @@ -0,0 +1,52 @@ +import { join } from 'path'; +import Project from 'ts-morph'; + +import generateNgModule from './generateNgModule'; + +describe('generateNgModule.ts', () => { + const project = new Project({ + addFilesFromTsConfig: false + }); + + it('should return null when filePath is null', () => { + expect(generateNgModule(null, null)).toBeNull(); + }); + + it('should return null when sourceFile is null', () => { + expect(generateNgModule('filePath', null)).toBeNull(); + }); + + it('should generate a Mezzurite component from an ngModule file passing all the checks', () => { + const filePath = join('.', 'server', 'src', 'utilities', 'generateComponent', 'helpers', '__mocks__', 'ngModuleInstrumented.ts'); + const sourceFile = project.addExistingSourceFile(filePath); + expect(generateNgModule(filePath, sourceFile)) + .toMatchObject({ + checks: { + hasAngularPerfModule: true, + hasImport: true, + hasRoutingServiceStart: true + }, + filePath, + name: 'InstrumentedModule', + type: 'ngModule' + }); + project.removeSourceFile(sourceFile); + }); + + it('should generate a Mezzurite component from an ngModule file passing none of the checks', () => { + const filePath = join('.', 'server', 'src', 'utilities', 'generateComponent', 'helpers', '__mocks__', 'ngModuleNotInstrumented.ts'); + const sourceFile = project.addExistingSourceFile(filePath); + expect(generateNgModule(filePath, sourceFile)) + .toMatchObject({ + checks: { + hasAngularPerfModule: false, + hasImport: false, + hasRoutingServiceStart: false + }, + filePath, + name: 'NotInstrumentedModule', + type: 'ngModule' + }); + project.removeSourceFile(sourceFile); + }); +}); diff --git a/server/src/utilities/generateComponent/helpers/generateNgModule.ts b/server/src/utilities/generateComponent/helpers/generateNgModule.ts new file mode 100644 index 0000000..8896b49 --- /dev/null +++ b/server/src/utilities/generateComponent/helpers/generateNgModule.ts @@ -0,0 +1,91 @@ +import { ClassDeclaration, Node, SyntaxKind, SourceFile } from 'ts-morph'; + +import MezzuriteComponent from '../../../models/MezzuriteComponent'; + +function generateNgModule (filePath: string, sourceFile: SourceFile): MezzuriteComponent { + let component = null; + + if (filePath != null && sourceFile != null) { + // TODO: Handle multiple classes in a file. + const sourceClass = sourceFile.getClasses()[0]; + + const hasAngularPerfModule = containsAngularPerfImport(sourceClass); + const hasImport = sourceFile.getImportDeclaration('@microsoft/mezzurite-angular') != null; + const hasRoutingServiceStart = containsRoutingServiceStart(sourceClass); + + const name = sourceClass.getName(); + + component = { + checks: { + hasAngularPerfModule, + hasImport, + hasRoutingServiceStart + }, + filePath, + name, + type: 'ngModule' + }; + } + + return component; +} + +function containsAngularPerfImport (sourceClass: ClassDeclaration): boolean { + const ngModuleDecorator = sourceClass.getDecorator('NgModule'); + let containsAngularPerfImport = false; + + if (ngModuleDecorator != null) { + const decoratorArgument = ngModuleDecorator.getArguments().find((child: Node) => { + return child.getKind() === SyntaxKind.ObjectLiteralExpression; + }); + + if (decoratorArgument != null) { + const decoratorArgumentParameters = decoratorArgument.getFirstChildByKind(SyntaxKind.SyntaxList).getChildren(); + const importNode = decoratorArgumentParameters.find((argument: Node) => { + const isPropertyAssignment = argument.getKind() === SyntaxKind.PropertyAssignment; + let hasImports = false; + if (isPropertyAssignment) { + const identifier = argument.getFirstChildByKind(SyntaxKind.Identifier); + if (identifier.getText() === 'imports') { + hasImports = true; + } + } + return hasImports; + }); + + if (importNode != null) { + const imports = importNode.getFirstChildByKind(SyntaxKind.ArrayLiteralExpression); + containsAngularPerfImport = imports.getText().indexOf('AngularPerfModule.forRoot()') > -1; + } + } + } + + return containsAngularPerfImport; +} + +function containsRoutingServiceStart (sourceClass: ClassDeclaration): boolean { + let containsRoutingServiceStart = false; + const constructors = sourceClass.getConstructors(); + + if (constructors.length > 0) { + // TODO: How do you handle multiple constructors? + const constructorParameters = constructors[0].getFirstChildByKind(SyntaxKind.Parameter); + + if (constructorParameters != null) { + const routingServiceType = constructorParameters.getFirstChildByKind(SyntaxKind.TypeQuery); + if (constructorParameters != null) { + if (routingServiceType.getText().indexOf('RoutingService') > -1) { + const constructorBlock = constructors[0].getFirstChildByKind(SyntaxKind.Block); + // TODO: Make this checking more robust. + containsRoutingServiceStart = constructorBlock.getChildren().find((child: Node) => { + return child.getText().indexOf('start()') > -1; + }) != null; + } + } + } + } + + return containsRoutingServiceStart; +} + +export default generateNgModule; diff --git a/server/src/utilities/generateComponent/helpers/index.ts b/server/src/utilities/generateComponent/helpers/index.ts index fa67938..159eb6d 100644 --- a/server/src/utilities/generateComponent/helpers/index.ts +++ b/server/src/utilities/generateComponent/helpers/index.ts @@ -1,5 +1,7 @@ import generateNgComponent from './generateNgComponent'; +import generateNgModule from './generateNgModule'; export default { - generateNgComponent + generateNgComponent, + generateNgModule }; diff --git a/server/src/utilities/generateComponent/index.test.ts b/server/src/utilities/generateComponent/index.test.ts index 6b4f8d1..d35bd30 100644 --- a/server/src/utilities/generateComponent/index.test.ts +++ b/server/src/utilities/generateComponent/index.test.ts @@ -42,4 +42,10 @@ describe('index.ts', () => { helpers.generateNgComponent = jest.fn(() => component); expect(generateComponent('ngComponent', 'filePath', sourceFile)).toMatchObject(component); }); + + it('should return the component when generateNgModule is not null', () => { + helpers.generateNgModule = jest.fn(() => component); + expect(generateComponent('ngModule', 'filePath', sourceFile)).toMatchObject(component); + }); + }); diff --git a/server/src/utilities/generateComponent/index.ts b/server/src/utilities/generateComponent/index.ts index 21a5694..6c55bc7 100644 --- a/server/src/utilities/generateComponent/index.ts +++ b/server/src/utilities/generateComponent/index.ts @@ -9,6 +9,8 @@ function generateComponent (componentType: string, filePath: string, sourceFile: if (componentType != null && filePath != null && sourceFile != null) { if (componentType === 'ngComponent') { component = helpers.generateNgComponent(filePath, sourceFile); + } else if (componentType === 'ngModule') { + component = helpers.generateNgModule(filePath, sourceFile); } }