diff --git a/aio/tools/transforms/angular-api-package/processors/processNgModuleDocs.js b/aio/tools/transforms/angular-api-package/processors/processNgModuleDocs.js index b1dbbf032d5..933c640fe80 100644 --- a/aio/tools/transforms/angular-api-package/processors/processNgModuleDocs.js +++ b/aio/tools/transforms/angular-api-package/processors/processNgModuleDocs.js @@ -5,77 +5,222 @@ module.exports = function processNgModuleDocs(getDocFromAlias, createDocMessage, exportDocTypes: ['directive', 'pipe'], skipAbstractDirectives: true, $process(docs) { - // Match all the directives/pipes to their module const errors = []; - docs.forEach(doc => { + + for (const doc of docs) { 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; - } - - doc.ngModules.forEach((ngModule, index) => { - - const ngModuleDocs = getDocFromAlias(ngModule, doc); - - if (ngModuleDocs.length === 0) { - errors.push(createDocMessage(`"@ngModule ${ngModule}" does not match a public NgModule`, doc)); - return; - } - - if (ngModuleDocs.length > 1) { - errors.push(createDocMessage(`"@ngModule ${ngModule}" is ambiguous. Matches: ${ngModuleDocs.map(d => d.id).join(', ')}`, doc)); - return; - } - - const ngModuleDoc = ngModuleDocs[0]; - const containerName = getContainerName(doc.docType); - const container = ngModuleDoc[containerName] = ngModuleDoc[containerName] || []; - container.push(doc); - - doc.ngModules[index] = ngModuleDoc; - }); + // Match all the directives/pipes to their module + this.processNgModuleExportDoc(doc, errors); } - }); + + if (doc.docType === 'class') { + this.processInjectableDoc(doc, errors); + } + } if (errors.length) { errors.forEach(error => log.error(error)); throw new Error('Failed to process NgModule relationships.'); } - docs.forEach(doc => { + // Update the NgModule docs after we have associated the directives/pipes/injectables docs. + for (const doc of docs) { if (doc.docType === 'ngmodule') { - Object.keys(doc.ngmoduleOptions).forEach(key => { - const value = doc.ngmoduleOptions[key]; - if (value && !Array.isArray(value)) { - doc.ngmoduleOptions[key] = [value]; - } - }); - this.exportDocTypes.forEach(type => { - const containerName = getContainerName(type); - const container = doc[containerName]; - if (container) { - container.sort(byId); - } - }); + convertAllPropertiesToArrays(doc.ngmoduleOptions); + this.sortExportDocContainers(doc); + this.processNgModuleProviders(doc); + } + } + }, + + /** + * Associate the `exportDoc` that is expected to have been exported from an `NgModule` with its + * `NgModule` doc. + */ + processNgModuleExportDoc(exportDoc, errors) { + const options = exportDoc[`${exportDoc.docType}Options`]; + + // Directives without a selector are considered abstract and do not need to be part of + // any `@NgModule`. + if (this.skipAbstractDirectives && exportDoc.docType === 'directive' && !options.selector) { + return; + } + + if (!exportDoc.ngModules || exportDoc.ngModules.length === 0) { + errors.push(createDocMessage( + `"${exportDoc.id}" has no @ngModule tag. Docs of type "${exportDoc.docType}" must have this tag.`, + exportDoc)); + return; + } + + exportDoc.ngModules.forEach((ngModule, index) => { + const ngModuleDoc = getNgModule(ngModule, exportDoc, errors); + if (ngModuleDoc !== null) { + const containerName = getContainerName(exportDoc.docType); + const container = ngModuleDoc[containerName] = ngModuleDoc[containerName] || []; + container.push(exportDoc); + exportDoc.ngModules[index] = ngModuleDoc; } }); + }, + + /** + * Associate the given `injectableDoc` with an NgModule doc if it is provided on an `NgModule`. + */ + processInjectableDoc(injectableDoc, errors) { + const ngModules = []; + + if (Array.isArray(injectableDoc.ngModules)) { + for (const ngModule of injectableDoc.ngModules) { + if (isWrappedInQuotes(ngModule)) { + // `ngModule` is wrapped in quotes, so it will be one of `'any'`, `'root'` or `'platform'` + // and is not associated with a specific NgModule. So just use the string. + ngModules.push(ngModule.slice(1, -1)); + continue; + } + // Convert any `@ngModule` JSDOC tags to actual NgModule docs. + // Don't add this doc to the NgModule doc, since this should already be in the `providers` + // property of the `@NgModule()` decorator. + const ngModuleDoc = getNgModule(ngModule, injectableDoc, errors); + if (ngModuleDoc !== null) { + ngModules.push(ngModuleDoc); + } + } + } + + // Check for `providedIn` property on `@Injectable()`. + for (const decorator of injectableDoc.decorators || []) { + if (decorator.name === 'Injectable' && decorator.argumentInfo[0]) { + const providedIn = decorator.argumentInfo[0].providedIn; + this.processProvidedIn(providedIn, injectableDoc, ngModules, errors); + } + } + + // Check for `providedIn` property on an `ɵprov` static property + if (injectableDoc.symbol?.exports.has('ɵprov')) { + const declaration = injectableDoc.symbol?.exports.get('ɵprov')?.valueDeclaration; + const properties = declaration?.initializer?.arguments?.[0]?.properties; + const providedInProp = properties?.find(prop => prop.name.text === 'providedIn'); + const providedInNode = providedInProp?.initializer; + if (providedInNode) { + const providedIn = providedInNode.getSourceFile().text.slice(providedInNode.pos, providedInNode.end).trim(); + this.processProvidedIn(providedIn, injectableDoc, ngModules, errors); + } + } + + if (ngModules.length > 0) { + injectableDoc.ngModules = ngModules; + } + }, + + processProvidedIn(providedIn, injectableDoc, ngModules, errors) { + if (typeof providedIn !== 'string') { + // `providedIn` is not a string, which means that this is not a tree-shakable provider + // that needs associating with an NgModule. + return; + } + if (isWrappedInQuotes(providedIn)) { + // `providedIn` is wrapped in quotes, so it will be one of `'root'` or `'platform'` and + // is not associated with a specific NgModule. So just use the string. + ngModules.push(providedIn.slice(1, -1)); + return; + } + + // `providedIn` ought to reference a public NgModule + const ngModuleDoc = getNgModule(providedIn, injectableDoc, errors); + if (ngModuleDoc === null) { + return; + } + + const container = ngModuleDoc.providers = ngModuleDoc.providers || []; + container.push(injectableDoc); + ngModules.push(ngModuleDoc); + }, + + /** + * Ensure that the arrays containing the docs exported from the `ngModuleDoc` are sorted. + */ + sortExportDocContainers(ngModuleDoc) { + for (const type of this.exportDocTypes) { + const container = ngModuleDoc[getContainerName(type)]; + if (Array.isArray(container)) { + container.sort(byId); + } + } + }, + + /** + * Process the providers of the `ngModuleDoc`. + * + * Some providers come from the `@NgModule({providers: ...})` decoration. + * Other providers come from the `@Injectable({providedIn: ...})` decoration. + */ + processNgModuleProviders(ngModuleDoc) { + // Add providers from the NgModule decorator. + const providers = ngModuleDoc.ngmoduleOptions.providers || []; + // And also add those associated via the `Injectable` `providedIn` property. + if (Array.isArray(ngModuleDoc.providers)) { + for (const provider of ngModuleDoc.providers) { + providers.push(`{ provide: ${provider.name}, useClass: ${provider.name} }`); + } + } + + if (providers.length > 0) { + ngModuleDoc.providers = providers; + } } }; + + function getNgModule(ngModuleId, doc, errors) { + const ngModuleDocs = getDocFromAlias(ngModuleId, doc); + + if (ngModuleDocs.length === 0) { + errors.push( + createDocMessage(`The referenced "${ngModuleId}" does not match a public NgModule`, doc)); + return null; + } + + if (ngModuleDocs.length > 1) { + errors.push(createDocMessage( + `The referenced "${ngModuleId}" is ambiguous. Matches: ${ngModuleDocs.map(d => d.id).join(', ')}`, + doc)); + return null; + } + + return ngModuleDocs[0]; + } }; +/** + * Compute the name of the array that will hold items of this type in the NgModule document. + */ function getContainerName(docType) { return docType + 's'; } +/** + * Comparison function for sorting docs associated with an NgModule. + * + * This is used to sort docs by their id. + */ function byId(a, b) { return a.id > b.id ? 1 : -1; } + +/** + * Convert all the values of properties on the `obj` to arrays, if not already. + */ +function convertAllPropertiesToArrays(obj) { + for (const [key, value] of Object.entries(obj)) { + if (value && !Array.isArray(value)) { + obj[key] = [value]; + } + } +} + +/** + * Returns true if the `str` is wrapped in single or double quotes. + */ +function isWrappedInQuotes(str) { + return /^['"].+['"]$/.test(str); +} 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 6d22f74c09b..4bcefc37d60 100644 --- a/aio/tools/transforms/angular-api-package/processors/processNgModuleDocs.spec.js +++ b/aio/tools/transforms/angular-api-package/processors/processNgModuleDocs.spec.js @@ -22,7 +22,7 @@ describe('processNgModuleDocs processor', () => { expect(processor.$runAfter).toEqual(['extractDecoratedClassesProcessor', 'computeIdsProcessor']); }); - it('should non-arrayNgModule options to arrays', () => { + it('should convert non-array NgModule options to arrays', () => { const docs = [{ docType: 'ngmodule', ngmoduleOptions: { @@ -39,15 +39,15 @@ 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'], 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']}; + 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'], 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'] }; aliasMap.addDoc(ngModule1); aliasMap.addDoc(ngModule2); @@ -67,22 +67,138 @@ describe('processNgModuleDocs processor', () => { expect(pipe3.ngModules).toEqual([ngModule1, ngModule2]); }); - it('should not error if an abstract directove does not have a `@ngModule` tag', () => { + it('should link classes that have a `providedIn` property on an @Injectable decorator that references a known NgModule doc', () => { + const ngModule1 = { docType: 'ngmodule', id: 'NgModule1', aliases: ['NgModule1'], ngmoduleOptions: {} }; + const ngModule2 = { docType: 'ngmodule', id: 'NgModule2', aliases: ['NgModule2'], ngmoduleOptions: {} }; + const injectable1 = { docType: 'class', name: 'Injectable1', decorators: [{ name: 'Injectable', argumentInfo: [{ providedIn: '\'root\'' }] }] }; + const injectable2 = { docType: 'class', name: 'Injectable2', decorators: [{ name: 'Injectable', argumentInfo: [{ providedIn: '\'platform\'' }] }] }; + const injectable3 = { docType: 'class', name: 'Injectable3', decorators: [{ name: 'Injectable', argumentInfo: [{ providedIn: '"root"' }] }] }; + const injectable4 = { docType: 'class', name: 'Injectable4', decorators: [{ name: 'Injectable', argumentInfo: [{ providedIn: '"platform"' }] }] }; + const injectable5 = { docType: 'class', name: 'Injectable5', decorators: [{ name: 'Injectable', argumentInfo: [{ providedIn: 'NgModule1' }] }] }; + const injectable6 = { docType: 'class', name: 'Injectable6', decorators: [{ name: 'Injectable', argumentInfo: [{ providedIn: 'NgModule2' }] }] }; + const injectable7 = { docType: 'class', name: 'Injectable7' }; + const nonInjectable = { docType: 'class', name: 'nonInjectable' }; + + const aliasMap = injector.get('aliasMap'); + aliasMap.addDoc(ngModule1); + aliasMap.addDoc(ngModule2); + processor.$process([ngModule1, ngModule2, injectable1, injectable2, injectable3, injectable4, injectable5, injectable6, injectable7, nonInjectable]); + + expect(ngModule1.providers).toEqual(['{ provide: Injectable5, useClass: Injectable5 }']); + expect(ngModule2.providers).toEqual(['{ provide: Injectable6, useClass: Injectable6 }']); + + expect(injectable1.ngModules).toEqual(['root']); + expect(injectable2.ngModules).toEqual(['platform']); + expect(injectable3.ngModules).toEqual(['root']); + expect(injectable4.ngModules).toEqual(['platform']); + expect(injectable5.ngModules).toEqual([ngModule1]); + expect(injectable6.ngModules).toEqual([ngModule2]); + expect(injectable7.ngModules).toBeUndefined(); + expect(nonInjectable.ngModules).toBeUndefined(); + }); + + it('should link classes that have a `providedIn` property on a ɵprov static that references a known NgModule doc', () => { + const ngModule1 = { docType: 'ngmodule', id: 'NgModule1', aliases: ['NgModule1'], ngmoduleOptions: {} }; + const ngModule2 = { docType: 'ngmodule', id: 'NgModule2', aliases: ['NgModule2'], ngmoduleOptions: {} }; + const injectable1 = { docType: 'class', name: 'Injectable1', symbol: createSymbolWithProvider('\'root\'') }; + const injectable2 = { docType: 'class', name: 'Injectable2', symbol: createSymbolWithProvider('\'platform\'') }; + const injectable3 = { docType: 'class', name: 'Injectable3', symbol: createSymbolWithProvider('"root"') }; + const injectable4 = { docType: 'class', name: 'Injectable4', symbol: createSymbolWithProvider('"platform"') }; + const injectable5 = { docType: 'class', name: 'Injectable5', symbol: createSymbolWithProvider('NgModule1') }; + const injectable6 = { docType: 'class', name: 'Injectable6', symbol: createSymbolWithProvider('NgModule2') }; + const injectable7 = { docType: 'class', name: 'Injectable7' }; + const nonInjectable = { docType: 'class', name: 'nonInjectable' }; + + const aliasMap = injector.get('aliasMap'); + aliasMap.addDoc(ngModule1); + aliasMap.addDoc(ngModule2); + processor.$process([ngModule1, ngModule2, injectable1, injectable2, injectable3, injectable4, injectable5, injectable6, injectable7, nonInjectable]); + + expect(ngModule1.providers).toEqual(['{ provide: Injectable5, useClass: Injectable5 }']); + expect(ngModule2.providers).toEqual(['{ provide: Injectable6, useClass: Injectable6 }']); + + expect(injectable1.ngModules).toEqual(['root']); + expect(injectable2.ngModules).toEqual(['platform']); + expect(injectable3.ngModules).toEqual(['root']); + expect(injectable4.ngModules).toEqual(['platform']); + expect(injectable5.ngModules).toEqual([ngModule1]); + expect(injectable6.ngModules).toEqual([ngModule2]); + expect(injectable7.ngModules).toBeUndefined(); + expect(nonInjectable.ngModules).toBeUndefined(); + }); + + it('should link injectables that are marked with `@ngModule` JSDOC tags', () => { + const ngModule1 = { docType: 'ngmodule', id: 'NgModule1', aliases: ['NgModule1'], ngmoduleOptions: {} }; + const ngModule2 = { docType: 'ngmodule', id: 'NgModule2', aliases: ['NgModule2'], ngmoduleOptions: { providers: ['PROVIDER'] } }; + const injectable1 = { docType: 'class', name: 'Injectable1', ngModules: ['NgModule1'] }; + const injectable2 = { docType: 'class', name: 'Injectable2', ngModules: ['NgModule2'] }; + const injectable3 = { docType: 'class', name: 'Injectable3' }; + const nonInjectable = { docType: 'class', name: 'nonInjectable' }; + + const aliasMap = injector.get('aliasMap'); + aliasMap.addDoc(ngModule1); + aliasMap.addDoc(ngModule2); + processor.$process([ngModule1, ngModule2, injectable1, injectable2, injectable3, nonInjectable]); + + // Should not update the NgModule docs in this case. + expect(ngModule1.providers).toBeUndefined(); + expect(ngModule2.providers).toEqual(['PROVIDER']); + + expect(injectable1.ngModules).toEqual([ngModule1]); + expect(injectable2.ngModules).toEqual([ngModule2]); + expect(injectable3.ngModules).toBeUndefined(); + expect(nonInjectable.ngModules).toBeUndefined(); + }); + + it('should error if an injectable that has a `providedIn` property that references an unknown NgModule doc', () => { + const log = injector.get('log'); + const injectable = { docType: 'class', name: 'Injectable1', decorators: [{ name: 'Injectable', argumentInfo: [{ providedIn: 'NgModuleRef' }] }] }; + + expect(() => { + processor.$process([injectable]); + }).toThrowError('Failed to process NgModule relationships.'); + expect(log.error).toHaveBeenCalledWith( + 'The referenced "NgModuleRef" does not match a public NgModule - doc "Injectable1" (class) '); + }); + + it('should error if an injectable that has a `providedIn` property that references an ambiguous NgModule doc', () => { + const log = injector.get('log'); + + const ngModule1 = { docType: 'ngmodule', id: 'NgModule1', aliases: ['NgModuleRef'], ngmoduleOptions: {} }; + const ngModule2 = { docType: 'ngmodule', id: 'NgModule2', aliases: ['NgModuleRef'], ngmoduleOptions: {} }; + const injectable = { docType: 'class', name: 'Injectable1', decorators: [{ name: 'Injectable', argumentInfo: [{ providedIn: 'NgModuleRef' }] }] }; + + const aliasMap = injector.get('aliasMap'); + aliasMap.addDoc(ngModule1); + aliasMap.addDoc(ngModule2); + + expect(() => { + processor.$process([injectable]); + }).toThrowError('Failed to process NgModule relationships.'); + expect(log.error).toHaveBeenCalledWith( + 'The referenced "NgModuleRef" is ambiguous. Matches: NgModule1, NgModule2 - doc "Injectable1" (class) '); + }); + + it('should not error if an abstract directive 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} }]); + 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', - directiveOptions: {selector: 'dir1'} }]); + 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) '); @@ -97,39 +213,66 @@ 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'], - directiveOptions: {selector: 'dir1'} }]); + 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) '); + 'The referenced "MissingNgModule" does not match a public NgModule - doc "Directive1" (directive) '); expect(() => { processor.$process([{ docType: 'pipe', id: 'Pipe1', ngModules: ['MissingNgModule'] }]); }).toThrowError('Failed to process NgModule relationships.'); expect(log.error).toHaveBeenCalledWith( - '"@ngModule MissingNgModule" does not match a public NgModule - doc "Pipe1" (pipe) '); + 'The referenced "MissingNgModule" does not match a public NgModule - doc "Pipe1" (pipe) '); }); it('should error if a pipe/directive has an @ngModule tag that matches more than one NgModule doc', () => { const aliasMap = injector.get('aliasMap'); const log = injector.get('log'); - const ngModule1 = { docType: 'ngmodule', id: 'NgModule1', aliases: ['NgModuleAlias'], ngmoduleOptions: {}}; - const ngModule2 = { docType: 'ngmodule', id: 'NgModule2', aliases: ['NgModuleAlias'], ngmoduleOptions: {}}; + const ngModule1 = { docType: 'ngmodule', id: 'NgModule1', aliases: ['NgModuleAlias'], ngmoduleOptions: {} }; + const ngModule2 = { docType: 'ngmodule', id: 'NgModule2', aliases: ['NgModuleAlias'], ngmoduleOptions: {} }; aliasMap.addDoc(ngModule1); aliasMap.addDoc(ngModule2); expect(() => { processor.$process([{ docType: 'directive', id: 'Directive1', ngModules: ['NgModuleAlias'], - directiveOptions: {selector: 'dir1'} }]); + directiveOptions: { selector: 'dir1' } + }]); }).toThrowError('Failed to process NgModule relationships.'); expect(log.error).toHaveBeenCalledWith( - '"@ngModule NgModuleAlias" is ambiguous. Matches: NgModule1, NgModule2 - doc "Directive1" (directive) '); + 'The referenced "NgModuleAlias" is ambiguous. Matches: NgModule1, NgModule2 - doc "Directive1" (directive) '); expect(() => { processor.$process([{ docType: 'pipe', id: 'Pipe1', ngModules: ['NgModuleAlias'] }]); }).toThrowError('Failed to process NgModule relationships.'); expect(log.error).toHaveBeenCalledWith( - '"@ngModule NgModuleAlias" is ambiguous. Matches: NgModule1, NgModule2 - doc "Pipe1" (pipe) '); + 'The referenced "NgModuleAlias" is ambiguous. Matches: NgModule1, NgModule2 - doc "Pipe1" (pipe) '); }); }); + +/** + * This function simulates a TS AST node for the code: + * + * ``` + * static ɵprov = ɵɵdefineInjectable({ + * providedIn: 'xxxx', + * }); + * ``` + * + */ +function createSymbolWithProvider(providedIn) { + const initializer = { + pos: 0, + end: providedIn.length, + getSourceFile() { + return { text: providedIn }; + } + }; + const valueDeclaration = { initializer: { arguments: [{ properties: [ { name: { text: 'providedIn' }, initializer } ] } ] } }; + const exportMap = new Map(); + exportMap.set('ɵprov', {valueDeclaration}); + return {exports: exportMap}; +} \ No newline at end of file diff --git a/aio/tools/transforms/templates/api/class.template.html b/aio/tools/transforms/templates/api/class.template.html index 50d52311e68..5c22b539790 100644 --- a/aio/tools/transforms/templates/api/class.template.html +++ b/aio/tools/transforms/templates/api/class.template.html @@ -1,9 +1,11 @@ +{% import "lib/ngmodule.html" as ngModuleHelpers -%} {% extends 'export-base.template.html' -%} {% block overview %} {% include "includes/class-overview.html" %} {% endblock %} {% block details %} + {$ ngModuleHelpers.ngModuleList(doc.ngModules, 'Provided in') $} {% include "includes/description.html" %} {% include "includes/class-members.html" %} {% endblock %} diff --git a/aio/tools/transforms/templates/api/directive.template.html b/aio/tools/transforms/templates/api/directive.template.html index 1596da9825c..0d40b440c1e 100644 --- a/aio/tools/transforms/templates/api/directive.template.html +++ b/aio/tools/transforms/templates/api/directive.template.html @@ -1,10 +1,11 @@ {% import "lib/memberHelpers.html" as memberHelpers -%} +{% import "lib/ngmodule.html" as ngModuleHelpers -%} {% extends 'class.template.html' -%} {% block overview %}{% endblock %} {% block details -%} - {% include "includes/ngmodule.html" %} + {$ ngModuleHelpers.ngModuleList(doc.ngModules, 'Exported from') $} {% include "includes/selectors.html" %} {$ memberHelpers.renderDirectiveProperties(doc, 'Properties') $} diff --git a/aio/tools/transforms/templates/api/includes/ngmodule.html b/aio/tools/transforms/templates/api/includes/ngmodule.html deleted file mode 100644 index fb4674a2b23..00000000000 --- a/aio/tools/transforms/templates/api/includes/ngmodule.html +++ /dev/null @@ -1,11 +0,0 @@ -{% if doc.ngModules.length == 1 %}

NgModule

{% else %}

NgModules

{% endif %} - - diff --git a/aio/tools/transforms/templates/api/includes/pipe-overview.html b/aio/tools/transforms/templates/api/includes/pipe-overview.html index 06b6131105a..1b82f05cf89 100644 --- a/aio/tools/transforms/templates/api/includes/pipe-overview.html +++ b/aio/tools/transforms/templates/api/includes/pipe-overview.html @@ -1,4 +1,5 @@ {% import "lib/memberHelpers.html" as memberHelpers -%} +{% import "lib/ngmodule.html" as ngModuleHelpers -%} {% import "lib/paramList.html" as params -%}
@@ -10,7 +11,7 @@ {%- if param.isOptional or param.defaultValue !== undefined %} ]{% endif %} {%- endfor %} }} - {% include "includes/ngmodule.html" %} + {$ ngModuleHelpers.ngModuleList(doc.ngModules, 'Exported from') $} {% if doc.valueParam.type %}

