in src/jsdoc.ts [212:289]
export function parseContents(commentText: string): ParsedJSDocComment|null {
// Make sure we have proper line endings before parsing on Windows.
commentText = normalizeLineEndings(commentText);
// Strip all the " * " bits from the front of each line.
commentText = commentText.replace(/^\s*\*? ?/gm, '');
const lines = commentText.split('\n');
const tags: Tag[] = [];
const warnings: string[] = [];
for (const line of lines) {
let match = line.match(/^\s*@(\S+) *(.*)/);
if (match) {
let [_, tagName, text] = match;
if (tagName === 'returns') {
// A synonym for 'return'.
tagName = 'return';
}
let type: string|undefined;
if (BANNED_JSDOC_TAGS_INPUT.has(tagName)) {
if (tagName !== 'template') {
// Tell the user to not write banned tags, because there is TS
// syntax available for them.
warnings.push(`@${tagName} annotations are redundant with TypeScript equivalents`);
continue; // Drop the tag so Closure won't process it.
} else {
// But @template in particular is special: it's ok for the user to
// write it for documentation purposes, but we don't want the
// user-written one making it into the output because Closure interprets
// it as well.
// Drop it without any warning. (We also don't ensure its correctness.)
continue;
}
} else if (JSDOC_TAGS_WITH_TYPES.has(tagName)) {
if (text[0] === '{') {
warnings.push(
`the type annotation on @${tagName} is redundant with its TypeScript type, ` +
`remove the {...} part`);
continue;
}
} else if (tagName === 'suppress') {
const typeMatch = text.match(/^\{(.*)\}(.*)$/);
if (typeMatch) {
[, type, text] = typeMatch;
} else {
warnings.push(`malformed @${tagName} tag: "${text}"`);
}
} else if (tagName === 'dict') {
warnings.push('use index signatures (`[k: string]: type`) instead of @dict');
continue;
}
// Grab the parameter name from @param tags.
let parameterName: string|undefined;
if (tagName === 'param') {
match = text.match(/^(\S+) ?(.*)/);
if (match) [_, parameterName, text] = match;
}
const tag: Tag = {tagName};
if (parameterName) tag.parameterName = parameterName;
if (text) tag.text = text;
if (type) tag.type = type;
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({tagName: '', text: line});
} else {
const lastTag = tags[tags.length - 1];
lastTag.text = (lastTag.text || '') + '\n' + line;
}
}
}
if (warnings.length > 0) {
return {tags, warnings};
}
return {tags};
}