| 
									
										
										
										
											2017-08-18 14:48:40 +01:00
										 |  |  | const visit = require('unist-util-visit-parents'); | 
					
						
							| 
									
										
										
										
											2017-05-10 18:34:19 +01:00
										 |  |  | const is = require('hast-util-is-element'); | 
					
						
							|  |  |  | const textContent = require('hast-util-to-string'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							| 
									
										
										
										
											2017-09-09 09:15:08 +01:00
										 |  |  |  * Automatically add in a link to the relevant document for code blocks. | 
					
						
							|  |  |  |  * E.g. `<code>MyClass</code>` becomes `<code><a href="path/to/myclass">MyClass</a></code>` | 
					
						
							| 
									
										
										
										
											2017-05-10 18:34:19 +01:00
										 |  |  |  * | 
					
						
							| 
									
										
										
										
											2017-09-09 09:15:08 +01:00
										 |  |  |  * @property docTypes an array of strings. | 
					
						
							|  |  |  |  * Only docs that have one of these docTypes will be linked to. | 
					
						
							| 
									
										
										
										
											2017-05-10 18:34:19 +01:00
										 |  |  |  * Usually set to the API exported docTypes, e.g. "class", "function", "directive", etc. | 
					
						
							| 
									
										
										
										
											2017-09-09 09:15:08 +01:00
										 |  |  |  * | 
					
						
							| 
									
										
										
										
											2017-11-15 23:30:09 +00:00
										 |  |  |  * @property customFilters array of functions `(docs, words, wordIndex) => docs` that will filter | 
					
						
							|  |  |  |  * out docs where a word should not link to a doc. | 
					
						
							|  |  |  |  *   - `docs` is the array of docs that match the link `word` | 
					
						
							|  |  |  |  *   - `words` is the collection of words parsed from the code text | 
					
						
							|  |  |  |  *   - `wordIndex` is the index of the current `word` for which we are finding a link | 
					
						
							|  |  |  |  * | 
					
						
							| 
									
										
										
										
											2017-09-09 09:15:08 +01:00
										 |  |  |  * @property codeElements an array of strings. | 
					
						
							|  |  |  |  * Only text contained in these elements will be linked to. | 
					
						
							|  |  |  |  * Usually set to "code" but also "code-example" for angular.io. | 
					
						
							| 
									
										
										
										
											2017-05-10 18:34:19 +01:00
										 |  |  |  */ | 
					
						
							|  |  |  | module.exports = function autoLinkCode(getDocFromAlias) { | 
					
						
							|  |  |  |   autoLinkCodeImpl.docTypes = []; | 
					
						
							| 
									
										
										
										
											2017-11-15 23:30:09 +00:00
										 |  |  |   autoLinkCodeImpl.customFilters = []; | 
					
						
							| 
									
										
										
										
											2017-09-09 09:15:08 +01:00
										 |  |  |   autoLinkCodeImpl.codeElements = ['code']; | 
					
						
							| 
									
										
										
										
											2019-11-16 22:12:04 +00:00
										 |  |  |   autoLinkCodeImpl.ignoredLanguages = ['bash', 'sh', 'shell', 'json', 'markdown']; | 
					
						
							| 
									
										
										
										
											2017-05-10 18:34:19 +01:00
										 |  |  |   return autoLinkCodeImpl; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-16 22:18:34 +00:00
										 |  |  |   function autoLinkCodeImpl() { | 
					
						
							| 
									
										
										
										
											2020-03-30 07:10:15 -04:00
										 |  |  |     return (ast, file) => { | 
					
						
							| 
									
										
										
										
											2017-09-09 09:15:08 +01:00
										 |  |  |       visit(ast, 'element', (node, ancestors) => { | 
					
						
							| 
									
										
										
										
											2020-04-17 06:59:18 -04:00
										 |  |  |         if (!isValidCodeElement(node, ancestors)) { | 
					
						
							|  |  |  |           return; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2017-09-09 09:15:08 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-17 06:59:18 -04:00
										 |  |  |         visit(node, 'text', (node, ancestors) => { | 
					
						
							|  |  |  |           const isInLink = isInsideLink(ancestors); | 
					
						
							|  |  |  |           if (isInLink) { | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |           } | 
					
						
							| 
									
										
										
										
											2017-09-09 09:15:08 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-17 06:59:18 -04:00
										 |  |  |           const parent = ancestors[ancestors.length - 1]; | 
					
						
							|  |  |  |           const index = parent.children.indexOf(node); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           // Can we convert the whole text node into a doc link?
 | 
					
						
							|  |  |  |           const docs = getDocFromAlias(node.value); | 
					
						
							|  |  |  |           if (foundValidDoc(docs, node.value, file)) { | 
					
						
							|  |  |  |             parent.children.splice(index, 1, createLinkNode(docs[0], node.value)); | 
					
						
							|  |  |  |           } else { | 
					
						
							|  |  |  |             // Parse the text for words that we can convert to links
 | 
					
						
							|  |  |  |             const nodes = getNodes(node, file); | 
					
						
							|  |  |  |             // Replace the text node with the links and leftover text nodes
 | 
					
						
							|  |  |  |             Array.prototype.splice.apply(parent.children, [index, 1].concat(nodes)); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2017-05-10 18:34:19 +01:00
										 |  |  |       }); | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-06-14 14:02:56 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-17 06:59:18 -04:00
										 |  |  |   function isValidCodeElement(node, ancestors) { | 
					
						
							|  |  |  |     // Only interested in code elements that:
 | 
					
						
							|  |  |  |     // * do not have `no-auto-link` class
 | 
					
						
							|  |  |  |     // * do not have an ignored language
 | 
					
						
							|  |  |  |     // * are not inside links
 | 
					
						
							|  |  |  |     const isCodeElement = autoLinkCodeImpl.codeElements.some(elementType => is(node, elementType)); | 
					
						
							|  |  |  |     const hasNoAutoLink = node.properties.className && node.properties.className.includes('no-auto-link'); | 
					
						
							|  |  |  |     const isLanguageSupported = !autoLinkCodeImpl.ignoredLanguages.includes(node.properties.language); | 
					
						
							|  |  |  |     const isInLink = isInsideLink(ancestors); | 
					
						
							|  |  |  |     return isCodeElement && !hasNoAutoLink && isLanguageSupported && !isInLink; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function isInsideLink(ancestors) { | 
					
						
							|  |  |  |     return ancestors.some(ancestor => is(ancestor, 'a')); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function getNodes(node, file) { | 
					
						
							|  |  |  |     return textContent(node) | 
					
						
							|  |  |  |       .split(/([A-Za-z0-9_.-]+)/) | 
					
						
							|  |  |  |       .filter(word => word.length) | 
					
						
							|  |  |  |       .map((word, index, words) => { | 
					
						
							|  |  |  |         // remove docs that fail the custom filter tests
 | 
					
						
							|  |  |  |         const filteredDocs = autoLinkCodeImpl.customFilters.reduce( | 
					
						
							|  |  |  |             (docs, filter) => filter(docs, words, index), getDocFromAlias(word)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return foundValidDoc(filteredDocs, word, file) ? | 
					
						
							|  |  |  |             // Create a link wrapping the text node.
 | 
					
						
							|  |  |  |             createLinkNode(filteredDocs[0], word) : | 
					
						
							|  |  |  |             // this is just text so push a new text node
 | 
					
						
							|  |  |  |             {type: 'text', value: word}; | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-30 07:10:15 -04:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * Validates the docs to be used to generate the links. The validation ensures | 
					
						
							|  |  |  |    * that the docs are not `internal` and that the `docType` is supported. The `path` | 
					
						
							|  |  |  |    * can be empty when the `API` is not public. | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * @param {Array<Object>} docs An array of objects containing the doc details | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * @param {string} keyword The keyword the doc applies to | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   function foundValidDoc(docs, keyword, file) { | 
					
						
							|  |  |  |     if (docs.length !== 1) { | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     var doc = docs[0]; | 
					
						
							| 
									
										
										
										
											2020-04-14 07:44:14 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     const isInvalidDoc = doc.docType === 'member' && !keyword.includes('.'); | 
					
						
							|  |  |  |     if (isInvalidDoc) { | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-30 07:10:15 -04:00
										 |  |  |     if (doc.path === '') { | 
					
						
							|  |  |  |       var message = `
 | 
					
						
							|  |  |  |       autoLinkCode: Doc path is empty for "${doc.id}" - link will not be generated for "${keyword}". | 
					
						
							|  |  |  |       Please make sure if the doc should be public. If not, it should probably not be referenced in the docs.`;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       file.message(message); | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return !doc.internal && autoLinkCodeImpl.docTypes.includes(doc.docType); | 
					
						
							| 
									
										
										
										
											2017-09-09 09:15:08 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function createLinkNode(doc, text) { | 
					
						
							|  |  |  |     return { | 
					
						
							|  |  |  |       type: 'element', | 
					
						
							|  |  |  |       tagName: 'a', | 
					
						
							| 
									
										
										
										
											2019-11-16 22:18:34 +00:00
										 |  |  |       properties: {href: doc.path, class: 'code-anchor'}, | 
					
						
							|  |  |  |       children: [{type: 'text', value: text}] | 
					
						
							| 
									
										
										
										
											2017-09-09 09:15:08 +01:00
										 |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2017-05-10 18:34:19 +01:00
										 |  |  | }; |