Input value

diff --git a/aio/tools/transforms/templates/api/lib/ngmodule.html b/aio/tools/transforms/templates/api/lib/ngmodule.html new file mode 100644 index 00000000000..4c65ebc37e3 --- /dev/null +++ b/aio/tools/transforms/templates/api/lib/ngmodule.html @@ -0,0 +1,18 @@ +{% macro ngModuleList(ngModules, heading) %} +{% if ngModules and ngModules.length > 0 %} +

{$ heading $}

+ +{% endif %} +{% endmacro %} \ No newline at end of file diff --git a/aio/tools/transforms/templates/api/ngmodule.template.html b/aio/tools/transforms/templates/api/ngmodule.template.html index c124a03096e..3bb84bbabb1 100644 --- a/aio/tools/transforms/templates/api/ngmodule.template.html +++ b/aio/tools/transforms/templates/api/ngmodule.template.html @@ -69,8 +69,8 @@ {$ memberHelpers.renderMethodDetails(versionInfo, doc.methods, 'instance-methods', 'instance-method', 'Methods') $} - {% if doc.ngmoduleOptions.providers %} - {$ renderTable(doc.ngmoduleOptions.providers, 'providers', 'Providers', 'Provider') $} + {% if doc.providers %} + {$ renderTable(doc.providers, 'providers', 'Providers', 'Provider') $} {% endif %} {% if doc.directives.length %}