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;
});