tsickle
Version:
Transpile TypeScript code to JavaScript with Closure annotations.
264 lines (262 loc) • 8.54 kB
JavaScript
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
;
function arrayIncludes(array, key) {
for (var _i = 0, array_1 = array; _i < array_1.length; _i++) {
var elem = array_1[_i];
if (elem === key)
return true;
}
return false;
}
/**
* A list of all JSDoc tags allowed by the Closure compiler.
* The public Closure docs don't list all the tags it allows; this list comes
* from the compiler source itself.
* https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/parsing/Annotation.java
*/
var JSDOC_TAGS_WHITELIST = [
'ngInject',
'abstract',
'argument',
'author',
'consistentIdGenerator',
'const',
'constant',
'constructor',
'copyright',
'define',
'deprecated',
'desc',
'dict',
'disposes',
'enum',
'export',
'expose',
'extends',
'externs',
'fileoverview',
'final',
'hidden',
'idGenerator',
'implements',
'implicitCast',
'inheritDoc',
'interface',
'record',
'jaggerInject',
'jaggerModule',
'jaggerProvidePromise',
'jaggerProvide',
'lends',
'license',
'meaning',
'modifies',
'noalias',
'nocollapse',
'nocompile',
'nosideeffects',
'override',
'owner',
'package',
'param',
'polymerBehavior',
'preserve',
'preserveTry',
'private',
'protected',
'public',
'return',
'returns',
'see',
'stableIdGenerator',
'struct',
'suppress',
'template',
'this',
'throws',
'type',
'typedef',
'unrestricted',
'version',
'wizaction',
];
/**
* A list of JSDoc @tags that are never allowed in TypeScript source.
* These are Closure tags that can be expressed in the TypeScript surface
* syntax.
*/
var JSDOC_TAGS_BLACKLIST = ['constructor', 'extends', 'implements', 'private', 'public', 'type'];
/** A list of JSDoc @tags that might include a {type} after them. */
var JSDOC_TAGS_WITH_TYPES = ['export', 'param', 'return'];
/**
* parse parses JSDoc out of a comment string.
* Returns null if comment is not JSDoc.
*/
function parse(comment) {
// TODO(evanm): this is a pile of hacky regexes for now, because we
// would rather use the better TypeScript implementation of JSDoc
// parsing. https://github.com/Microsoft/TypeScript/issues/7393
var match = comment.match(/^\/\*\*([\s\S]*?)\*\/$/);
if (!match)
return null;
comment = match[1].trim();
// Strip all the " * " bits from the front of each line.
comment = comment.replace(/^\s*\*? ?/gm, '');
var lines = comment.split('\n');
var tags = [];
var warnings = [];
for (var _i = 0, lines_1 = lines; _i < lines_1.length; _i++) {
var line = lines_1[_i];
match = line.match(/^@(\S+) *(.*)/);
if (match) {
var _ = match[0], tagName = match[1], text = match[2];
if (tagName === 'returns') {
// A synonym for 'return'.
tagName = 'return';
}
if (arrayIncludes(JSDOC_TAGS_BLACKLIST, tagName)) {
warnings.push("@" + tagName + " annotations are redundant with TypeScript equivalents");
continue; // Drop the tag so Closure won't process it.
}
else if (arrayIncludes(JSDOC_TAGS_WITH_TYPES, tagName) && text[0] === '{') {
warnings.push('type annotations (using {...}) are redundant with TypeScript types');
continue;
}
// Grab the parameter name from @param tags.
var parameterName = void 0;
if (tagName === 'param') {
match = text.match(/^(\S+) ?(.*)/);
if (match)
_ = match[0], parameterName = match[1], text = match[2];
}
var tag = { tagName: tagName };
if (parameterName)
tag.parameterName = parameterName;
if (text)
tag.text = text;
tags.push(tag);
}
else {
// Text without a preceding @tag on it is either the plain text
// documentation or a continuation of a previous tag.
if (tags.length === 0) {
tags.push({ text: line });
}
else {
var lastTag = tags[tags.length - 1];
lastTag.text = (lastTag.text || '') + '\n' + line;
}
}
}
if (warnings.length > 0) {
return { tags: tags, warnings: warnings };
}
return { tags: tags };
}
exports.parse = parse;
/**
* Serializes a Tag into a string usable in a comment.
* Returns a string like " @foo {bar} baz" (note the whitespace).
*/
function tagToString(tag, escapeExtraTags) {
if (escapeExtraTags === void 0) { escapeExtraTags = []; }
var out = '';
if (tag.tagName) {
if (!arrayIncludes(JSDOC_TAGS_WHITELIST, tag.tagName) ||
arrayIncludes(escapeExtraTags, tag.tagName)) {
// Escape tags we don't understand. This is a subtle
// compromise between multiple issues.
// 1) If we pass through these non-Closure tags, the user will
// get a warning from Closure, and the point of tsickle is
// to insulate the user from Closure.
// 2) The output of tsickle is for Closure but also may be read
// by humans, for example non-TypeScript users of Angular.
// 3) Finally, we don't want to warn because users should be
// free to add whichever JSDoc they feel like. If the user
// wants help ensuring they didn't typo a tag, that is the
// responsibility of a linter.
out += " \\@" + tag.tagName;
}
else {
out += " @" + tag.tagName;
}
}
if (tag.type) {
out += ' {';
if (tag.restParam) {
out += '...';
}
out += tag.type;
if (tag.optional) {
out += '=';
}
out += '}';
}
if (tag.parameterName) {
out += ' ' + tag.parameterName;
}
if (tag.text) {
out += ' ' + tag.text.replace(/@/g, '\\@');
}
return out;
}
/** Serializes a Comment out to a string usable in source code. */
function toString(tags, escapeExtraTags) {
if (escapeExtraTags === void 0) { escapeExtraTags = []; }
if (tags.length === 0)
return '';
if (tags.length === 1) {
var tag = tags[0];
if (tag.tagName === 'type' && (!tag.text || !tag.text.match('\n'))) {
// Special-case one-liner "type" tags to fit on one line, e.g.
// /** @type {foo} */
return '/**' + tagToString(tag, escapeExtraTags) + ' */\n';
}
}
var out = '';
out += '/**\n';
for (var _i = 0, tags_1 = tags; _i < tags_1.length; _i++) {
var tag = tags_1[_i];
out += ' *';
// If the tagToString is multi-line, insert " * " prefixes on subsequent lines.
out += tagToString(tag, escapeExtraTags).split('\n').join('\n * ');
out += '\n';
}
out += ' */\n';
return out;
}
exports.toString = toString;
/** Merges multiple tags (of the same tagName type) into a single unified tag. */
function merge(tags) {
var tagNames = new Set();
var parameterNames = new Set();
var types = new Set();
var texts = new Set();
for (var _i = 0, tags_2 = tags; _i < tags_2.length; _i++) {
var tag = tags_2[_i];
if (tag.tagName)
tagNames.add(tag.tagName);
if (tag.parameterName)
parameterNames.add(tag.parameterName);
if (tag.type)
types.add(tag.type);
if (tag.text)
texts.add(tag.text);
}
if (tagNames.size !== 1) {
throw new Error("cannot merge differing tags: " + JSON.stringify(tags));
}
var tagName = tagNames.values().next().value;
var parameterName = parameterNames.size > 0 ? Array.from(parameterNames).join('_or_') : undefined;
var type = types.size > 0 ? Array.from(types).join('|') : undefined;
var text = texts.size > 0 ? Array.from(texts).join(' / ') : undefined;
return { tagName: tagName, parameterName: parameterName, type: type, text: text };
}
exports.merge = merge;
//# sourceMappingURL=jsdoc.js.map