2017-03-01 18:28:51 +01:00
|
|
|
/* @internal */
|
2016-09-07 16:04:46 +02:00
|
|
|
namespace ts.JsDoc {
|
|
|
|
const jsDocTagNames = [
|
2018-06-27 00:46:24 +02:00
|
|
|
"abstract",
|
|
|
|
"access",
|
|
|
|
"alias",
|
|
|
|
"argument",
|
|
|
|
"async",
|
2016-09-07 16:04:46 +02:00
|
|
|
"augments",
|
|
|
|
"author",
|
|
|
|
"borrows",
|
2018-05-17 18:28:11 +02:00
|
|
|
"callback",
|
2016-09-07 16:04:46 +02:00
|
|
|
"class",
|
2018-07-14 01:02:20 +02:00
|
|
|
"classdesc",
|
2016-09-07 16:04:46 +02:00
|
|
|
"constant",
|
|
|
|
"constructor",
|
|
|
|
"constructs",
|
2018-06-27 00:46:24 +02:00
|
|
|
"copyright",
|
2016-09-07 16:04:46 +02:00
|
|
|
"default",
|
|
|
|
"deprecated",
|
|
|
|
"description",
|
2018-06-27 00:46:24 +02:00
|
|
|
"emits",
|
|
|
|
"enum",
|
2016-09-07 16:04:46 +02:00
|
|
|
"event",
|
|
|
|
"example",
|
2018-06-27 00:46:24 +02:00
|
|
|
"exports",
|
2016-09-07 16:04:46 +02:00
|
|
|
"extends",
|
2018-06-27 00:46:24 +02:00
|
|
|
"external",
|
2016-09-07 16:04:46 +02:00
|
|
|
"field",
|
2018-06-27 00:46:24 +02:00
|
|
|
"file",
|
2018-07-14 01:02:20 +02:00
|
|
|
"fileoverview",
|
2018-06-27 00:46:24 +02:00
|
|
|
"fires",
|
2016-09-07 16:04:46 +02:00
|
|
|
"function",
|
2018-06-27 00:46:24 +02:00
|
|
|
"generator",
|
|
|
|
"global",
|
2018-07-14 01:02:20 +02:00
|
|
|
"hideconstructor",
|
2018-06-27 00:46:24 +02:00
|
|
|
"host",
|
2016-09-07 16:04:46 +02:00
|
|
|
"ignore",
|
2018-06-27 00:46:24 +02:00
|
|
|
"implements",
|
2018-07-14 01:02:20 +02:00
|
|
|
"inheritdoc",
|
2016-09-07 16:04:46 +02:00
|
|
|
"inner",
|
2018-06-27 00:46:24 +02:00
|
|
|
"instance",
|
|
|
|
"interface",
|
|
|
|
"kind",
|
2016-09-07 16:04:46 +02:00
|
|
|
"lends",
|
2018-06-27 00:46:24 +02:00
|
|
|
"license",
|
2021-04-02 02:02:50 +02:00
|
|
|
"link",
|
2018-06-27 00:46:24 +02:00
|
|
|
"listens",
|
|
|
|
"member",
|
2018-07-14 01:02:20 +02:00
|
|
|
"memberof",
|
2016-12-14 01:36:54 +01:00
|
|
|
"method",
|
2018-06-27 00:46:24 +02:00
|
|
|
"mixes",
|
|
|
|
"module",
|
2016-09-07 16:04:46 +02:00
|
|
|
"name",
|
|
|
|
"namespace",
|
2018-06-27 00:46:24 +02:00
|
|
|
"override",
|
|
|
|
"package",
|
2016-09-07 16:04:46 +02:00
|
|
|
"param",
|
|
|
|
"private",
|
|
|
|
"property",
|
2018-06-27 00:46:24 +02:00
|
|
|
"protected",
|
2016-09-07 16:04:46 +02:00
|
|
|
"public",
|
2018-06-27 00:46:24 +02:00
|
|
|
"readonly",
|
2016-09-07 16:04:46 +02:00
|
|
|
"requires",
|
|
|
|
"returns",
|
|
|
|
"see",
|
|
|
|
"since",
|
|
|
|
"static",
|
2018-06-27 00:46:24 +02:00
|
|
|
"summary",
|
2018-02-17 01:27:57 +01:00
|
|
|
"template",
|
2018-06-27 00:46:24 +02:00
|
|
|
"this",
|
2016-09-07 16:04:46 +02:00
|
|
|
"throws",
|
2018-06-27 00:46:24 +02:00
|
|
|
"todo",
|
|
|
|
"tutorial",
|
2016-09-07 16:04:46 +02:00
|
|
|
"type",
|
|
|
|
"typedef",
|
2018-06-27 00:46:24 +02:00
|
|
|
"var",
|
|
|
|
"variation",
|
|
|
|
"version",
|
|
|
|
"virtual",
|
|
|
|
"yields"
|
2016-09-07 16:04:46 +02:00
|
|
|
];
|
2017-03-01 00:41:35 +01:00
|
|
|
let jsDocTagNameCompletionEntries: CompletionEntry[];
|
2017-03-02 02:46:35 +01:00
|
|
|
let jsDocTagCompletionEntries: CompletionEntry[];
|
2016-09-07 16:04:46 +02:00
|
|
|
|
2021-03-23 00:39:35 +01:00
|
|
|
export function getJsDocCommentsFromDeclarations(declarations: readonly Declaration[], checker?: TypeChecker): SymbolDisplayPart[] {
|
2016-09-15 20:53:04 +02:00
|
|
|
// Only collect doc comments from duplicate declarations once:
|
|
|
|
// In case of a union property there might be same declaration multiple times
|
|
|
|
// which only varies in type parameter
|
|
|
|
// Eg. const a: Array<string> | Array<number>; a.length
|
|
|
|
// The property length will have two declarations of property length coming
|
|
|
|
// from Array<T> - Array<string> and Array<number>
|
2021-03-23 00:39:35 +01:00
|
|
|
const parts: SymbolDisplayPart[][] = [];
|
2016-09-15 20:53:04 +02:00
|
|
|
forEachUnique(declarations, declaration => {
|
2021-06-24 01:19:14 +02:00
|
|
|
for (const jsdoc of getCommentHavingNodes(declaration)) {
|
|
|
|
// skip comments containing @typedefs since they're not associated with particular declarations
|
|
|
|
// Exceptions:
|
|
|
|
// - @typedefs are themselves declarations with associated comments
|
|
|
|
// - @param or @return indicate that the author thinks of it as a 'local' @typedef that's part of the function documentation
|
|
|
|
if (jsdoc.comment === undefined
|
|
|
|
|| isJSDoc(jsdoc)
|
|
|
|
&& declaration.kind !== SyntaxKind.JSDocTypedefTag && declaration.kind !== SyntaxKind.JSDocCallbackTag
|
|
|
|
&& jsdoc.tags
|
|
|
|
&& jsdoc.tags.some(t => t.kind === SyntaxKind.JSDocTypedefTag || t.kind === SyntaxKind.JSDocCallbackTag)
|
|
|
|
&& !jsdoc.tags.some(t => t.kind === SyntaxKind.JSDocParameterTag || t.kind === SyntaxKind.JSDocReturnTag)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const newparts = getDisplayPartsFromComment(jsdoc.comment, checker);
|
2021-03-23 00:39:35 +01:00
|
|
|
if (!contains(parts, newparts, isIdenticalListOfDisplayParts)) {
|
|
|
|
parts.push(newparts);
|
|
|
|
}
|
2018-01-11 19:49:39 +01:00
|
|
|
}
|
2016-09-15 20:53:04 +02:00
|
|
|
});
|
2021-03-23 00:39:35 +01:00
|
|
|
return flatten(intersperse(parts, [lineBreakPart()]));
|
|
|
|
}
|
|
|
|
|
|
|
|
function isIdenticalListOfDisplayParts(parts1: SymbolDisplayPart[], parts2: SymbolDisplayPart[]) {
|
|
|
|
return arraysEqual(parts1, parts2, (p1, p2) => p1.kind === p2.kind && p1.text === p2.text);
|
2016-09-15 20:53:04 +02:00
|
|
|
}
|
2016-09-07 16:04:46 +02:00
|
|
|
|
2019-08-08 20:30:18 +02:00
|
|
|
function getCommentHavingNodes(declaration: Declaration): readonly (JSDoc | JSDocTag)[] {
|
2018-01-11 19:49:39 +01:00
|
|
|
switch (declaration.kind) {
|
2018-05-17 18:28:11 +02:00
|
|
|
case SyntaxKind.JSDocParameterTag:
|
2018-01-11 19:49:39 +01:00
|
|
|
case SyntaxKind.JSDocPropertyTag:
|
|
|
|
return [declaration as JSDocPropertyTag];
|
2018-05-17 18:28:11 +02:00
|
|
|
case SyntaxKind.JSDocCallbackTag:
|
2018-01-11 19:49:39 +01:00
|
|
|
case SyntaxKind.JSDocTypedefTag:
|
2018-05-17 18:28:11 +02:00
|
|
|
return [(declaration as JSDocTypedefTag), (declaration as JSDocTypedefTag).parent];
|
2018-01-11 19:49:39 +01:00
|
|
|
default:
|
|
|
|
return getJSDocCommentsAndTags(declaration);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-23 00:39:35 +01:00
|
|
|
export function getJsDocTagsFromDeclarations(declarations?: Declaration[], checker?: TypeChecker): JSDocTagInfo[] {
|
2016-12-13 00:29:29 +01:00
|
|
|
// Only collect doc comments from duplicate declarations once.
|
2021-06-24 01:19:14 +02:00
|
|
|
const infos: JSDocTagInfo[] = [];
|
2016-12-13 00:29:29 +01:00
|
|
|
forEachUnique(declarations, declaration => {
|
2021-06-24 01:19:14 +02:00
|
|
|
const tags = getJSDocTags(declaration);
|
|
|
|
// skip comments containing @typedefs since they're not associated with particular declarations
|
|
|
|
// Exceptions:
|
|
|
|
// - @param or @return indicate that the author thinks of it as a 'local' @typedef that's part of the function documentation
|
|
|
|
if (tags.some(t => t.kind === SyntaxKind.JSDocTypedefTag || t.kind === SyntaxKind.JSDocCallbackTag)
|
|
|
|
&& !tags.some(t => t.kind === SyntaxKind.JSDocParameterTag || t.kind === SyntaxKind.JSDocReturnTag)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
for (const tag of tags) {
|
|
|
|
infos.push({ name: tag.tagName.text, text: getCommentDisplayParts(tag, checker) });
|
2016-12-13 00:29:29 +01:00
|
|
|
}
|
|
|
|
});
|
2021-06-24 01:19:14 +02:00
|
|
|
return infos;
|
2016-12-13 00:29:29 +01:00
|
|
|
}
|
|
|
|
|
2021-05-24 22:01:58 +02:00
|
|
|
function getDisplayPartsFromComment(comment: string | readonly JSDocComment[], checker: TypeChecker | undefined): SymbolDisplayPart[] {
|
2021-03-23 00:39:35 +01:00
|
|
|
if (typeof comment === "string") {
|
|
|
|
return [textPart(comment)];
|
|
|
|
}
|
|
|
|
return flatMap(
|
|
|
|
comment,
|
|
|
|
node => node.kind === SyntaxKind.JSDocText ? [textPart(node.text)] : buildLinkParts(node, checker)
|
|
|
|
) as SymbolDisplayPart[];
|
|
|
|
}
|
|
|
|
|
|
|
|
function getCommentDisplayParts(tag: JSDocTag, checker?: TypeChecker): SymbolDisplayPart[] | undefined {
|
2021-04-09 18:44:44 +02:00
|
|
|
const { comment, kind } = tag;
|
|
|
|
const namePart = getTagNameDisplayPart(kind);
|
|
|
|
switch (kind) {
|
2020-02-27 18:27:37 +01:00
|
|
|
case SyntaxKind.JSDocImplementsTag:
|
|
|
|
return withNode((tag as JSDocImplementsTag).class);
|
2017-10-30 18:27:19 +01:00
|
|
|
case SyntaxKind.JSDocAugmentsTag:
|
|
|
|
return withNode((tag as JSDocAugmentsTag).class);
|
|
|
|
case SyntaxKind.JSDocTemplateTag:
|
2021-03-23 00:39:35 +01:00
|
|
|
return addComment((tag as JSDocTemplateTag).typeParameters.map(tp => tp.getText()).join(", "));
|
2017-10-30 18:27:19 +01:00
|
|
|
case SyntaxKind.JSDocTypeTag:
|
2019-04-30 18:46:32 +02:00
|
|
|
return withNode((tag as JSDocTypeTag).typeExpression);
|
2017-10-30 18:27:19 +01:00
|
|
|
case SyntaxKind.JSDocTypedefTag:
|
2018-05-17 18:28:11 +02:00
|
|
|
case SyntaxKind.JSDocCallbackTag:
|
2017-10-30 18:27:19 +01:00
|
|
|
case SyntaxKind.JSDocPropertyTag:
|
|
|
|
case SyntaxKind.JSDocParameterTag:
|
2020-09-10 17:28:38 +02:00
|
|
|
case SyntaxKind.JSDocSeeTag:
|
2021-04-09 18:44:44 +02:00
|
|
|
const { name } = tag as JSDocTypedefTag | JSDocCallbackTag | JSDocPropertyTag | JSDocParameterTag | JSDocSeeTag;
|
2021-04-24 01:23:34 +02:00
|
|
|
return name ? withNode(name)
|
|
|
|
: comment === undefined ? undefined
|
|
|
|
: getDisplayPartsFromComment(comment, checker);
|
2017-10-30 18:27:19 +01:00
|
|
|
default:
|
2021-03-23 00:39:35 +01:00
|
|
|
return comment === undefined ? undefined : getDisplayPartsFromComment(comment, checker);
|
2017-10-30 18:27:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function withNode(node: Node) {
|
2018-01-11 19:49:39 +01:00
|
|
|
return addComment(node.getText());
|
2017-10-30 18:27:19 +01:00
|
|
|
}
|
|
|
|
|
2018-01-11 19:49:39 +01:00
|
|
|
function addComment(s: string) {
|
2021-04-24 01:23:34 +02:00
|
|
|
if (comment) {
|
|
|
|
if (s.match(/^https?$/)) {
|
|
|
|
return [textPart(s), ...getDisplayPartsFromComment(comment, checker)];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return [namePart(s), spacePart(), ...getDisplayPartsFromComment(comment, checker)];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return [textPart(s)];
|
|
|
|
}
|
2021-04-09 18:44:44 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function getTagNameDisplayPart(kind: SyntaxKind): (text: string) => SymbolDisplayPart {
|
|
|
|
switch (kind) {
|
|
|
|
case SyntaxKind.JSDocParameterTag:
|
|
|
|
return parameterNamePart;
|
|
|
|
case SyntaxKind.JSDocPropertyTag:
|
|
|
|
return propertyNamePart;
|
|
|
|
case SyntaxKind.JSDocTemplateTag:
|
|
|
|
return typeParameterNamePart;
|
|
|
|
case SyntaxKind.JSDocTypedefTag:
|
|
|
|
case SyntaxKind.JSDocCallbackTag:
|
|
|
|
return typeAliasNamePart;
|
|
|
|
default:
|
|
|
|
return textPart;
|
2017-10-30 18:27:19 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-02 02:46:35 +01:00
|
|
|
export function getJSDocTagNameCompletions(): CompletionEntry[] {
|
2018-03-01 23:20:18 +01:00
|
|
|
return jsDocTagNameCompletionEntries || (jsDocTagNameCompletionEntries = map(jsDocTagNames, tagName => {
|
2016-09-07 16:04:46 +02:00
|
|
|
return {
|
|
|
|
name: tagName,
|
|
|
|
kind: ScriptElementKind.keyword,
|
|
|
|
kindModifiers: "",
|
2020-09-11 23:48:24 +02:00
|
|
|
sortText: Completions.SortText.LocationPriority,
|
2016-09-07 16:04:46 +02:00
|
|
|
};
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2017-10-26 19:58:33 +02:00
|
|
|
export const getJSDocTagNameCompletionDetails = getJSDocTagCompletionDetails;
|
|
|
|
|
2017-03-02 02:46:35 +01:00
|
|
|
export function getJSDocTagCompletions(): CompletionEntry[] {
|
2018-03-01 23:20:18 +01:00
|
|
|
return jsDocTagCompletionEntries || (jsDocTagCompletionEntries = map(jsDocTagNames, tagName => {
|
2017-03-02 02:46:35 +01:00
|
|
|
return {
|
|
|
|
name: `@${tagName}`,
|
|
|
|
kind: ScriptElementKind.keyword,
|
|
|
|
kindModifiers: "",
|
2020-09-11 23:48:24 +02:00
|
|
|
sortText: Completions.SortText.LocationPriority
|
2017-03-03 16:00:52 +01:00
|
|
|
};
|
2017-03-02 02:46:35 +01:00
|
|
|
}));
|
2016-09-07 16:04:46 +02:00
|
|
|
}
|
|
|
|
|
2017-10-26 19:58:33 +02:00
|
|
|
export function getJSDocTagCompletionDetails(name: string): CompletionEntryDetails {
|
|
|
|
return {
|
|
|
|
name,
|
|
|
|
kind: ScriptElementKind.unknown, // TODO: should have its own kind?
|
|
|
|
kindModifiers: "",
|
|
|
|
displayParts: [textPart(name)],
|
|
|
|
documentation: emptyArray,
|
2018-09-13 17:47:50 +02:00
|
|
|
tags: undefined,
|
2017-10-26 19:58:33 +02:00
|
|
|
codeActions: undefined,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-06-07 21:28:52 +02:00
|
|
|
export function getJSDocParameterNameCompletions(tag: JSDocParameterTag): CompletionEntry[] {
|
2017-07-26 19:57:29 +02:00
|
|
|
if (!isIdentifier(tag.name)) {
|
|
|
|
return emptyArray;
|
|
|
|
}
|
2017-07-25 22:16:34 +02:00
|
|
|
const nameThusFar = tag.name.text;
|
2017-06-07 21:28:52 +02:00
|
|
|
const jsdoc = tag.parent;
|
|
|
|
const fn = jsdoc.parent;
|
2018-03-01 23:20:18 +01:00
|
|
|
if (!isFunctionLike(fn)) return [];
|
2017-06-07 21:28:52 +02:00
|
|
|
|
|
|
|
return mapDefined(fn.parameters, param => {
|
|
|
|
if (!isIdentifier(param.name)) return undefined;
|
|
|
|
|
2017-07-25 22:16:34 +02:00
|
|
|
const name = param.name.text;
|
2018-05-22 23:46:57 +02:00
|
|
|
if (jsdoc.tags!.some(t => t !== tag && isJSDocParameterTag(t) && isIdentifier(t.name) && t.name.escapedText === name) // TODO: GH#18217
|
2017-06-08 00:50:26 +02:00
|
|
|
|| nameThusFar !== undefined && !startsWith(name, nameThusFar)) {
|
2017-06-07 21:28:52 +02:00
|
|
|
return undefined;
|
2017-06-08 00:50:26 +02:00
|
|
|
}
|
2017-06-07 21:28:52 +02:00
|
|
|
|
2020-09-11 23:48:24 +02:00
|
|
|
return { name, kind: ScriptElementKind.parameterElement, kindModifiers: "", sortText: Completions.SortText.LocationPriority };
|
2017-06-07 21:28:52 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-10-26 19:58:33 +02:00
|
|
|
export function getJSDocParameterNameCompletionDetails(name: string): CompletionEntryDetails {
|
|
|
|
return {
|
|
|
|
name,
|
|
|
|
kind: ScriptElementKind.parameterElement,
|
|
|
|
kindModifiers: "",
|
|
|
|
displayParts: [textPart(name)],
|
|
|
|
documentation: emptyArray,
|
2018-09-13 17:47:50 +02:00
|
|
|
tags: undefined,
|
2017-10-26 19:58:33 +02:00
|
|
|
codeActions: undefined,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2016-09-07 16:04:46 +02:00
|
|
|
/**
|
|
|
|
* Checks if position points to a valid position to add JSDoc comments, and if so,
|
|
|
|
* returns the appropriate template. Otherwise returns an empty string.
|
2017-12-12 00:56:31 +01:00
|
|
|
* Valid positions are
|
|
|
|
* - outside of comments, statements, and expressions, and
|
|
|
|
* - preceding a:
|
|
|
|
* - function/constructor/method declaration
|
|
|
|
* - class declarations
|
|
|
|
* - variable statements
|
|
|
|
* - namespace declarations
|
|
|
|
* - interface declarations
|
|
|
|
* - method signatures
|
2017-12-12 01:15:32 +01:00
|
|
|
* - type alias declarations
|
2016-09-07 16:04:46 +02:00
|
|
|
*
|
|
|
|
* Hosts should ideally check that:
|
|
|
|
* - The line is all whitespace up to 'position' before performing the insertion.
|
|
|
|
* - If the keystroke sequence "/\*\*" induced the call, we also check that the next
|
|
|
|
* non-whitespace character is '*', which (approximately) indicates whether we added
|
|
|
|
* the second '*' to complete an existing (JSDoc) comment.
|
|
|
|
* @param fileName The file in which to perform the check.
|
|
|
|
* @param position The (character-indexed) position in the file where the check should
|
|
|
|
* be performed.
|
|
|
|
*/
|
2021-02-06 00:24:48 +01:00
|
|
|
export function getDocCommentTemplateAtPosition(newLine: string, sourceFile: SourceFile, position: number, options?: DocCommentTemplateOptions): TextInsertion | undefined {
|
2018-07-28 02:34:16 +02:00
|
|
|
const tokenAtPos = getTokenAtPosition(sourceFile, position);
|
|
|
|
const existingDocComment = findAncestor(tokenAtPos, isJSDoc);
|
|
|
|
if (existingDocComment && (existingDocComment.comment !== undefined || length(existingDocComment.tags))) {
|
|
|
|
// Non-empty comment already exists.
|
2016-09-07 16:04:46 +02:00
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
2018-05-29 21:17:57 +02:00
|
|
|
const tokenStart = tokenAtPos.getStart(sourceFile);
|
2018-07-28 02:34:16 +02:00
|
|
|
// Don't provide a doc comment template based on a *previous* node. (But an existing empty jsdoc comment will likely start before `position`.)
|
|
|
|
if (!existingDocComment && tokenStart < position) {
|
2016-09-07 16:04:46 +02:00
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
2021-02-06 00:24:48 +01:00
|
|
|
const commentOwnerInfo = getCommentOwnerInfo(tokenAtPos, options);
|
2017-09-07 16:21:47 +02:00
|
|
|
if (!commentOwnerInfo) {
|
2017-12-12 00:56:31 +01:00
|
|
|
return undefined;
|
2016-09-07 16:04:46 +02:00
|
|
|
}
|
2021-01-08 02:57:23 +01:00
|
|
|
|
|
|
|
const { commentOwner, parameters, hasReturn } = commentOwnerInfo;
|
2018-05-29 21:17:57 +02:00
|
|
|
if (commentOwner.getStart(sourceFile) < position) {
|
2016-09-07 16:04:46 +02:00
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
2018-06-25 20:40:45 +02:00
|
|
|
const indentationStr = getIndentationStringAtPosition(sourceFile, position);
|
2021-01-08 02:57:23 +01:00
|
|
|
const isJavaScriptFile = hasJSFileExtension(sourceFile.fileName);
|
|
|
|
const tags =
|
|
|
|
(parameters ? parameterDocComments(parameters || [], isJavaScriptFile, indentationStr, newLine) : "") +
|
|
|
|
(hasReturn ? returnsDocComment(indentationStr, newLine) : "");
|
2016-09-07 16:04:46 +02:00
|
|
|
|
|
|
|
// A doc comment consists of the following
|
|
|
|
// * The opening comment line
|
|
|
|
// * the first line (without a param) for the object's untagged info (this is also where the caret ends up)
|
|
|
|
// * the '@param'-tagged lines
|
2021-01-08 02:57:23 +01:00
|
|
|
// * the '@returns'-tag
|
2016-09-07 16:04:46 +02:00
|
|
|
// * TODO: other tags.
|
|
|
|
// * the closing comment line
|
|
|
|
// * if the caret was directly in front of the object, then we add an extra line and indentation.
|
2021-01-08 02:57:23 +01:00
|
|
|
const openComment = "/**";
|
|
|
|
const closeComment = " */";
|
|
|
|
if (tags) {
|
|
|
|
const preamble = openComment + newLine + indentationStr + " * ";
|
|
|
|
const endLine = tokenStart === position ? newLine + indentationStr : "";
|
|
|
|
const result = preamble + newLine + tags + indentationStr + closeComment + endLine;
|
|
|
|
return { newText: result, caretOffset: preamble.length };
|
|
|
|
}
|
|
|
|
return { newText: openComment + closeComment, caretOffset: 3 };
|
2016-09-07 16:04:46 +02:00
|
|
|
}
|
|
|
|
|
2018-06-25 20:40:45 +02:00
|
|
|
function getIndentationStringAtPosition(sourceFile: SourceFile, position: number): string {
|
|
|
|
const { text } = sourceFile;
|
|
|
|
const lineStart = getLineStartPositionForPosition(position, sourceFile);
|
|
|
|
let pos = lineStart;
|
|
|
|
for (; pos <= position && isWhiteSpaceSingleLine(text.charCodeAt(pos)); pos++);
|
|
|
|
return text.slice(lineStart, pos);
|
|
|
|
}
|
|
|
|
|
2019-08-08 20:30:18 +02:00
|
|
|
function parameterDocComments(parameters: readonly ParameterDeclaration[], isJavaScriptFile: boolean, indentationStr: string, newLine: string): string {
|
2018-05-29 21:17:57 +02:00
|
|
|
return parameters.map(({ name, dotDotDotToken }, i) => {
|
|
|
|
const paramName = name.kind === SyntaxKind.Identifier ? name.text : "param" + i;
|
|
|
|
const type = isJavaScriptFile ? (dotDotDotToken ? "{...any} " : "{any} ") : "";
|
|
|
|
return `${indentationStr} * @param ${type}${paramName}${newLine}`;
|
|
|
|
}).join("");
|
|
|
|
}
|
|
|
|
|
2021-01-08 02:57:23 +01:00
|
|
|
function returnsDocComment(indentationStr: string, newLine: string) {
|
|
|
|
return `${indentationStr} * @returns${newLine}`;
|
|
|
|
}
|
|
|
|
|
2017-09-07 16:21:47 +02:00
|
|
|
interface CommentOwnerInfo {
|
|
|
|
readonly commentOwner: Node;
|
2019-08-08 20:30:18 +02:00
|
|
|
readonly parameters?: readonly ParameterDeclaration[];
|
2021-01-08 02:57:23 +01:00
|
|
|
readonly hasReturn?: boolean;
|
2017-09-07 16:21:47 +02:00
|
|
|
}
|
2021-02-06 00:24:48 +01:00
|
|
|
function getCommentOwnerInfo(tokenAtPos: Node, options: DocCommentTemplateOptions | undefined): CommentOwnerInfo | undefined {
|
|
|
|
return forEachAncestor(tokenAtPos, n => getCommentOwnerInfoWorker(n, options));
|
2018-07-25 02:43:46 +02:00
|
|
|
}
|
2021-02-06 00:24:48 +01:00
|
|
|
function getCommentOwnerInfoWorker(commentOwner: Node, options: DocCommentTemplateOptions | undefined): CommentOwnerInfo | undefined | "quit" {
|
2018-07-25 02:43:46 +02:00
|
|
|
switch (commentOwner.kind) {
|
|
|
|
case SyntaxKind.FunctionDeclaration:
|
|
|
|
case SyntaxKind.FunctionExpression:
|
|
|
|
case SyntaxKind.MethodDeclaration:
|
|
|
|
case SyntaxKind.Constructor:
|
|
|
|
case SyntaxKind.MethodSignature:
|
2020-10-06 16:50:08 +02:00
|
|
|
case SyntaxKind.ArrowFunction:
|
2021-01-08 02:57:23 +01:00
|
|
|
const host = commentOwner as ArrowFunction | FunctionDeclaration | MethodDeclaration | ConstructorDeclaration | MethodSignature;
|
2021-02-06 00:24:48 +01:00
|
|
|
return { commentOwner, parameters: host.parameters, hasReturn: hasReturn(host, options) };
|
2018-07-25 02:43:46 +02:00
|
|
|
|
|
|
|
case SyntaxKind.PropertyAssignment:
|
2021-02-06 00:24:48 +01:00
|
|
|
return getCommentOwnerInfoWorker((commentOwner as PropertyAssignment).initializer, options);
|
2018-07-25 02:43:46 +02:00
|
|
|
|
|
|
|
case SyntaxKind.ClassDeclaration:
|
|
|
|
case SyntaxKind.InterfaceDeclaration:
|
|
|
|
case SyntaxKind.PropertySignature:
|
|
|
|
case SyntaxKind.EnumDeclaration:
|
|
|
|
case SyntaxKind.EnumMember:
|
|
|
|
case SyntaxKind.TypeAliasDeclaration:
|
|
|
|
return { commentOwner };
|
|
|
|
|
|
|
|
case SyntaxKind.VariableStatement: {
|
2021-05-18 15:20:57 +02:00
|
|
|
const varStatement = commentOwner as VariableStatement;
|
2018-07-25 02:43:46 +02:00
|
|
|
const varDeclarations = varStatement.declarationList.declarations;
|
2021-01-08 02:57:23 +01:00
|
|
|
const host = varDeclarations.length === 1 && varDeclarations[0].initializer
|
|
|
|
? getRightHandSideOfAssignment(varDeclarations[0].initializer)
|
2018-07-25 02:43:46 +02:00
|
|
|
: undefined;
|
2021-01-08 02:57:23 +01:00
|
|
|
return host
|
2021-02-06 00:24:48 +01:00
|
|
|
? { commentOwner, parameters: host.parameters, hasReturn: hasReturn(host, options) }
|
2021-01-08 02:57:23 +01:00
|
|
|
: { commentOwner };
|
2018-07-25 02:43:46 +02:00
|
|
|
}
|
2016-09-07 16:04:46 +02:00
|
|
|
|
2018-07-25 02:43:46 +02:00
|
|
|
case SyntaxKind.SourceFile:
|
|
|
|
return "quit";
|
2017-09-07 16:21:47 +02:00
|
|
|
|
2018-07-25 02:43:46 +02:00
|
|
|
case SyntaxKind.ModuleDeclaration:
|
|
|
|
// If in walking up the tree, we hit a a nested namespace declaration,
|
|
|
|
// then we must be somewhere within a dotted namespace name; however we don't
|
|
|
|
// want to give back a JSDoc template for the 'b' or 'c' in 'namespace a.b.c { }'.
|
|
|
|
return commentOwner.parent.kind === SyntaxKind.ModuleDeclaration ? undefined : { commentOwner };
|
2017-12-12 00:56:31 +01:00
|
|
|
|
2020-04-20 20:15:12 +02:00
|
|
|
case SyntaxKind.ExpressionStatement:
|
2021-02-06 00:24:48 +01:00
|
|
|
return getCommentOwnerInfoWorker((commentOwner as ExpressionStatement).expression, options);
|
2018-07-25 02:43:46 +02:00
|
|
|
case SyntaxKind.BinaryExpression: {
|
|
|
|
const be = commentOwner as BinaryExpression;
|
2018-09-12 19:44:46 +02:00
|
|
|
if (getAssignmentDeclarationKind(be) === AssignmentDeclarationKind.None) {
|
2018-07-25 02:43:46 +02:00
|
|
|
return "quit";
|
2017-09-07 16:21:47 +02:00
|
|
|
}
|
2021-01-08 02:57:23 +01:00
|
|
|
return isFunctionLike(be.right)
|
2021-02-06 00:24:48 +01:00
|
|
|
? { commentOwner, parameters: be.right.parameters, hasReturn: hasReturn(be.right, options) }
|
2021-01-08 02:57:23 +01:00
|
|
|
: { commentOwner };
|
2016-09-07 16:04:46 +02:00
|
|
|
}
|
2020-02-06 23:38:21 +01:00
|
|
|
case SyntaxKind.PropertyDeclaration:
|
|
|
|
const init = (commentOwner as PropertyDeclaration).initializer;
|
|
|
|
if (init && (isFunctionExpression(init) || isArrowFunction(init))) {
|
2021-02-06 00:24:48 +01:00
|
|
|
return { commentOwner, parameters: init.parameters, hasReturn: hasReturn(init, options) };
|
2020-02-06 23:38:21 +01:00
|
|
|
}
|
2016-09-07 16:04:46 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-06 00:24:48 +01:00
|
|
|
function hasReturn(node: Node, options: DocCommentTemplateOptions | undefined) {
|
|
|
|
return !!options?.generateReturnInDocTemplate &&
|
|
|
|
(isArrowFunction(node) && isExpression(node.body)
|
|
|
|
|| isFunctionLikeDeclaration(node) && node.body && isBlock(node.body) && !!forEachReturnStatement(node.body, n => n));
|
2021-01-08 02:57:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function getRightHandSideOfAssignment(rightHandSide: Expression): FunctionExpression | ArrowFunction | ConstructorDeclaration | undefined {
|
2016-09-07 16:04:46 +02:00
|
|
|
while (rightHandSide.kind === SyntaxKind.ParenthesizedExpression) {
|
2021-05-18 15:20:57 +02:00
|
|
|
rightHandSide = (rightHandSide as ParenthesizedExpression).expression;
|
2016-09-07 16:04:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
switch (rightHandSide.kind) {
|
|
|
|
case SyntaxKind.FunctionExpression:
|
|
|
|
case SyntaxKind.ArrowFunction:
|
2021-05-18 15:20:57 +02:00
|
|
|
return (rightHandSide as FunctionExpression);
|
2021-01-08 02:57:23 +01:00
|
|
|
case SyntaxKind.ClassExpression:
|
|
|
|
return find((rightHandSide as ClassExpression).members, isConstructorDeclaration);
|
2016-09-07 16:04:46 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|