From 8b571309ed3ddcaf0497da0d01823005a687e566 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Fri, 6 Oct 2017 08:28:46 +0100 Subject: [PATCH] build(aio): append information about links in and out of docs (#19583) Closes #19560 PR Close #19583 --- .../transforms/angular-base-package/index.js | 1 + .../processors/renderLinkInfo.js | 52 ++++ .../processors/renderLinkInfo.spec.js | 222 ++++++++++++++++++ .../transforms/angular.io-package/index.js | 4 + 4 files changed, 279 insertions(+) create mode 100644 aio/tools/transforms/angular-base-package/processors/renderLinkInfo.js create mode 100644 aio/tools/transforms/angular-base-package/processors/renderLinkInfo.spec.js diff --git a/aio/tools/transforms/angular-base-package/index.js b/aio/tools/transforms/angular-base-package/index.js index 427eae281e..ed45692a12 100644 --- a/aio/tools/transforms/angular-base-package/index.js +++ b/aio/tools/transforms/angular-base-package/index.js @@ -29,6 +29,7 @@ module.exports = new Package('angular-base', [ .processor(require('./processors/convertToJson')) .processor(require('./processors/fixInternalDocumentLinks')) .processor(require('./processors/copyContentAssets')) + .processor(require('./processors/renderLinkInfo')) // overrides base packageInfo and returns the one for the 'angular/angular' repo. .factory('packageInfo', function() { return require(path.resolve(PROJECT_ROOT, 'package.json')); }) diff --git a/aio/tools/transforms/angular-base-package/processors/renderLinkInfo.js b/aio/tools/transforms/angular-base-package/processors/renderLinkInfo.js new file mode 100644 index 0000000000..0a0f71df1f --- /dev/null +++ b/aio/tools/transforms/angular-base-package/processors/renderLinkInfo.js @@ -0,0 +1,52 @@ +/** + * @dgProcessor renderLinkInfo + * @description For each doc that has one of the specified docTypes, + * add HTML comments that describe the links to and from the doc. + */ +module.exports = function renderLinkInfo(extractLinks) { + return { + docTypes: [], + $runBefore: ['convertToJsonProcessor'], + $runAfter: ['fixInternalDocumentLinks'], + $process(docs) { + const toLinks = {}; + const fromLinks = {}; + const docsToCheck = docs.filter(doc => this.docTypes.indexOf(doc.docType) !== -1); + + // Extract and store all links found in each doc in hashes + docsToCheck.forEach(doc => { + const linksFromDoc = extractLinks(doc.renderedContent).hrefs; + // Update the hashes + fromLinks[doc.path] = linksFromDoc; + linksFromDoc.forEach(linkPath => { + linkPath = linkPath.match(/^[^#?]+/)[0]; // remove the query and hash from the link + (toLinks[linkPath] = toLinks[linkPath] || []).push(doc.path); + }); + }); + + // Add HTML comments to the end of the rendered content that list the links found above + docsToCheck.forEach(doc => { + const linksFromDoc = getLinks(fromLinks, doc.path); + const linksToDoc = getLinks(toLinks, doc.path); + doc.renderedContent += + `\n\n` + + ``; + }); + } + }; +}; + +function getLinks(hash, docPath) { + const links = (hash[docPath] || []).filter(link => link !== docPath); + const internal = {}; + const external = {}; + links.forEach(link => { + if (/^[^:/#?]+:/.test(link)) { + external[link] = true; + } else { + internal[link] = true; + } + }); + return Object.keys(internal).sort() + .concat(Object.keys(external).sort()); +} diff --git a/aio/tools/transforms/angular-base-package/processors/renderLinkInfo.spec.js b/aio/tools/transforms/angular-base-package/processors/renderLinkInfo.spec.js new file mode 100644 index 0000000000..08d5cd8cdd --- /dev/null +++ b/aio/tools/transforms/angular-base-package/processors/renderLinkInfo.spec.js @@ -0,0 +1,222 @@ +const testPackage = require('../../helpers/test-package'); +const processorFactory = require('./renderLinkInfo'); +const extractLinks = require('dgeni-packages/base/services/extractLinks')(); +const Dgeni = require('dgeni'); + +describe('renderLinkInfo processor', () => { + + it('should be available on the injector', () => { + const dgeni = new Dgeni([testPackage('angular-base-package')]); + const injector = dgeni.configureInjector(); + const processor = injector.get('renderLinkInfo'); + expect(processor.$process).toBeDefined(); + }); + + it('should run before the correct processor', () => { + const processor = processorFactory(extractLinks); + expect(processor.$runBefore).toEqual(['convertToJsonProcessor']); + }); + + it('should run after the correct processor', () => { + const processor = processorFactory(extractLinks); + expect(processor.$runAfter).toEqual(['fixInternalDocumentLinks']); + }); + + it('should add HTML comments for links out of docs', () => { + const processor = processorFactory(extractLinks); + processor.docTypes = ['test']; + const docs = [ + { path: 'test-1', docType: 'test', renderedContent: '' }, + { path: 'test-2', docType: 'test', renderedContent: '' }, + ]; + processor.$process(docs); + expect(docs).toEqual([ + { + path: 'test-1', + docType: 'test', + renderedContent: '\n' + + '\n' + + '' + }, + { + path: 'test-2', + docType: 'test', + renderedContent: '\n' + + '\n' + + '' + }, + ]); + }); + + it('should order links alphabetically', () => { + const processor = processorFactory(extractLinks); + processor.docTypes = ['test']; + const docs = [ + { path: 'test-1', docType: 'test', renderedContent: '' }, + ]; + processor.$process(docs); + expect(docs).toEqual([ + { + path: 'test-1', + docType: 'test', + renderedContent: '\n' + + '\n' + + '' + }, + ]); + }); + + it('should list repeated links only once', () => { + const processor = processorFactory(extractLinks); + processor.docTypes = ['test']; + const docs = [ + { path: 'test-1', docType: 'test', renderedContent: '' }, + ]; + processor.$process(docs); + expect(docs).toEqual([ + { + path: 'test-1', + docType: 'test', + renderedContent: '\n' + + '\n' + + '' + }, + ]); + }); + + it('should list internal links before external', () => { + const processor = processorFactory(extractLinks); + processor.docTypes = ['test']; + const docs = [ + { path: 'test-1', docType: 'test', renderedContent: '' }, + ]; + processor.$process(docs); + expect(docs).toEqual([ + { + path: 'test-1', + docType: 'test', + renderedContent: '\n' + + '\n' + + '' + }, + ]); + }); + + it('should ignore docs that do not have the specified docType', () => { + const processor = processorFactory(extractLinks); + processor.docTypes = ['test']; + const docs = [ + { path: 'test-1', docType: 'test', renderedContent: '' }, + { path: 'test-2', docType: 'test2', renderedContent: '' }, + ]; + processor.$process(docs); + expect(docs).toEqual([ + { + path: 'test-1', + docType: 'test', + renderedContent: '\n' + + '\n' + + '' + }, + { + path: 'test-2', + docType: 'test2', + renderedContent: '' + }, + ]); + }); + + it('should add HTML comments for links into docs', () => { + const processor = processorFactory(extractLinks); + processor.docTypes = ['test']; + const docs = [ + { path: 'test-1', docType: 'test', renderedContent: '' }, + { path: 'test-2', docType: 'test', renderedContent: '' }, + { path: 'test-3', docType: 'test', renderedContent: '' }, + ]; + processor.$process(docs); + expect(docs).toEqual([ + { + path: 'test-1', + docType: 'test', + renderedContent: '\n' + + '\n' + + '' + }, + { + path: 'test-2', + docType: 'test', + renderedContent: '\n' + + '\n' + + '' + }, + { + path: 'test-3', + docType: 'test', + renderedContent: '\n' + + '\n' + + '' + }, + ]); + }); + + it('should not include links to themselves', () => { + const processor = processorFactory(extractLinks); + processor.docTypes = ['test']; + const docs = [ + { path: 'test-1', docType: 'test', renderedContent: '' }, + { path: 'test-2', docType: 'test', renderedContent: '' }, + ]; + processor.$process(docs); + expect(docs).toEqual([ + { + path: 'test-1', + docType: 'test', + renderedContent: '\n' + + '\n' + + '' + }, + { + path: 'test-2', + docType: 'test', + renderedContent: '\n' + + '\n' + + '' + }, + ]); + }); + + it('should match links that contain fragments or queries', () => { + const processor = processorFactory(extractLinks); + processor.docTypes = ['test']; + const docs = [ + { path: 'test-1', docType: 'test', renderedContent: '' }, + { path: 'test-2', docType: 'test', renderedContent: '' }, + { path: 'test-3', docType: 'test', renderedContent: '' }, + ]; + processor.$process(docs); + expect(docs).toEqual([ + { + path: 'test-1', + docType: 'test', + renderedContent: '\n' + + '\n' + + '' + }, + { + path: 'test-2', + docType: 'test', + renderedContent: '\n' + + '\n' + + '' + }, + { + path: 'test-3', + docType: 'test', + renderedContent: '\n' + + '\n' + + '' + }, + ]); + }); +}); diff --git a/aio/tools/transforms/angular.io-package/index.js b/aio/tools/transforms/angular.io-package/index.js index 1964bb696a..538a15fb85 100644 --- a/aio/tools/transforms/angular.io-package/index.js +++ b/aio/tools/transforms/angular.io-package/index.js @@ -48,4 +48,8 @@ module.exports = new Package('angular.io', [gitPackage, apiPackage, contentPacka }); checkAnchorLinksProcessor.pathVariants = ['', '/', '.html', '/index.html', '#top-of-page']; checkAnchorLinksProcessor.errorOnUnmatchedLinks = true; + }) + + .config(function(renderLinkInfo, postProcessHtml) { + renderLinkInfo.docTypes = postProcessHtml.docTypes; });