@microsoft/api-extractor
Version:
Validate, document, and review the exported API for a TypeScript library
216 lines (214 loc) • 8.34 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 });
const PrettyPrinter_1 = require("./PrettyPrinter");
class TypeScriptHelpers {
/**
* Returns the Symbol for the provided Declaration. This is a workaround for a missing
* feature of the TypeScript Compiler API. It is the only apparent way to reach
* certain data structures, and seems to always work, but is not officially documented.
*
* @returns The associated Symbol. If there is no semantic information (e.g. if the
* declaration is an extra semicolon somewhere), then "undefined" is returned.
*/
static tryGetSymbolForDeclaration(declaration) {
/* tslint:disable:no-any */
const symbol = declaration.symbol;
/* tslint:enable:no-any */
return symbol;
}
/**
* Same semantics as tryGetSymbolForDeclaration(), but throws an exception if the symbol
* cannot be found.
*/
static getSymbolForDeclaration(declaration) {
const symbol = TypeScriptHelpers.tryGetSymbolForDeclaration(declaration);
if (!symbol) {
PrettyPrinter_1.default.throwUnexpectedSyntaxError(declaration, 'Unable to determine the semantic information for this declaration');
}
return symbol;
}
/**
* Returns the JSDoc comments associated with the specified node, if any.
*
* Example:
* "This \n is \n a comment" from "\/** This\r\n* is\r\n* a comment *\/
*/
static getJsdocComments(node, errorLogger) {
let jsdoc = '';
// tslint:disable-next-line:no-any
const nodeJsdocObjects = node.jsDoc;
if (nodeJsdocObjects && nodeJsdocObjects.length > 0) {
// Use the JSDoc closest to the declaration
const lastJsdocIndex = nodeJsdocObjects.length - 1;
const jsdocFullText = nodeJsdocObjects[lastJsdocIndex].getText();
const jsdocLines = jsdocFullText.split(TypeScriptHelpers.newLineRegEx);
const jsdocStartSeqExists = TypeScriptHelpers.jsdocStartRegEx.test(jsdocLines[0].toString());
// Report error for each missing sequence separately
if (!jsdocStartSeqExists) {
errorLogger('Jsdoc comment must begin with a \"/**\" sequence.');
return '';
}
const jsdocEndSeqExists = TypeScriptHelpers.jsdocEndRegEx.test(jsdocLines[jsdocLines.length - 1].toString());
if (!jsdocEndSeqExists) {
errorLogger('Jsdoc comment must end with a \"*/\" sequence.');
return '';
}
jsdoc = TypeScriptHelpers.removeJsdocSequences(jsdocLines);
}
return jsdoc;
}
/**
* Helper function to remove the comment stars ('/**'. '*', '/*) from lines of comment text.
*
* Example:
* ["\/**", "*This \n", "*is \n", "*a comment", "*\/"] to "This \n is \n a comment"
*/
static removeJsdocSequences(textLines) {
// Remove '/**'
textLines[0] = textLines[0].replace(TypeScriptHelpers.jsdocStartRegEx, '');
if (textLines[0] === '') {
textLines.shift();
}
// Remove '*/'
textLines[textLines.length - 1] = textLines[textLines.length - 1].replace(TypeScriptHelpers.jsdocEndRegEx, '');
if (textLines[textLines.length - 1] === '') {
textLines.pop();
}
// Remove the leading '*' from any intermediate lines
if (textLines.length > 0) {
for (let i = 0; i < textLines.length; i++) {
textLines[i] = textLines[i].replace(TypeScriptHelpers.jsdocIntermediateRegEx, '');
}
}
return textLines.join('\n');
}
/**
* Similar to calling string.split() with a RegExp, except that the delimiters
* are included in the result.
*
* Example: _splitStringWithRegEx("ABCDaFG", /A/gi) -> [ "A", "BCD", "a", "FG" ]
* Example: _splitStringWithRegEx("", /A/gi) -> [ ]
* Example: _splitStringWithRegEx("", /A?/gi) -> [ "" ]
*/
static splitStringWithRegEx(text, regExp) {
if (!regExp.global) {
throw new Error('RegExp must have the /g flag');
}
if (text === undefined) {
return [];
}
const result = [];
let index = 0;
let match;
do {
match = regExp.exec(text);
if (match) {
if (match.index > index) {
result.push(text.substring(index, match.index));
}
const matchText = match[0];
if (!matchText) {
// It might be interesting to support matching e.g. '\b', but regExp.exec()
// doesn't seem to iterate properly in this situation.
throw new Error('The regular expression must match a nonzero number of characters');
}
result.push(matchText);
index = regExp.lastIndex;
}
} while (match && regExp.global);
if (index < text.length) {
result.push(text.substr(index));
}
return result;
}
/**
* Extracts the body of a TypeScript comment and returns it.
*/
// Examples:
// "/**\n * this is\n * a test\n */\n" --> "this is\na test"
// "/** single line comment */" --> "single line comment"
static extractCommentContent(text) {
const lines = text.replace('\r', '').split('\n');
let State;
(function (State) {
State[State["Start"] = 0] = "Start";
State[State["Body"] = 1] = "Body";
State[State["Done"] = 2] = "Done";
State[State["Error"] = 3] = "Error";
})(State || (State = {}));
let state = State.Start;
const startRegExp = /^\s*\/\*\*+ ?/;
const bodyRegExp = /^\s*\* ?/;
const endRegExp = /^\s*\*+\/\s*$/;
const singleLineEndRegExp = / ?\*+\/\s*$/;
let content = '';
for (const line of lines) {
if (line.trim().length === 0) {
continue;
}
let modified = line;
switch (state) {
case State.Start:
if (line.match(startRegExp)) {
modified = line.replace(startRegExp, '');
if (modified.match(singleLineEndRegExp)) {
modified = modified.replace(singleLineEndRegExp, '');
state = State.Done;
}
else {
state = State.Body;
}
}
else {
state = State.Error;
}
break;
case State.Body:
if (line.match(endRegExp)) {
modified = line.replace(endRegExp, '');
state = State.Done;
}
else if (line.match(bodyRegExp)) {
modified = line.replace(bodyRegExp, '');
}
else {
state = State.Error;
}
break;
case State.Done:
state = State.Error;
break;
}
if (modified !== '') {
if (content !== '') {
content += '\n';
}
content += modified;
}
}
if (state !== State.Done) {
return '[ERROR PARSING COMMENT]';
}
return content;
}
}
/**
* Splits by the characters '\r\n'.
*/
TypeScriptHelpers.newLineRegEx = /\r\n|\n/g;
/**
* Start sequence is '/**'.
*/
TypeScriptHelpers.jsdocStartRegEx = /^\s*\/\*\*\s?/g;
/**
* End sequence is '*\/'.
*/
TypeScriptHelpers.jsdocEndRegEx = /\s*\*\/\s*$/g;
/**
* Intermediate lines of JSDoc comment character.
*/
TypeScriptHelpers.jsdocIntermediateRegEx = /^\s*[*]\s?/g;
exports.default = TypeScriptHelpers;
//# sourceMappingURL=TypeScriptHelpers.js.map