-
+ {% for ambiguousDoc in doc.docs %}
- + {$ ambiguousDoc.id $} + {$ ambiguousDoc.shortDescription | marked $} + {% endfor %} +
diff --git a/aio/tools/transforms/angular-base-package/index.js b/aio/tools/transforms/angular-base-package/index.js index 037f52322e..cfe8540329 100644 --- a/aio/tools/transforms/angular-base-package/index.js +++ b/aio/tools/transforms/angular-base-package/index.js @@ -37,6 +37,7 @@ module.exports = new Package('angular-base', [ .processor(require('./processors/renderLinkInfo')) .processor(require('./processors/checkContentRules')) .processor(require('./processors/splitDescription')) + .processor(require('./processors/disambiguateDocPaths')) // overrides base packageInfo and returns the one for the 'angular/angular' repo. .factory('packageInfo', function() { return require(path.resolve(PROJECT_ROOT, 'package.json')); }) @@ -66,7 +67,7 @@ module.exports = new Package('angular-base', [ collectExamples.exampleFolders = []; generateKeywordsProcessor.ignoreWords = require(path.resolve(__dirname, 'ignore-words'))['en']; - generateKeywordsProcessor.docTypesToIgnore = [undefined, 'example-region', 'json-doc', 'api-list-data', 'api-list-data', 'contributors-json', 'navigation-json', 'announcements-json']; + generateKeywordsProcessor.docTypesToIgnore = [undefined, 'example-region', 'json-doc', 'api-list-data', 'api-list-data', 'contributors-json', 'navigation-json', 'announcements-json', 'disambiguator']; generateKeywordsProcessor.propertiesToIgnore = ['basePath', 'renderedContent', 'docType', 'searchTitle']; }) @@ -167,5 +168,5 @@ module.exports = new Package('angular-base', [ }) .config(function(convertToJsonProcessor) { - convertToJsonProcessor.docTypes = []; + convertToJsonProcessor.docTypes = ['disambiguator']; }); diff --git a/aio/tools/transforms/angular-base-package/processors/disambiguateDocPaths.js b/aio/tools/transforms/angular-base-package/processors/disambiguateDocPaths.js new file mode 100644 index 0000000000..2f3d01b624 --- /dev/null +++ b/aio/tools/transforms/angular-base-package/processors/disambiguateDocPaths.js @@ -0,0 +1,68 @@ +/** + * @dgProcessor disambiguateDocPathsProcessor + * @description + * + * Ensures that docs that have the same path, other than case changes, + * are disambiguated. + * + * For example in Angular there is the `ROUTES` const and a `Routes` type. + * In a case-sensitive file-system these would both be stored at the paths + * + * ``` + * aio/src/generated/router/Routes.json + * aio/src/generated/router/ROUTES.json + * ``` + * + * but in a case-insensitive file-system these two paths point to the same file! + */ +module.exports = function disambiguateDocPathsProcessor(log) { + return { + $runAfter: ['paths-computed'], + $runBefore: ['rendering-docs'], + $process(docs) { + // Collect all the ambiguous docs, whose outputPath is are only different by casing. + const ambiguousDocMap = new Map(); + for (const doc of docs) { + if (!doc.outputPath) { + continue; + } + const outputPath = doc.outputPath.toLowerCase(); + if (!ambiguousDocMap.has(outputPath)) { + ambiguousDocMap.set(outputPath, []); + } + const ambiguousDocs = ambiguousDocMap.get(outputPath); + ambiguousDocs.push(doc); + } + + // Create a disambiguator doc for each set of such ambiguous docs, + // and update the ambiguous docs to have unique `path` and `outputPath` properties. + for (const [outputPath, ambiguousDocs] of ambiguousDocMap) { + if (ambiguousDocs.length === 1) { + continue; + } + + log.debug('Docs with ambiguous outputPath:' + ambiguousDocs.map((d, i) => `\n - ${d.id}: "${d.outputPath}" replaced with "${convertPath(d.outputPath, i)}".`)); + + const doc = ambiguousDocs[0]; + const path = doc.path; + const id = `${doc.id.toLowerCase()}-disambiguator`; + const title = `${doc.id.toLowerCase()} (disambiguation)`; + const aliases = [id]; + docs.push({ docType: 'disambiguator', id, title, aliases, path, outputPath, docs: ambiguousDocs }); + + // Update the paths + let count = 0; + for (const doc of ambiguousDocs) { + doc.path = convertPath(doc.path, count); + doc.outputPath = convertPath(doc.outputPath, count); + count += 1; + } + } + } + }; +}; + +function convertPath(path, count) { + // Add the counter before any extension + return path.replace(/(\.[^.]*)?$/, `-${count}$1`); +} diff --git a/aio/tools/transforms/angular-base-package/processors/disambiguateDocPaths.spec.js b/aio/tools/transforms/angular-base-package/processors/disambiguateDocPaths.spec.js new file mode 100644 index 0000000000..6bcd5995bc --- /dev/null +++ b/aio/tools/transforms/angular-base-package/processors/disambiguateDocPaths.spec.js @@ -0,0 +1,65 @@ +const testPackage = require('../../helpers/test-package'); +const Dgeni = require('dgeni'); + +describe('disambiguateDocPaths processor', () => { + let dgeni, injector, processor, docs; + + beforeEach(() => { + dgeni = new Dgeni([testPackage('angular-base-package')]); + injector = dgeni.configureInjector(); + processor = injector.get('disambiguateDocPathsProcessor'); + docs = [ + {docType: 'test-doc', id: 'test-doc', path: 'test/doc', outputPath: 'test/doc.json'}, + {docType: 'test-doc', id: 'TEST-DOC', path: 'TEST/DOC', outputPath: 'TEST/DOC.json'}, + {docType: 'test-doc', id: 'test-Doc', path: 'test/Doc', outputPath: 'test/Doc.xml'}, + {docType: 'test-doc', id: 'unique-doc', path: 'unique/doc', outputPath: 'unique/doc.json'}, + {docType: 'test-doc', id: 'other-doc', path: 'other/doc', outputPath: 'other/doc.json'}, + {docType: 'test-doc', id: 'other-DOC', path: 'other/DOC', outputPath: 'other/DOC.json'}, + ]; + }); + + it('should be part of the dgeni package', () => { + expect(processor).toBeDefined(); + }); + + it('should create `disambiguator` documents for docs that have ambiguous outputPaths', () => { + const numDocs = docs.length; + processor.$process(docs); + expect(docs.length).toEqual(numDocs + 2); + expect(docs[docs.length - 2]).toEqual({ + docType: 'disambiguator', + id: 'test-doc-disambiguator', + title: 'test-doc (disambiguation)', + aliases: ['test-doc-disambiguator'], + path: 'test/doc', + outputPath: 'test/doc.json', + docs: [docs[0], docs[1]], + }); + expect(docs[docs.length - 1]).toEqual({ + docType: 'disambiguator', + id: 'other-doc-disambiguator', + title: 'other-doc (disambiguation)', + aliases: ['other-doc-disambiguator'], + path: 'other/doc', + outputPath: 'other/doc.json', + docs: [docs[4], docs[5]], + }); + }); + + it('should update the path and outputPath properties of each ambiguous doc', () => { + processor.$process(docs); + expect(docs[0].path).toEqual('test/doc-0'); + expect(docs[0].outputPath).toEqual('test/doc-0.json'); + expect(docs[1].path).toEqual('TEST/DOC-1'); + expect(docs[1].outputPath).toEqual('TEST/DOC-1.json'); + + // The non-ambiguous docs are left alone + expect(docs[2].outputPath).toEqual('test/Doc.xml'); + expect(docs[3].outputPath).toEqual('unique/doc.json'); + + expect(docs[4].path).toEqual('other/doc-0'); + expect(docs[4].outputPath).toEqual('other/doc-0.json'); + expect(docs[5].path).toEqual('other/DOC-1'); + expect(docs[5].outputPath).toEqual('other/DOC-1.json'); + }); +}); diff --git a/aio/tools/transforms/templates/disambiguator.template.html b/aio/tools/transforms/templates/disambiguator.template.html new file mode 100644 index 0000000000..feab8f5070 --- /dev/null +++ b/aio/tools/transforms/templates/disambiguator.template.html @@ -0,0 +1,15 @@ +{% marked %} +# {$ doc.title $} +{% endmarked %} + +{% block content %} +