diff --git a/aio/tools/transforms/angular-api-package/processors/extractDecoratedClasses.js b/aio/tools/transforms/angular-api-package/processors/extractDecoratedClasses.js index 9224223721..e245b40987 100644 --- a/aio/tools/transforms/angular-api-package/processors/extractDecoratedClasses.js +++ b/aio/tools/transforms/angular-api-package/processors/extractDecoratedClasses.js @@ -18,7 +18,10 @@ module.exports = function extractDecoratedClassesProcessor(EXPORT_DOC_TYPES) { if (decoratorTypes.indexOf(decorator.name) !== -1) { doc.docType = decorator.name.toLowerCase(); - doc[doc.docType + 'Options'] = decorator.argumentInfo[0]; + // Directives do not always have an argument (i.e. abstract directives). + // We still create options for those, as an empty object literal is equal + // to just having an empty object literal as decorator argument. + doc[doc.docType + 'Options'] = decorator.argumentInfo[0] || {}; } }); }); diff --git a/aio/tools/transforms/angular-api-package/processors/processNgModuleDocs.js b/aio/tools/transforms/angular-api-package/processors/processNgModuleDocs.js index d92a2715b8..b1dbbf032d 100644 --- a/aio/tools/transforms/angular-api-package/processors/processNgModuleDocs.js +++ b/aio/tools/transforms/angular-api-package/processors/processNgModuleDocs.js @@ -3,11 +3,20 @@ module.exports = function processNgModuleDocs(getDocFromAlias, createDocMessage, $runAfter: ['extractDecoratedClassesProcessor', 'computeIdsProcessor'], $runBefore: ['createSitemap'], exportDocTypes: ['directive', 'pipe'], + skipAbstractDirectives: true, $process(docs) { // Match all the directives/pipes to their module const errors = []; docs.forEach(doc => { if (this.exportDocTypes.indexOf(doc.docType) !== -1) { + const options = doc[`${doc.docType}Options`]; + + // Directives without a selector are considered abstract and do + // not need to be part of any `@NgModule`. + if (this.skipAbstractDirectives && doc.docType === 'directive' && !options.selector) { + return; + } + if (!doc.ngModules || doc.ngModules.length === 0) { errors.push(createDocMessage(`"${doc.id}" has no @ngModule tag. Docs of type "${doc.docType}" must have this tag.`, doc)); return; diff --git a/aio/tools/transforms/angular-api-package/processors/processNgModuleDocs.spec.js b/aio/tools/transforms/angular-api-package/processors/processNgModuleDocs.spec.js index adfed1035c..6d22f74c09 100644 --- a/aio/tools/transforms/angular-api-package/processors/processNgModuleDocs.spec.js +++ b/aio/tools/transforms/angular-api-package/processors/processNgModuleDocs.spec.js @@ -39,11 +39,12 @@ describe('processNgModuleDocs processor', () => { it('should link directive/pipe docs with their NgModule docs (sorted by id)', () => { const aliasMap = injector.get('aliasMap'); + const directiveOptions = {selector: 'some-selector'}; const ngModule1 = { docType: 'ngmodule', id: 'NgModule1', aliases: ['NgModule1'], ngmoduleOptions: {}}; const ngModule2 = { docType: 'ngmodule', id: 'NgModule2', aliases: ['NgModule2'], ngmoduleOptions: {}}; - const directive1 = { docType: 'directive', id: 'Directive1', ngModules: ['NgModule1']}; - const directive2 = { docType: 'directive', id: 'Directive2', ngModules: ['NgModule2']}; - const directive3 = { docType: 'directive', id: 'Directive3', ngModules: ['NgModule1', 'NgModule2']}; + const directive1 = { docType: 'directive', id: 'Directive1', ngModules: ['NgModule1'], directiveOptions}; + const directive2 = { docType: 'directive', id: 'Directive2', ngModules: ['NgModule2'], directiveOptions}; + const directive3 = { docType: 'directive', id: 'Directive3', ngModules: ['NgModule1', 'NgModule2'], directiveOptions}; const pipe1 = { docType: 'pipe', id: 'Pipe1', ngModules: ['NgModule1']}; const pipe2 = { docType: 'pipe', id: 'Pipe2', ngModules: ['NgModule2']}; const pipe3 = { docType: 'pipe', id: 'Pipe3', ngModules: ['NgModule1', 'NgModule2']}; @@ -66,10 +67,22 @@ describe('processNgModuleDocs processor', () => { expect(pipe3.ngModules).toEqual([ngModule1, ngModule2]); }); + it('should not error if an abstract directove does not have a `@ngModule` tag', () => { + expect(() => { + processor.$process([{ docType: 'directive', id: 'AbstractDir', directiveOptions: {} }]); + }).not.toThrow(); + + expect(() => { + processor.$process([{ docType: 'directive', id: 'AbstractDir', + directiveOptions: {selector: undefined} }]); + }).not.toThrow(); + }); + it('should error if a pipe/directive does not have a `@ngModule` tag', () => { const log = injector.get('log'); expect(() => { - processor.$process([{ docType: 'directive', id: 'Directive1' }]); + processor.$process([{ docType: 'directive', id: 'Directive1', + directiveOptions: {selector: 'dir1'} }]); }).toThrowError('Failed to process NgModule relationships.'); expect(log.error).toHaveBeenCalledWith( '"Directive1" has no @ngModule tag. Docs of type "directive" must have this tag. - doc "Directive1" (directive) '); @@ -84,7 +97,8 @@ describe('processNgModuleDocs processor', () => { it('should error if a pipe/directive has an @ngModule tag that does not match an NgModule doc', () => { const log = injector.get('log'); expect(() => { - processor.$process([{ docType: 'directive', id: 'Directive1', ngModules: ['MissingNgModule'] }]); + processor.$process([{ docType: 'directive', id: 'Directive1', ngModules: ['MissingNgModule'], + directiveOptions: {selector: 'dir1'} }]); }).toThrowError('Failed to process NgModule relationships.'); expect(log.error).toHaveBeenCalledWith( '"@ngModule MissingNgModule" does not match a public NgModule - doc "Directive1" (directive) '); @@ -105,7 +119,9 @@ describe('processNgModuleDocs processor', () => { aliasMap.addDoc(ngModule2); expect(() => { - processor.$process([{ docType: 'directive', id: 'Directive1', ngModules: ['NgModuleAlias'] }]); + processor.$process([{ + docType: 'directive', id: 'Directive1', ngModules: ['NgModuleAlias'], + directiveOptions: {selector: 'dir1'} }]); }).toThrowError('Failed to process NgModule relationships.'); expect(log.error).toHaveBeenCalledWith( '"@ngModule NgModuleAlias" is ambiguous. Matches: NgModule1, NgModule2 - doc "Directive1" (directive) ');