@microsoft/api-extractor
Version:
Validate, document, and review the exported API for a TypeScript library
350 lines (348 loc) • 12.4 kB
JavaScript
;
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Provides various operations for working with MarkupElement objects.
*
* @public
*/
class Markup {
/**
* Constructs an IMarkupText element representing the specified text string, with
* optional formatting.
*
* @remarks
* NOTE: All whitespace (including newlines) will be collapsed to single spaces.
* This behavior is similar to how HTML handles whitespace. To preserve
* newlines, use {@link Markup.createTextParagraphs} instead.
*/
static createTextElements(text, options) {
if (!text) {
return [];
}
else {
const result = {
kind: 'text',
text: Markup._trimRawText(text)
};
if (options) {
if (options.bold) {
result.bold = true;
}
if (options.italics) {
result.italics = true;
}
}
// The return value is represented as an array containing at most one element.
// Another possible design would be to return a single IMarkupText object that
// is possibly undefined; however, in practice appending arrays turns out to be
// more concise than checking for undefined.
return [result];
}
}
/**
* This function is similar to {@link Markup.createTextElements}, except that multiple newlines
* will be converted to a Markup.PARAGRAPH object.
*/
static createTextParagraphs(text, options) {
const result = [];
if (text) {
// Split up the paragraphs
for (const paragraph of text.split(/\n\s*\n/g)) {
if (result.length > 0) {
result.push(Markup.PARAGRAPH);
}
result.push(...Markup.createTextElements(paragraph, options));
}
}
return result;
}
/**
* Constructs an IMarkupApiLink element that represents a hyperlink to the specified
* API object. The hyperlink is applied to an existing stream of markup elements.
* @param textElements - the markup sequence that will serve as the link text
* @param target - the API object that the hyperlink will point to
*/
static createApiLink(textElements, target) {
if (!textElements.length) {
throw new Error('Missing text for link');
}
if (!target.packageName || target.packageName.length < 1) {
throw new Error('The IApiItemReference.packageName cannot be empty');
}
return {
kind: 'api-link',
elements: textElements,
target: target
};
}
/**
* Constructs an IMarkupApiLink element that represents a hyperlink to the specified
* API object. The hyperlink is applied to a plain text string.
* @param text - the text string that will serve as the link text
* @param target - the API object that the hyperlink will point to
*/
static createApiLinkFromText(text, target) {
return Markup.createApiLink(Markup.createTextElements(text), target);
}
/**
* Constructs an IMarkupWebLink element that represents a hyperlink an internet URL.
* @param textElements - the markup sequence that will serve as the link text
* @param targetUrl - the URL that the hyperlink will point to
*/
static createWebLink(textElements, targetUrl) {
if (!textElements.length) {
throw new Error('Missing text for link');
}
if (!targetUrl || !targetUrl.trim()) {
throw new Error('Missing link target');
}
return {
kind: 'web-link',
elements: textElements,
targetUrl: targetUrl
};
}
/**
* Constructs an IMarkupWebLink element that represents a hyperlink an internet URL.
* @param text - the plain text string that will serve as the link text
* @param targetUrl - the URL that the hyperlink will point to
*/
static createWebLinkFromText(text, targetUrl) {
return Markup.createWebLink(Markup.createTextElements(text), targetUrl);
}
/**
* Constructs an IMarkupHighlightedText element representing a program code text
* with optional syntax highlighting
*/
static createCode(code, highlighter) {
if (!code) {
throw new Error('The code parameter is missing');
}
return {
kind: 'code',
text: code,
highlighter: highlighter || 'plain'
};
}
/**
* Constructs an IMarkupHeading1 element with the specified title text
*/
static createHeading1(text) {
return {
kind: 'heading1',
text: Markup._trimRawText(text)
};
}
/**
* Constructs an IMarkupHeading2 element with the specified title text
*/
static createHeading2(text) {
return {
kind: 'heading2',
text: Markup._trimRawText(text)
};
}
/**
* Constructs an IMarkupCodeBox element representing a program code text
* with the specified syntax highlighting
*/
static createCodeBox(code, highlighter) {
if (!code) {
throw new Error('The code parameter is missing');
}
return {
kind: 'code-box',
text: code,
highlighter: highlighter
};
}
/**
* Constructs an IMarkupNoteBox element that will display the specified markup content
*/
static createNoteBox(textElements) {
return {
kind: 'note-box',
elements: textElements
};
}
/**
* Constructs an IMarkupNoteBox element that will display the specified plain text string
*/
static createNoteBoxFromText(text) {
return Markup.createNoteBox(Markup.createTextElements(text));
}
/**
* Constructs an IMarkupTableRow element containing the specified cells, which each contain a
* sequence of MarkupBasicElement content
*/
static createTableRow(cellValues = undefined) {
const row = {
kind: 'table-row',
cells: []
};
if (cellValues) {
for (const cellValue of cellValues) {
const cell = {
kind: 'table-cell',
elements: cellValue
};
row.cells.push(cell);
}
}
return row;
}
/**
* Constructs an IMarkupTable element containing the specified header cells, which each contain a
* sequence of MarkupBasicElement content.
* @remarks
* The table initially has zero rows.
*/
static createTable(headerCellValues = undefined) {
let header = undefined;
if (headerCellValues) {
header = Markup.createTableRow(headerCellValues);
}
return {
kind: 'table',
header: header,
rows: []
};
}
/**
* Constructs an IMarkupTable element with the specified title.
*/
static createPage(title) {
return {
kind: 'page',
breadcrumb: [],
title: Markup._trimRawText(title),
elements: []
};
}
/**
* Extracts plain text from the provided markup elements, discarding any formatting.
*
* @remarks
* The returned string is suitable for counting words or extracting search keywords.
* Its formatting is not guaranteed, and may change in future updates of this API.
*
* API Extractor determines whether an API is "undocumented" by using extractTextContent()
* to extract the text from its summary, and then counting the number of words.
*/
static extractTextContent(elements) {
// Pass a buffer, since "+=" uses less memory than "+"
const buffer = { text: '' };
Markup._extractTextContent(elements, buffer);
return buffer.text;
}
/**
* Use this to clean up a MarkupElement sequence, assuming the sequence is now in
* its final form.
*
* @remarks
* The following operations are performed:
*
* 1. Remove leading/trailing white space around paragraphs
*
* 2. Remove redundant paragraph elements
*/
static normalize(elements) {
let i = 0;
while (i < elements.length) {
const element = elements[i];
const previousElement = i - 1 >= 0 ? elements[i - 1] : undefined;
const nextElement = i + 1 < elements.length ? elements[i + 1] : undefined;
const paragraphBefore = !!(previousElement && previousElement.kind === 'paragraph');
const paragraphAfter = !!(nextElement && nextElement.kind === 'paragraph');
if (element.kind === 'paragraph') {
if (i === 0 || i === elements.length - 1 || paragraphBefore) {
// Delete this element. We do not update i because the "previous" item
// is unchanged on the next loop.
elements.splice(i, 1);
continue;
}
}
else if (element.kind === 'text') {
const textElement = element;
if (paragraphBefore || i === 0) {
textElement.text = textElement.text.replace(/^\s+/, ''); // trim left
}
if (paragraphAfter || i === elements.length - 1) {
textElement.text = textElement.text.replace(/\s+$/, ''); // trim right
}
}
++i;
}
}
static _extractTextContent(elements, buffer) {
for (const element of elements) {
switch (element.kind) {
case 'api-link':
buffer.text += Markup.extractTextContent(element.elements);
break;
case 'break':
buffer.text += '\n';
break;
case 'code':
case 'code-box':
break;
case 'heading1':
case 'heading2':
buffer.text += element.text;
break;
case 'note-box':
buffer.text += Markup.extractTextContent(element.elements);
break;
case 'page':
buffer.text += element.title + '\n';
buffer.text += Markup.extractTextContent(element.elements);
break;
case 'paragraph':
buffer.text += '\n\n';
break;
case 'table':
if (element.header) {
buffer.text += Markup.extractTextContent([element.header]);
}
buffer.text += Markup.extractTextContent(element.rows);
break;
case 'table-cell':
buffer.text += Markup.extractTextContent(element.elements);
buffer.text += '\n';
break;
case 'table-row':
buffer.text += Markup.extractTextContent(element.cells);
buffer.text += '\n';
break;
case 'text':
buffer.text += element.text;
break;
case 'web-link':
buffer.text += Markup.extractTextContent(element.elements);
break;
default:
throw new Error('Unsupported element kind');
}
}
}
static _trimRawText(text) {
// Replace multiple whitespaces with a single space
return text.replace(/\s+/g, ' ');
}
}
/**
* A predefined constant for the IMarkupLineBreak element.
*/
Markup.BREAK = {
kind: 'break'
};
/**
* A predefined constant for the IMarkupParagraph element.
*/
Markup.PARAGRAPH = {
kind: 'paragraph'
};
exports.Markup = Markup;
//# sourceMappingURL=Markup.js.map