Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(schematics): add helper for adding destroy subject to Angular ar…
…tifact
- Loading branch information
Showing
5 changed files
with
240 additions
and
0 deletions.
There are no files selected for viewing
85 changes: 85 additions & 0 deletions
85
schematics/src/add-destroy-subject-to-component/factory.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import { Rule, SchematicsException, chain } from '@angular-devkit/schematics'; | ||
import { buildDefaultPath, getProject } from '@schematics/angular/utility/project'; | ||
import { Scope } from 'ts-morph'; | ||
|
||
import { applyLintFix } from '../utils/lint-fix'; | ||
import { createTsMorphProject } from '../utils/ts-morph'; | ||
|
||
import { PWAAddDestroySubjectToComponentOptionsSchema as Options } from './schema'; | ||
|
||
export function add(options: Options): Rule { | ||
return host => { | ||
if (!options.project) { | ||
throw new SchematicsException('Option (project) is required.'); | ||
} | ||
let path = `${buildDefaultPath(getProject(host, options.project))}/${options.name | ||
.replace(/.*src\/app\//, '') | ||
.replace(/\/$/, '')}`; | ||
|
||
if (!path.endsWith('.ts') && host.getDir(path)) { | ||
const file = host.getDir(path).subfiles.find(el => /(component|pipe|directive)\.ts/.test(el)); | ||
path = `${path}/${file}`; | ||
} | ||
|
||
if (!path || !host.exists(path)) { | ||
throw new SchematicsException('Option (path) is required and must exist.'); | ||
} | ||
|
||
const tsMorphProject = createTsMorphProject(host); | ||
tsMorphProject.addSourceFileAtPath(path); | ||
|
||
const sourceFile = tsMorphProject.getSourceFile(path); | ||
sourceFile | ||
.getClasses() | ||
.filter(clazz => clazz.isExported()) | ||
.forEach(classDeclaration => { | ||
if (!classDeclaration.getImplements().find(imp => imp.getText() === 'OnDestroy')) { | ||
classDeclaration.addImplements('OnDestroy'); | ||
} | ||
|
||
if (!classDeclaration.getProperty('destroy$')) { | ||
classDeclaration.insertProperty(classDeclaration.getProperties().length, { | ||
name: 'destroy$', | ||
initializer: 'new Subject()', | ||
scope: Scope.Private, | ||
}); | ||
} | ||
|
||
if (!classDeclaration.getMethod('ngOnDestroy')) { | ||
const onDestroyMethod = classDeclaration.addMethod({ | ||
name: 'ngOnDestroy', | ||
}); | ||
|
||
const methodBody = onDestroyMethod.addBody(); | ||
methodBody.setBodyText(`this.destroy$.next(); | ||
this.destroy$.complete();`); | ||
} | ||
}); | ||
|
||
const angularCoreImport = sourceFile.getImportDeclarationOrThrow( | ||
imp => imp.getModuleSpecifierValue() === '@angular/core' | ||
); | ||
if (!angularCoreImport.getNamedImports().find(el => el.getText() === 'OnDestroy')) { | ||
angularCoreImport.addNamedImport('OnDestroy'); | ||
} | ||
|
||
const rxjsImport = sourceFile.getImportDeclaration(imp => imp.getModuleSpecifierValue() === 'rxjs'); | ||
if (!rxjsImport) { | ||
sourceFile.addImportDeclaration({ | ||
namedImports: ['Subject'], | ||
moduleSpecifier: 'rxjs', | ||
}); | ||
} else if (!rxjsImport.getNamedImports().find(el => el.getText() === 'Subject')) { | ||
rxjsImport.addNamedImport('Subject'); | ||
} | ||
sourceFile.formatText({ indentSize: 2, convertTabsToSpaces: true }); | ||
host.overwrite(path, sourceFile.getText()); | ||
|
||
const operations = []; | ||
if (!options.ci) { | ||
operations.push(applyLintFix()); | ||
} | ||
|
||
return chain(operations); | ||
}; | ||
} |
110 changes: 110 additions & 0 deletions
110
schematics/src/add-destroy-subject-to-component/factory_spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import { UnitTestTree } from '@angular-devkit/schematics/testing'; | ||
import { switchMap } from 'rxjs/operators'; | ||
|
||
import { PWAComponentOptionsSchema } from '../component/schema'; | ||
import { createApplication, createSchematicRunner } from '../utils/testHelper'; | ||
|
||
describe('Lazy Component Schematic', () => { | ||
const schematicRunner = createSchematicRunner(); | ||
|
||
let appTree: UnitTestTree; | ||
beforeEach(async () => { | ||
appTree = await createApplication(schematicRunner) | ||
.pipe( | ||
switchMap(tree => | ||
schematicRunner.runSchematicAsync( | ||
'component', | ||
{ | ||
project: 'bar', | ||
name: 'foo', | ||
} as PWAComponentOptionsSchema, | ||
tree | ||
) | ||
) | ||
) | ||
.toPromise(); | ||
}); | ||
|
||
it('should be created', () => { | ||
expect(appTree.files).toContain('/src/app/foo/foo.component.ts'); | ||
}); | ||
|
||
it('should be runnable on a component', async () => { | ||
await schematicRunner | ||
.runSchematicAsync( | ||
'add-destroy-subject-to-component', | ||
{ | ||
project: 'bar', | ||
name: 'src/app/foo/foo.component.ts', | ||
}, | ||
appTree | ||
) | ||
.toPromise(); | ||
}); | ||
|
||
it('should be runnable on the folder of a component', async () => { | ||
await schematicRunner | ||
.runSchematicAsync( | ||
'add-destroy', | ||
{ | ||
project: 'bar', | ||
name: 'src/app/foo', | ||
}, | ||
appTree | ||
) | ||
.toPromise(); | ||
}); | ||
|
||
it('should be runnable on the relative project folder of a component', async () => { | ||
await schematicRunner | ||
.runSchematicAsync( | ||
'add-destroy', | ||
{ | ||
project: 'bar', | ||
name: 'foo', | ||
}, | ||
appTree | ||
) | ||
.toPromise(); | ||
}); | ||
|
||
describe('after run', () => { | ||
let content: string; | ||
|
||
beforeEach(async () => { | ||
appTree = await schematicRunner | ||
.runSchematicAsync( | ||
'add-destroy-subject-to-component', | ||
{ | ||
project: 'bar', | ||
name: 'src/app/foo/foo.component.ts', | ||
}, | ||
appTree | ||
) | ||
.toPromise(); | ||
content = appTree.readContent('src/app/foo/foo.component.ts'); | ||
}); | ||
|
||
it('should add import for OnDestroy', () => { | ||
expect(content).toMatch(/OnDestroy.* from .@angular\/core.;/); | ||
}); | ||
|
||
it('should add import for Subject', () => { | ||
expect(content).toMatch(/Subject.* from .rxjs.;/); | ||
}); | ||
|
||
it('should add implements for OnDestroy', () => { | ||
expect(content).toMatch(/implements.*OnDestroy/); | ||
}); | ||
|
||
it('should add destroy$ subject', () => { | ||
expect(content).toContain('destroy$ = new Subject();'); | ||
}); | ||
|
||
it('should add ngOnDestroy method', () => { | ||
expect(content).toContain('ngOnDestroy()'); | ||
expect(content).toContain('this.destroy$.next();'); | ||
expect(content).toContain('this.destroy$.complete();'); | ||
}); | ||
}); | ||
}); |
25 changes: 25 additions & 0 deletions
25
schematics/src/add-destroy-subject-to-component/schema.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
{ | ||
"$schema": "http://json-schema.org/schema", | ||
"id": "SchematicsPWAComponent", | ||
"title": "PWA Add Destroy Subject To Component Options Schema", | ||
"type": "object", | ||
"description": "Adds destroy subject to an existing Angular artifact.", | ||
"properties": { | ||
"project": { | ||
"type": "string", | ||
"$default": { | ||
"$source": "projectName" | ||
}, | ||
"visible": false | ||
}, | ||
"name": { | ||
"type": "string", | ||
"description": "The path of the component.", | ||
"$default": { | ||
"$source": "argv", | ||
"index": 0 | ||
}, | ||
"x-prompt": "What component should the destroy be added to?" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { Tree } from '@angular-devkit/schematics'; | ||
import { existsSync, readFileSync } from 'fs'; | ||
import { FileSystemHost, Project } from 'ts-morph'; | ||
|
||
export function createTsMorphProject(host: Tree) { | ||
return new Project({ | ||
// tslint:disable-next-line: ish-no-object-literal-type-assertion | ||
fileSystem: { | ||
getCurrentDirectory: () => '', | ||
directoryExistsSync: p => host.exists(p) || existsSync(p), | ||
fileExistsSync: p => host.exists(p) || existsSync(p), | ||
readFileSync: (p, encoding) => (host.read(p) || readFileSync(p)).toString(encoding), | ||
} as FileSystemHost, | ||
}); | ||
} |