mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Replace services' jsdoc parser with parsers'
Also modify scanner's definition of leading vs trailing comments, with associated changes to emitter and emit baselines (the last is in a separate commit).
This commit is contained in:
@@ -262,7 +262,7 @@ namespace ts {
|
||||
Debug.assert(node.parent.kind === SyntaxKind.JSDocFunctionType);
|
||||
let functionType = <JSDocFunctionType>node.parent;
|
||||
let index = indexOf(functionType.parameters, node);
|
||||
return "p" + index;
|
||||
return "arg" + index;
|
||||
case SyntaxKind.JSDocTypedefTag:
|
||||
const parentNode = node.parent && node.parent.parent;
|
||||
let nameFromParentNode: string;
|
||||
@@ -517,9 +517,7 @@ namespace ts {
|
||||
// because the scope of JsDocComment should not be affected by whether the current node is a
|
||||
// container or not.
|
||||
if (isInJavaScriptFile(node) && node.jsDocComments) {
|
||||
for (const jsDocComment of node.jsDocComments) {
|
||||
bind(jsDocComment);
|
||||
}
|
||||
forEach(node.jsDocComments, bind);
|
||||
}
|
||||
if (checkUnreachable(node)) {
|
||||
forEachChild(node, bind);
|
||||
|
||||
@@ -5629,12 +5629,13 @@ namespace ts {
|
||||
case SyntaxKind.JSDocThisType:
|
||||
case SyntaxKind.JSDocOptionalType:
|
||||
return getTypeFromTypeNode((<ParenthesizedTypeNode | JSDocTypeReferencingNode>node).type);
|
||||
case SyntaxKind.JSDocRecordType:
|
||||
return getTypeFromTypeNode((node as JSDocRecordType).literal);
|
||||
case SyntaxKind.FunctionType:
|
||||
case SyntaxKind.ConstructorType:
|
||||
case SyntaxKind.TypeLiteral:
|
||||
case SyntaxKind.JSDocTypeLiteral:
|
||||
case SyntaxKind.JSDocFunctionType:
|
||||
case SyntaxKind.JSDocRecordType:
|
||||
return getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node, aliasSymbol, aliasTypeArguments);
|
||||
// This function assumes that an identifier or qualified name is a type expression
|
||||
// Callers should first ensure this by calling isTypeNode
|
||||
|
||||
+55
-27
@@ -851,7 +851,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
}
|
||||
}
|
||||
|
||||
function emitLinePreservingList(parent: Node, nodes: NodeArray<Node>, allowTrailingComma: boolean, spacesBetweenBraces: boolean) {
|
||||
function emitLinePreservingList(parent: Node, nodes: NodeArray<Node>, allowTrailingComma: boolean, spacesBetweenBraces: boolean, commentsBeforePunctuation: boolean) {
|
||||
Debug.assert(nodes.length > 0);
|
||||
|
||||
increaseIndent();
|
||||
@@ -877,6 +877,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
}
|
||||
|
||||
emit(nodes[i]);
|
||||
if (commentsBeforePunctuation) {
|
||||
emitLeadingCommentsAtEnd(nodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (nodes.hasTrailingComma && allowTrailingComma) {
|
||||
@@ -895,7 +898,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
}
|
||||
}
|
||||
|
||||
function emitList<TNode extends Node>(nodes: TNode[], start: number, count: number, multiLine: boolean, trailingComma: boolean, leadingComma?: boolean, noTrailingNewLine?: boolean, emitNode?: (node: TNode) => void): number {
|
||||
function emitList<TNode extends Node>(nodes: TNode[], start: number, count: number, multiLine: boolean, trailingComma: boolean, leadingComma?: boolean, noTrailingNewLine?: boolean, commentsBeforePunctuation?: boolean, emitNode?: (node: TNode) => void): number {
|
||||
if (!emitNode) {
|
||||
emitNode = emit;
|
||||
}
|
||||
@@ -913,13 +916,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
}
|
||||
}
|
||||
const node = nodes[start + i];
|
||||
// This emitting is to make sure we emit following comment properly
|
||||
// ...(x, /*comment1*/ y)...
|
||||
// ^ => node.pos
|
||||
// "comment1" is not considered leading comment for "y" but rather
|
||||
// considered as trailing comment of the previous node.
|
||||
emitTrailingCommentsOfPosition(node.pos);
|
||||
emitNode(node);
|
||||
if (commentsBeforePunctuation) {
|
||||
emitLeadingCommentsAtEnd(nodes[i]);
|
||||
}
|
||||
leadingComma = true;
|
||||
}
|
||||
if (trailingComma) {
|
||||
@@ -1897,7 +1897,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
}
|
||||
else if (languageVersion >= ScriptTarget.ES6 || !forEach(elements, isSpreadElementExpression)) {
|
||||
write("[");
|
||||
emitLinePreservingList(node, node.elements, elements.hasTrailingComma, /*spacesBetweenBraces*/ false);
|
||||
emitLinePreservingList(node, node.elements, elements.hasTrailingComma, /*spacesBetweenBraces*/ false, /*commentsBeforePunctuation*/ true);
|
||||
write("]");
|
||||
}
|
||||
else {
|
||||
@@ -1921,7 +1921,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
// then try to preserve the original shape of the object literal.
|
||||
// Otherwise just try to preserve the formatting.
|
||||
if (numElements === properties.length) {
|
||||
emitLinePreservingList(node, properties, /*allowTrailingComma*/ languageVersion >= ScriptTarget.ES5, /*spacesBetweenBraces*/ true);
|
||||
emitLinePreservingList(node, properties, /*allowTrailingComma*/ languageVersion >= ScriptTarget.ES5, /*spacesBetweenBraces*/ true, /*commentsBeforePunctuation*/ true);
|
||||
}
|
||||
else {
|
||||
const multiLine = node.multiLine;
|
||||
@@ -2166,14 +2166,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
function emitPropertyAssignment(node: PropertyDeclaration) {
|
||||
emit(node.name);
|
||||
write(": ");
|
||||
// This is to ensure that we emit comment in the following case:
|
||||
// For example:
|
||||
// obj = {
|
||||
// id: /*comment1*/ ()=>void
|
||||
// }
|
||||
// "comment1" is not considered to be leading comment for node.initializer
|
||||
// but rather a trailing comment on the previous node.
|
||||
emitTrailingCommentsOfPosition(node.initializer.pos);
|
||||
emit(node.initializer);
|
||||
}
|
||||
|
||||
@@ -2285,6 +2277,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
}
|
||||
|
||||
emit(node.expression);
|
||||
emitLeadingCommentsAtEnd(node.expression);
|
||||
const dotRangeStart = nodeIsSynthesized(node.expression) ? -1 : node.expression.end;
|
||||
const dotRangeEnd = nodeIsSynthesized(node.expression) ? -1 : skipTrivia(currentText, node.expression.end) + 1;
|
||||
const dotToken = <TextRange>{ pos: dotRangeStart, end: dotRangeEnd };
|
||||
@@ -2501,13 +2494,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
emitThis(expression);
|
||||
if (node.arguments.length) {
|
||||
write(", ");
|
||||
emitTrailingCommentsOfPosition(node.arguments.pos);
|
||||
emitCommaList(node.arguments);
|
||||
emitLeadingCommentsAtEnd(node.arguments);
|
||||
}
|
||||
write(")");
|
||||
}
|
||||
else {
|
||||
write("(");
|
||||
emitTrailingCommentsOfPosition(node.arguments.pos);
|
||||
emitCommaList(node.arguments);
|
||||
emitLeadingCommentsAtEnd(node.arguments);
|
||||
write(")");
|
||||
}
|
||||
}
|
||||
@@ -2608,6 +2605,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
|
||||
write("(");
|
||||
emit(node.expression);
|
||||
emitLeadingCommentsAtEnd(node.expression);
|
||||
write(")");
|
||||
}
|
||||
|
||||
@@ -2886,6 +2884,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
}
|
||||
else {
|
||||
emit(node.left);
|
||||
emitLeadingCommentsAtEnd(node.left);
|
||||
// Add indentation before emit the operator if the operator is on different line
|
||||
// For example:
|
||||
// 3
|
||||
@@ -3898,6 +3897,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
}
|
||||
emitNodeWithCommentsAndWithoutSourcemap(node.name);
|
||||
emitEnd(node.name);
|
||||
emitLeadingCommentsAtEnd(node.name);
|
||||
}
|
||||
|
||||
function createVoidZero(): Expression {
|
||||
@@ -4040,6 +4040,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
emit(name);
|
||||
}
|
||||
|
||||
emitLeadingCommentsAtEnd(name);
|
||||
write(" = ");
|
||||
emit(value);
|
||||
});
|
||||
@@ -4591,6 +4592,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
emitStart(restParam);
|
||||
write("var ");
|
||||
emitNodeWithCommentsAndWithoutSourcemap(restParam.name);
|
||||
emitLeadingCommentsAtEnd(restParam.name);
|
||||
write(" = [];");
|
||||
emitEnd(restParam);
|
||||
emitTrailingComments(restParam);
|
||||
@@ -4612,6 +4614,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
writeLine();
|
||||
emitStart(restParam);
|
||||
emitNodeWithCommentsAndWithoutSourcemap(restParam.name);
|
||||
emitLeadingCommentsAtEnd(restParam.name);
|
||||
write("[" + tempName + " - " + restIndex + "] = arguments[" + tempName + "];");
|
||||
emitEnd(restParam);
|
||||
decreaseIndent();
|
||||
@@ -4633,6 +4636,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
function emitDeclarationName(node: Declaration) {
|
||||
if (node.name) {
|
||||
emitNodeWithCommentsAndWithoutSourcemap(node.name);
|
||||
emitLeadingCommentsAtEnd(node.name);
|
||||
}
|
||||
else {
|
||||
write(getGeneratedNameForNode(node));
|
||||
@@ -4660,6 +4664,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
const { kind, parent } = node;
|
||||
if (kind !== SyntaxKind.MethodDeclaration &&
|
||||
kind !== SyntaxKind.MethodSignature &&
|
||||
kind !== SyntaxKind.ArrowFunction &&
|
||||
parent &&
|
||||
parent.kind !== SyntaxKind.PropertyAssignment &&
|
||||
parent.kind !== SyntaxKind.CallExpression &&
|
||||
@@ -4734,7 +4739,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
const parameters = node.parameters;
|
||||
const skipCount = node.parameters.length && (<Identifier>node.parameters[0].name).originalKeywordKind === SyntaxKind.ThisKeyword ? 1 : 0;
|
||||
const omitCount = languageVersion < ScriptTarget.ES6 && hasDeclaredRestParameter(node) ? 1 : 0;
|
||||
emitList(parameters, skipCount, parameters.length - omitCount - skipCount, /*multiLine*/ false, /*trailingComma*/ false);
|
||||
emitTrailingCommentsOfPosition(node.parameters.pos);
|
||||
emitList(parameters, skipCount, parameters.length - omitCount - skipCount, /*multiLine*/ false, /*trailingComma*/ false, /*leadingComma*/ false, /*noTrailingNewLine*/ false, /*commentsBeforePunctuation*/ true);
|
||||
if ((parameters.length - omitCount - skipCount) <= 0) {
|
||||
emitLeadingCommentsAtEnd(node.parameters);
|
||||
}
|
||||
}
|
||||
write(")");
|
||||
decreaseIndent();
|
||||
@@ -5791,7 +5800,7 @@ const _super = (function (geti, seti) {
|
||||
writeLine();
|
||||
|
||||
const decoratorCount = decorators ? decorators.length : 0;
|
||||
let argumentsWritten = emitList(decorators, 0, decoratorCount, /*multiLine*/ true, /*trailingComma*/ false, /*leadingComma*/ false, /*noTrailingNewLine*/ true,
|
||||
let argumentsWritten = emitList(decorators, 0, decoratorCount, /*multiLine*/ true, /*trailingComma*/ false, /*leadingComma*/ false, /*noTrailingNewLine*/ true, /*commentsBeforePunctuation*/ false,
|
||||
decorator => emit(decorator.expression));
|
||||
if (firstParameterDecorator) {
|
||||
argumentsWritten += emitDecoratorsOfParameters(constructor, /*leadingComma*/ argumentsWritten > 0);
|
||||
@@ -5891,7 +5900,7 @@ const _super = (function (geti, seti) {
|
||||
writeLine();
|
||||
|
||||
const decoratorCount = decorators ? decorators.length : 0;
|
||||
let argumentsWritten = emitList(decorators, 0, decoratorCount, /*multiLine*/ true, /*trailingComma*/ false, /*leadingComma*/ false, /*noTrailingNewLine*/ true,
|
||||
let argumentsWritten = emitList(decorators, 0, decoratorCount, /*multiLine*/ true, /*trailingComma*/ false, /*leadingComma*/ false, /*noTrailingNewLine*/ true, /*commentsBeforePunctuation*/ false,
|
||||
decorator => emit(decorator.expression));
|
||||
|
||||
if (firstParameterDecorator) {
|
||||
@@ -5933,7 +5942,7 @@ const _super = (function (geti, seti) {
|
||||
for (const parameter of node.parameters) {
|
||||
if (nodeIsDecorated(parameter)) {
|
||||
const decorators = parameter.decorators;
|
||||
argumentsWritten += emitList(decorators, 0, decorators.length, /*multiLine*/ true, /*trailingComma*/ false, /*leadingComma*/ leadingComma, /*noTrailingNewLine*/ true, decorator => {
|
||||
argumentsWritten += emitList(decorators, 0, decorators.length, /*multiLine*/ true, /*trailingComma*/ false, /*leadingComma*/ leadingComma, /*noTrailingNewLine*/ true, /*commentsBeforePunctuation*/ false, decorator => {
|
||||
write(`__param(${parameterIndex}, `);
|
||||
emit(decorator.expression);
|
||||
write(")");
|
||||
@@ -6347,6 +6356,7 @@ const _super = (function (geti, seti) {
|
||||
emitExpressionForPropertyName(node.name);
|
||||
emitEnd(node);
|
||||
write(";");
|
||||
emitLeadingCommentsAtEnd(node.name);
|
||||
}
|
||||
|
||||
function writeEnumMemberDeclarationValue(member: EnumMember) {
|
||||
@@ -8288,7 +8298,11 @@ const _super = (function (geti, seti) {
|
||||
emitNewLineBeforeLeadingComments(currentLineMap, writer, node, leadingComments);
|
||||
|
||||
// Leading comments are emitted at /*leading comment1 */space/*leading comment*/space
|
||||
emitComments(currentText, currentLineMap, writer, leadingComments, /*trailingSeparator*/ true, newLine, writeComment);
|
||||
// However, a leading mid-line comment of the end of file token is emitted with spaces before,
|
||||
// as if it were a trailing comment of the previous token
|
||||
const commentStartsInMidLine = leadingComments && leadingComments.length && leadingComments[0].pos - 1 > 0 && currentText.charCodeAt(leadingComments[0].pos - 1) !== 10;
|
||||
const isEndOfFile = node.kind === SyntaxKind.EndOfFileToken && commentStartsInMidLine;
|
||||
emitComments(currentText, currentLineMap, writer, leadingComments, /*trailingSeparator*/ !isEndOfFile, newLine, writeComment);
|
||||
}
|
||||
|
||||
function emitTrailingComments(node: Node) {
|
||||
@@ -8305,8 +8319,11 @@ const _super = (function (geti, seti) {
|
||||
|
||||
/**
|
||||
* Emit trailing comments at the position. The term trailing comment is used here to describe following comment:
|
||||
* x, /comment1/ y
|
||||
* ^ => pos; the function will emit "comment1" in the emitJS
|
||||
* x, /*comment1* / // comment2
|
||||
* ^ => pos; the function will emit "comment1" and "comment2" in the emitJS
|
||||
* However,
|
||||
* x /*comment1* /, y
|
||||
* 'comment1' is actually a leading comment of ',' and needs to be emitted using emitLeadingCommentsAtEnd
|
||||
*/
|
||||
function emitTrailingCommentsOfPosition(pos: number) {
|
||||
if (compilerOptions.removeComments) {
|
||||
@@ -8319,7 +8336,18 @@ const _super = (function (geti, seti) {
|
||||
emitComments(currentText, currentLineMap, writer, trailingComments, /*trailingSeparator*/ true, newLine, writeComment);
|
||||
}
|
||||
|
||||
function emitLeadingCommentsOfPositionWorker(pos: number) {
|
||||
/**
|
||||
* Emit leading comments for the token after the current token.
|
||||
* This is only needed for punctuation tokens. For example, in
|
||||
* x /*comment1* /, y
|
||||
* 'comment1' is a leading comment of ',' but needs to be emitted
|
||||
* from x because there is no token for ','.
|
||||
*/
|
||||
function emitLeadingCommentsAtEnd(node: Node | NodeArray<any>) {
|
||||
emitLeadingCommentsOfPositionWorker(node.end, /*trailingSeparator*/ false);
|
||||
}
|
||||
|
||||
function emitLeadingCommentsOfPositionWorker(pos: number, trailingSeparator = true) {
|
||||
if (compilerOptions.removeComments) {
|
||||
return;
|
||||
}
|
||||
@@ -8337,7 +8365,7 @@ const _super = (function (geti, seti) {
|
||||
emitNewLineBeforeLeadingComments(currentLineMap, writer, { pos: pos, end: pos }, leadingComments);
|
||||
|
||||
// Leading comments are emitted at /*leading comment1 */space/*leading comment*/space
|
||||
emitComments(currentText, currentLineMap, writer, leadingComments, /*trailingSeparator*/ true, newLine, writeComment);
|
||||
emitComments(currentText, currentLineMap, writer, leadingComments, trailingSeparator, newLine, writeComment);
|
||||
}
|
||||
|
||||
function emitDetachedCommentsAndUpdateCommentsInfo(node: TextRange) {
|
||||
|
||||
+275
-136
@@ -378,7 +378,7 @@ namespace ts {
|
||||
case SyntaxKind.JSDocNullableType:
|
||||
return visitNode(cbNode, (<JSDocNullableType>node).type);
|
||||
case SyntaxKind.JSDocRecordType:
|
||||
return visitNodes(cbNodes, (<JSDocRecordType>node).members);
|
||||
return visitNode(cbNode, (<JSDocRecordType>node).literal);
|
||||
case SyntaxKind.JSDocTypeReference:
|
||||
return visitNode(cbNode, (<JSDocTypeReference>node).name) ||
|
||||
visitNodes(cbNodes, (<JSDocTypeReference>node).typeArguments);
|
||||
@@ -397,7 +397,7 @@ namespace ts {
|
||||
return visitNode(cbNode, (<JSDocRecordMember>node).name) ||
|
||||
visitNode(cbNode, (<JSDocRecordMember>node).type);
|
||||
case SyntaxKind.JSDocComment:
|
||||
return visitNodes(cbNodes, (<JSDocComment>node).tags);
|
||||
return visitNodes(cbNodes, (<JSDoc>node).tags);
|
||||
case SyntaxKind.JSDocParameterTag:
|
||||
return visitNode(cbNode, (<JSDocParameterTag>node).preParameterName) ||
|
||||
visitNode(cbNode, (<JSDocParameterTag>node).typeExpression) ||
|
||||
@@ -450,10 +450,10 @@ namespace ts {
|
||||
/* @internal */
|
||||
export function parseIsolatedJSDocComment(content: string, start?: number, length?: number) {
|
||||
const result = Parser.JSDocParser.parseIsolatedJSDocComment(content, start, length);
|
||||
if (result && result.jsDocComment) {
|
||||
if (result && result.jsDoc) {
|
||||
// because the jsDocComment was parsed out of the source file, it might
|
||||
// not be covered by the fixupParentReferences.
|
||||
Parser.fixupParentReferences(result.jsDocComment);
|
||||
Parser.fixupParentReferences(result.jsDoc);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -652,20 +652,18 @@ namespace ts {
|
||||
|
||||
|
||||
function addJSDocComment<T extends Node>(node: T): T {
|
||||
if (contextFlags & NodeFlags.JavaScriptFile) {
|
||||
const comments = getLeadingCommentRangesOfNode(node, sourceFile);
|
||||
if (comments) {
|
||||
for (const comment of comments) {
|
||||
const jsDocComment = JSDocParser.parseJSDocComment(node, comment.pos, comment.end - comment.pos);
|
||||
if (!jsDocComment) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!node.jsDocComments) {
|
||||
node.jsDocComments = [];
|
||||
}
|
||||
node.jsDocComments.push(jsDocComment);
|
||||
const comments = getLeadingCommentRangesOfNode(node, sourceFile);
|
||||
if (comments) {
|
||||
for (const comment of comments) {
|
||||
const jsDoc = JSDocParser.parseJSDocComment(node, comment.pos, comment.end - comment.pos);
|
||||
if (!jsDoc) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!node.jsDocComments) {
|
||||
node.jsDocComments = [];
|
||||
}
|
||||
node.jsDocComments.push(jsDoc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2201,7 +2199,7 @@ namespace ts {
|
||||
}
|
||||
fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ false, /*awaitContext*/ false, /*requireCompleteParameterList*/ false, node);
|
||||
parseTypeMemberSemicolon();
|
||||
return finishNode(node);
|
||||
return addJSDocComment(finishNode(node));
|
||||
}
|
||||
|
||||
function isIndexSignature(): boolean {
|
||||
@@ -2291,7 +2289,7 @@ namespace ts {
|
||||
// [Yield] nor [Await]
|
||||
fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ false, /*awaitContext*/ false, /*requireCompleteParameterList*/ false, method);
|
||||
parseTypeMemberSemicolon();
|
||||
return finishNode(method);
|
||||
return addJSDocComment(finishNode(method));
|
||||
}
|
||||
else {
|
||||
const property = <PropertySignature>createNode(SyntaxKind.PropertySignature, fullStart);
|
||||
@@ -2308,7 +2306,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
parseTypeMemberSemicolon();
|
||||
return finishNode(property);
|
||||
return addJSDocComment(finishNode(property));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2897,7 +2895,7 @@ namespace ts {
|
||||
node.equalsGreaterThanToken = parseExpectedToken(SyntaxKind.EqualsGreaterThanToken, /*reportAtCurrentPosition*/ false, Diagnostics._0_expected, "=>");
|
||||
node.body = parseArrowFunctionExpressionBody(/*isAsync*/ !!asyncModifier);
|
||||
|
||||
return finishNode(node);
|
||||
return addJSDocComment(finishNode(node));
|
||||
}
|
||||
|
||||
function tryParseParenthesizedArrowFunctionExpression(): Expression {
|
||||
@@ -2930,7 +2928,7 @@ namespace ts {
|
||||
? parseArrowFunctionExpressionBody(isAsync)
|
||||
: parseIdentifier();
|
||||
|
||||
return finishNode(arrowFunction);
|
||||
return addJSDocComment(finishNode(arrowFunction));
|
||||
}
|
||||
|
||||
// True -> We definitely expect a parenthesized arrow function here.
|
||||
@@ -4114,7 +4112,7 @@ namespace ts {
|
||||
|
||||
function tryParseAccessorDeclaration(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray): AccessorDeclaration {
|
||||
if (parseContextualModifier(SyntaxKind.GetKeyword)) {
|
||||
return addJSDocComment(parseAccessorDeclaration(SyntaxKind.GetAccessor, fullStart, decorators, modifiers));
|
||||
return parseAccessorDeclaration(SyntaxKind.GetAccessor, fullStart, decorators, modifiers);
|
||||
}
|
||||
else if (parseContextualModifier(SyntaxKind.SetKeyword)) {
|
||||
return parseAccessorDeclaration(SyntaxKind.SetAccessor, fullStart, decorators, modifiers);
|
||||
@@ -4993,7 +4991,7 @@ namespace ts {
|
||||
: doOutsideOfContext(NodeFlags.YieldContext | NodeFlags.DisallowInContext, parseNonParameterInitializer);
|
||||
|
||||
parseSemicolon();
|
||||
return finishNode(property);
|
||||
return addJSDocComment(finishNode(property));
|
||||
}
|
||||
|
||||
function parsePropertyOrMethodDeclaration(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray): ClassElement {
|
||||
@@ -5022,7 +5020,7 @@ namespace ts {
|
||||
node.name = parsePropertyName();
|
||||
fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ false, /*awaitContext*/ false, /*requireCompleteParameterList*/ false, node);
|
||||
node.body = parseFunctionBlockOrSemicolon(/*isGenerator*/ false, /*isAsync*/ false);
|
||||
return finishNode(node);
|
||||
return addJSDocComment(finishNode(node));
|
||||
}
|
||||
|
||||
function isClassMemberModifier(idToken: SyntaxKind) {
|
||||
@@ -5265,7 +5263,7 @@ namespace ts {
|
||||
node.members = createMissingList<ClassElement>();
|
||||
}
|
||||
|
||||
return finishNode(node);
|
||||
return addJSDocComment(finishNode(node));
|
||||
}
|
||||
|
||||
function parseNameOfClassDeclarationOrExpression(): Identifier {
|
||||
@@ -5333,7 +5331,7 @@ namespace ts {
|
||||
node.typeParameters = parseTypeParameters();
|
||||
node.heritageClauses = parseHeritageClauses(/*isClassHeritageClause*/ false);
|
||||
node.members = parseObjectTypeMembers();
|
||||
return finishNode(node);
|
||||
return addJSDocComment(finishNode(node));
|
||||
}
|
||||
|
||||
function parseTypeAliasDeclaration(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray): TypeAliasDeclaration {
|
||||
@@ -5357,7 +5355,7 @@ namespace ts {
|
||||
const node = <EnumMember>createNode(SyntaxKind.EnumMember, scanner.getStartPos());
|
||||
node.name = parsePropertyName();
|
||||
node.initializer = allowInAnd(parseNonParameterInitializer);
|
||||
return finishNode(node);
|
||||
return addJSDocComment(finishNode(node));
|
||||
}
|
||||
|
||||
function parseEnumDeclaration(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray): EnumDeclaration {
|
||||
@@ -5373,7 +5371,7 @@ namespace ts {
|
||||
else {
|
||||
node.members = createMissingList<EnumMember>();
|
||||
}
|
||||
return finishNode(node);
|
||||
return addJSDocComment(finishNode(node));
|
||||
}
|
||||
|
||||
function parseModuleBlock(): ModuleBlock {
|
||||
@@ -5400,7 +5398,7 @@ namespace ts {
|
||||
node.body = parseOptional(SyntaxKind.DotToken)
|
||||
? parseModuleOrNamespaceDeclaration(getNodePos(), /*decorators*/ undefined, /*modifiers*/ undefined, NodeFlags.Export | namespaceFlag)
|
||||
: parseModuleBlock();
|
||||
return finishNode(node);
|
||||
return addJSDocComment(finishNode(node));
|
||||
}
|
||||
|
||||
function parseAmbientExternalModuleDeclaration(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray): ModuleDeclaration {
|
||||
@@ -5489,7 +5487,7 @@ namespace ts {
|
||||
parseExpected(SyntaxKind.EqualsToken);
|
||||
importEqualsDeclaration.moduleReference = parseModuleReference();
|
||||
parseSemicolon();
|
||||
return finishNode(importEqualsDeclaration);
|
||||
return addJSDocComment(finishNode(importEqualsDeclaration));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5810,6 +5808,7 @@ namespace ts {
|
||||
|
||||
export function parseJSDocTypeExpressionForTests(content: string, start: number, length: number) {
|
||||
initializeState("file.js", content, ScriptTarget.Latest, /*_syntaxCursor:*/ undefined, ScriptKind.JS);
|
||||
sourceFile = createSourceFile("file.js", ScriptTarget.Latest, ScriptKind.JS);
|
||||
scanner.setText(content, start, length);
|
||||
currentToken = scanner.scan();
|
||||
const jsDocTypeExpression = parseJSDocTypeExpression();
|
||||
@@ -6028,22 +6027,7 @@ namespace ts {
|
||||
|
||||
function parseJSDocRecordType(): JSDocRecordType {
|
||||
const result = <JSDocRecordType>createNode(SyntaxKind.JSDocRecordType);
|
||||
nextToken();
|
||||
result.members = parseDelimitedList(ParsingContext.JSDocRecordMembers, parseJSDocRecordMember);
|
||||
checkForTrailingComma(result.members);
|
||||
parseExpected(SyntaxKind.CloseBraceToken);
|
||||
return finishNode(result);
|
||||
}
|
||||
|
||||
function parseJSDocRecordMember(): JSDocRecordMember {
|
||||
const result = <JSDocRecordMember>createNode(SyntaxKind.JSDocRecordMember);
|
||||
result.name = parseSimplePropertyName();
|
||||
|
||||
if (token() === SyntaxKind.ColonToken) {
|
||||
nextToken();
|
||||
result.type = parseJSDocType();
|
||||
}
|
||||
|
||||
result.literal = parseTypeLiteral();
|
||||
return finishNode(result);
|
||||
}
|
||||
|
||||
@@ -6143,14 +6127,14 @@ namespace ts {
|
||||
export function parseIsolatedJSDocComment(content: string, start: number, length: number) {
|
||||
initializeState("file.js", content, ScriptTarget.Latest, /*_syntaxCursor:*/ undefined, ScriptKind.JS);
|
||||
sourceFile = <SourceFile>{ languageVariant: LanguageVariant.Standard, text: content };
|
||||
const jsDocComment = parseJSDocCommentWorker(start, length);
|
||||
const jsDoc = parseJSDocCommentWorker(start, length);
|
||||
const diagnostics = parseDiagnostics;
|
||||
clearState();
|
||||
|
||||
return jsDocComment ? { jsDocComment, diagnostics } : undefined;
|
||||
return jsDoc ? { jsDoc, diagnostics } : undefined;
|
||||
}
|
||||
|
||||
export function parseJSDocComment(parent: Node, start: number, length: number): JSDocComment {
|
||||
export function parseJSDocComment(parent: Node, start: number, length: number): JSDoc {
|
||||
const saveToken = currentToken;
|
||||
const saveParseDiagnosticsLength = parseDiagnostics.length;
|
||||
const saveParseErrorBeforeNextFinishedNode = parseErrorBeforeNextFinishedNode;
|
||||
@@ -6167,7 +6151,7 @@ namespace ts {
|
||||
return comment;
|
||||
}
|
||||
|
||||
export function parseJSDocCommentWorker(start: number, length: number): JSDocComment {
|
||||
export function parseJSDocCommentWorker(start: number, length: number): JSDoc {
|
||||
const content = sourceText;
|
||||
start = start || 0;
|
||||
const end = length === undefined ? content.length : start + length;
|
||||
@@ -6178,76 +6162,145 @@ namespace ts {
|
||||
Debug.assert(end <= content.length);
|
||||
|
||||
let tags: NodeArray<JSDocTag>;
|
||||
let result: JSDocComment;
|
||||
const comments: string[] = [];
|
||||
let result: JSDoc;
|
||||
|
||||
// Check for /** (JSDoc opening part)
|
||||
if (content.charCodeAt(start) === CharacterCodes.slash &&
|
||||
content.charCodeAt(start + 1) === CharacterCodes.asterisk &&
|
||||
content.charCodeAt(start + 2) === CharacterCodes.asterisk &&
|
||||
content.charCodeAt(start + 3) !== CharacterCodes.asterisk) {
|
||||
if (!isJsDocStart(content, start)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// + 3 for leading /**, - 5 in total for /** */
|
||||
scanner.scanRange(start + 3, length - 5, () => {
|
||||
// Initially we can parse out a tag. We also have seen a starting asterisk.
|
||||
// This is so that /** * @type */ doesn't parse.
|
||||
let canParseTag = true;
|
||||
let seenAsterisk = true;
|
||||
let advanceToken = true;
|
||||
let margin: number | undefined = undefined;
|
||||
let indent = start - Math.max(content.lastIndexOf("\n", start), 0) + 4;
|
||||
let text: string;
|
||||
|
||||
// + 3 for leading /**, - 5 in total for /** */
|
||||
scanner.scanRange(start + 3, length - 5, () => {
|
||||
// Initially we can parse out a tag. We also have seen a starting asterisk.
|
||||
// This is so that /** * @type */ doesn't parse.
|
||||
let canParseTag = true;
|
||||
let seenAsterisk = true;
|
||||
|
||||
nextJSDocToken();
|
||||
while (token() === SyntaxKind.WhitespaceTrivia) {
|
||||
nextJSDocToken();
|
||||
while (token() !== SyntaxKind.EndOfFileToken) {
|
||||
switch (token()) {
|
||||
case SyntaxKind.AtToken:
|
||||
if (canParseTag) {
|
||||
parseTag();
|
||||
}
|
||||
// This will take us to the end of the line, so it's OK to parse a tag on the next pass through the loop
|
||||
}
|
||||
if (token() === SyntaxKind.NewLineTrivia) {
|
||||
canParseTag = true;
|
||||
seenAsterisk = false;
|
||||
nextJSDocToken();
|
||||
}
|
||||
while (token() !== SyntaxKind.EndOfFileToken) {
|
||||
switch (token()) {
|
||||
case SyntaxKind.AtToken:
|
||||
if (canParseTag) {
|
||||
removeTrailingNewlines(comments);
|
||||
parseTag(indent);
|
||||
// This will take us past the end of the line, so it's OK to parse a tag on the next pass through the loop
|
||||
// NOTE: According to usejsdoc.org, a tag goes to end of line, except the last tag. But real-world comments may break this rule.
|
||||
seenAsterisk = false;
|
||||
break;
|
||||
advanceToken = false;
|
||||
margin = undefined;
|
||||
}
|
||||
else {
|
||||
comments.push(scanner.getTokenText());
|
||||
}
|
||||
indent++;
|
||||
break;
|
||||
|
||||
case SyntaxKind.NewLineTrivia:
|
||||
// After a line break, we can parse a tag, and we haven't seen an asterisk on the next line yet
|
||||
canParseTag = true;
|
||||
seenAsterisk = false;
|
||||
break;
|
||||
case SyntaxKind.NewLineTrivia:
|
||||
// After a line break, we can parse a tag, and we haven't seen an asterisk on the next line yet
|
||||
comments.push(scanner.getTokenText());
|
||||
canParseTag = true;
|
||||
seenAsterisk = false;
|
||||
indent = 0;
|
||||
break;
|
||||
|
||||
case SyntaxKind.AsteriskToken:
|
||||
if (seenAsterisk) {
|
||||
// If we've already seen an asterisk, then we can no longer parse a tag on this line
|
||||
canParseTag = false;
|
||||
}
|
||||
// Ignore the first asterisk on a line
|
||||
seenAsterisk = true;
|
||||
break;
|
||||
|
||||
case SyntaxKind.Identifier:
|
||||
// Anything else is doc comment text. We can't do anything with it. Because it
|
||||
// wasn't a tag, we can no longer parse a tag on this line until we hit the next
|
||||
// line break.
|
||||
case SyntaxKind.AsteriskToken:
|
||||
text = scanner.getTokenText();
|
||||
if (seenAsterisk) {
|
||||
// If we've already seen an asterisk, then we can no longer parse a tag on this line
|
||||
canParseTag = false;
|
||||
break;
|
||||
comments.push(text);
|
||||
if (!margin) {
|
||||
margin = indent;
|
||||
}
|
||||
}
|
||||
// Ignore the first asterisk on a line
|
||||
seenAsterisk = true;
|
||||
indent += text.length;
|
||||
break;
|
||||
|
||||
case SyntaxKind.EndOfFileToken:
|
||||
break;
|
||||
}
|
||||
case SyntaxKind.Identifier:
|
||||
// Anything else is doc comment text. We just save it. Because it
|
||||
// wasn't a tag, we can no longer parse a tag on this line until we hit the next
|
||||
// line break.
|
||||
text = scanner.getTokenText();
|
||||
comments.push(text);
|
||||
if (!margin) {
|
||||
margin = indent;
|
||||
}
|
||||
canParseTag = false;
|
||||
indent += text.length;
|
||||
break;
|
||||
|
||||
|
||||
case SyntaxKind.WhitespaceTrivia:
|
||||
// only collect whitespace if we *know* that we're just looking at comments, not a possible jsdoc tag
|
||||
text = scanner.getTokenText();
|
||||
if (!canParseTag || margin !== undefined && indent + text.length > margin) {
|
||||
comments.push(text.slice(margin - indent - 1));
|
||||
}
|
||||
indent += text.length;
|
||||
break;
|
||||
|
||||
case SyntaxKind.EndOfFileToken:
|
||||
break;
|
||||
|
||||
default:
|
||||
text = scanner.getTokenText();
|
||||
comments.push(text);
|
||||
indent += text.length;
|
||||
break;
|
||||
}
|
||||
if (advanceToken) {
|
||||
nextJSDocToken();
|
||||
}
|
||||
else {
|
||||
advanceToken = true;
|
||||
}
|
||||
}
|
||||
removeLeadingNewlines(comments);
|
||||
removeTrailingNewlines(comments);
|
||||
result = createJSDocComment();
|
||||
|
||||
result = createJSDocComment();
|
||||
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
|
||||
function createJSDocComment(): JSDocComment {
|
||||
if (!tags) {
|
||||
return undefined;
|
||||
function removeLeadingNewlines(comments: string[]) {
|
||||
while (comments.length && (comments[0] === "\n" || comments[0] === "\r")) {
|
||||
comments.shift();
|
||||
}
|
||||
}
|
||||
|
||||
const result = <JSDocComment>createNode(SyntaxKind.JSDocComment, start);
|
||||
function removeTrailingNewlines(comments: string[]) {
|
||||
while (comments.length && (comments[comments.length - 1] === "\n" || comments[comments.length - 1] === "\r")) {
|
||||
comments.pop();
|
||||
}
|
||||
}
|
||||
|
||||
function isJsDocStart(content: string, start: number) {
|
||||
return content.charCodeAt(start) === CharacterCodes.slash &&
|
||||
content.charCodeAt(start + 1) === CharacterCodes.asterisk &&
|
||||
content.charCodeAt(start + 2) === CharacterCodes.asterisk &&
|
||||
content.charCodeAt(start + 3) !== CharacterCodes.asterisk;
|
||||
}
|
||||
|
||||
function createJSDocComment(): JSDoc {
|
||||
const result = <JSDoc>createNode(SyntaxKind.JSDocComment, start);
|
||||
result.tags = tags;
|
||||
result.comment = comments.length ? comments.join("") : undefined;
|
||||
return finishNode(result, end);
|
||||
}
|
||||
|
||||
@@ -6257,78 +6310,158 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function parseTag(): void {
|
||||
function parseTag(indent: number) {
|
||||
Debug.assert(token() === SyntaxKind.AtToken);
|
||||
const atToken = createNode(SyntaxKind.AtToken, scanner.getTokenPos());
|
||||
atToken.end = scanner.getTextPos();
|
||||
nextJSDocToken();
|
||||
|
||||
const tagName = parseJSDocIdentifierName();
|
||||
skipWhitespace();
|
||||
if (!tagName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tag = handleTag(atToken, tagName) || handleUnknownTag(atToken, tagName);
|
||||
addTag(tag);
|
||||
}
|
||||
|
||||
function handleTag(atToken: Node, tagName: Identifier): JSDocTag {
|
||||
let tag: JSDocTag;
|
||||
if (tagName) {
|
||||
switch (tagName.text) {
|
||||
case "param":
|
||||
return handleParamTag(atToken, tagName);
|
||||
tag = parseParamTag(atToken, tagName);
|
||||
break;
|
||||
case "return":
|
||||
case "returns":
|
||||
return handleReturnTag(atToken, tagName);
|
||||
tag = parseReturnTag(atToken, tagName);
|
||||
break;
|
||||
case "template":
|
||||
return handleTemplateTag(atToken, tagName);
|
||||
tag = parseTemplateTag(atToken, tagName);
|
||||
break;
|
||||
case "type":
|
||||
return handleTypeTag(atToken, tagName);
|
||||
tag = parseTypeTag(atToken, tagName);
|
||||
break;
|
||||
case "typedef":
|
||||
return handleTypedefTag(atToken, tagName);
|
||||
tag = parseTypedefTag(atToken, tagName);
|
||||
break;
|
||||
default:
|
||||
tag = parseUnknownTag(atToken, tagName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
tag = parseUnknownTag(atToken, tagName);
|
||||
}
|
||||
|
||||
if (!tag) {
|
||||
// a badly malformed tag should not be added to the list of tags
|
||||
return;
|
||||
}
|
||||
addTag(tag, parseTagComments(indent + tag.end - tag.pos));
|
||||
}
|
||||
|
||||
function parseTagComments(indent: number) {
|
||||
const comments: string[] = [];
|
||||
let savingComments = false;
|
||||
let seenAsterisk = true;
|
||||
let done = false;
|
||||
let margin: number | undefined;
|
||||
let text: string;
|
||||
function pushComment(text: string) {
|
||||
if (!margin) {
|
||||
margin = indent;
|
||||
}
|
||||
comments.push(text);
|
||||
indent += text.length;
|
||||
}
|
||||
while (!done && token() !== SyntaxKind.EndOfFileToken) {
|
||||
text = scanner.getTokenText();
|
||||
switch (token()) {
|
||||
case SyntaxKind.NewLineTrivia:
|
||||
if (seenAsterisk) {
|
||||
savingComments = false;
|
||||
seenAsterisk = false;
|
||||
comments.push(text);
|
||||
}
|
||||
indent = 0;
|
||||
break;
|
||||
case SyntaxKind.AtToken:
|
||||
done = true;
|
||||
break;
|
||||
case SyntaxKind.WhitespaceTrivia:
|
||||
if (savingComments && seenAsterisk) {
|
||||
pushComment(text);
|
||||
}
|
||||
else {
|
||||
// if the whitespace crosses the margin, take only the whitespace that passes the margin
|
||||
if (margin !== undefined && indent + text.length > margin) {
|
||||
comments.push(text.slice(margin - indent - 1));
|
||||
}
|
||||
indent += text.length;
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.AsteriskToken:
|
||||
if (!seenAsterisk) {
|
||||
// leading asterisks start recording on the *next* (non-whitespace) token
|
||||
savingComments = false;
|
||||
indent += text.length;
|
||||
}
|
||||
// FALLTHROUGH to gather comments
|
||||
default:
|
||||
if (seenAsterisk) {
|
||||
savingComments = true; // leading identifiers start recording as well
|
||||
pushComment(text);
|
||||
}
|
||||
seenAsterisk = true;
|
||||
break;
|
||||
}
|
||||
if (!done) {
|
||||
nextJSDocToken();
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
removeLeadingNewlines(comments);
|
||||
removeTrailingNewlines(comments);
|
||||
return comments;
|
||||
}
|
||||
|
||||
function handleUnknownTag(atToken: Node, tagName: Identifier) {
|
||||
function parseUnknownTag(atToken: Node, tagName: Identifier) {
|
||||
const result = <JSDocTag>createNode(SyntaxKind.JSDocTag, atToken.pos);
|
||||
result.atToken = atToken;
|
||||
result.tagName = tagName;
|
||||
return finishNode(result);
|
||||
}
|
||||
|
||||
function addTag(tag: JSDocTag): void {
|
||||
if (tag) {
|
||||
if (!tags) {
|
||||
tags = <NodeArray<JSDocTag>>[];
|
||||
tags.pos = tag.pos;
|
||||
}
|
||||
function addTag(tag: JSDocTag, comments: string[]): void {
|
||||
tag.comment = comments.join("");
|
||||
|
||||
tags.push(tag);
|
||||
tags.end = tag.end;
|
||||
if (!tags) {
|
||||
tags = <NodeArray<JSDocTag>>[];
|
||||
tags.pos = tag.pos;
|
||||
}
|
||||
|
||||
tags.push(tag);
|
||||
tags.end = tag.end;
|
||||
}
|
||||
|
||||
function tryParseTypeExpression(): JSDocTypeExpression {
|
||||
if (token() !== SyntaxKind.OpenBraceToken) {
|
||||
return undefined;
|
||||
}
|
||||
return tryParse(() => {
|
||||
skipWhitespace();
|
||||
if (token() !== SyntaxKind.OpenBraceToken) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const typeExpression = parseJSDocTypeExpression();
|
||||
return typeExpression;
|
||||
return parseJSDocTypeExpression();
|
||||
});
|
||||
}
|
||||
|
||||
function handleParamTag(atToken: Node, tagName: Identifier) {
|
||||
function parseParamTag(atToken: Node, tagName: Identifier) {
|
||||
let typeExpression = tryParseTypeExpression();
|
||||
|
||||
skipWhitespace();
|
||||
|
||||
let name: Identifier;
|
||||
let isBracketed: boolean;
|
||||
// Looking for something like '[foo]' or 'foo'
|
||||
if (parseOptionalToken(SyntaxKind.OpenBracketToken)) {
|
||||
name = parseJSDocIdentifierName();
|
||||
skipWhitespace();
|
||||
isBracketed = true;
|
||||
|
||||
// May have an optional default, e.g. '[foo = 42]'
|
||||
@@ -6365,11 +6498,12 @@ namespace ts {
|
||||
result.preParameterName = preName;
|
||||
result.typeExpression = typeExpression;
|
||||
result.postParameterName = postName;
|
||||
result.parameterName = postName || preName;
|
||||
result.isBracketed = isBracketed;
|
||||
return finishNode(result);
|
||||
}
|
||||
|
||||
function handleReturnTag(atToken: Node, tagName: Identifier): JSDocReturnTag {
|
||||
function parseReturnTag(atToken: Node, tagName: Identifier): JSDocReturnTag {
|
||||
if (forEach(tags, t => t.kind === SyntaxKind.JSDocReturnTag)) {
|
||||
parseErrorAtPosition(tagName.pos, scanner.getTokenPos() - tagName.pos, Diagnostics._0_tag_already_specified, tagName.text);
|
||||
}
|
||||
@@ -6381,7 +6515,7 @@ namespace ts {
|
||||
return finishNode(result);
|
||||
}
|
||||
|
||||
function handleTypeTag(atToken: Node, tagName: Identifier): JSDocTypeTag {
|
||||
function parseTypeTag(atToken: Node, tagName: Identifier): JSDocTypeTag {
|
||||
if (forEach(tags, t => t.kind === SyntaxKind.JSDocTypeTag)) {
|
||||
parseErrorAtPosition(tagName.pos, scanner.getTokenPos() - tagName.pos, Diagnostics._0_tag_already_specified, tagName.text);
|
||||
}
|
||||
@@ -6393,10 +6527,11 @@ namespace ts {
|
||||
return finishNode(result);
|
||||
}
|
||||
|
||||
function handlePropertyTag(atToken: Node, tagName: Identifier): JSDocPropertyTag {
|
||||
function parsePropertyTag(atToken: Node, tagName: Identifier): JSDocPropertyTag {
|
||||
const typeExpression = tryParseTypeExpression();
|
||||
skipWhitespace();
|
||||
const name = parseJSDocIdentifierName();
|
||||
skipWhitespace();
|
||||
if (!name) {
|
||||
parseErrorAtPosition(scanner.getStartPos(), /*length*/ 0, Diagnostics.Identifier_expected);
|
||||
return undefined;
|
||||
@@ -6410,7 +6545,7 @@ namespace ts {
|
||||
return finishNode(result);
|
||||
}
|
||||
|
||||
function handleTypedefTag(atToken: Node, tagName: Identifier): JSDocTypedefTag {
|
||||
function parseTypedefTag(atToken: Node, tagName: Identifier): JSDocTypedefTag {
|
||||
const typeExpression = tryParseTypeExpression();
|
||||
skipWhitespace();
|
||||
|
||||
@@ -6419,6 +6554,7 @@ namespace ts {
|
||||
typedefTag.tagName = tagName;
|
||||
typedefTag.name = parseJSDocIdentifierName();
|
||||
typedefTag.typeExpression = typeExpression;
|
||||
skipWhitespace();
|
||||
|
||||
if (typeExpression) {
|
||||
if (typeExpression.type.kind === SyntaxKind.JSDocTypeReference) {
|
||||
@@ -6488,6 +6624,7 @@ namespace ts {
|
||||
nextJSDocToken();
|
||||
|
||||
const tagName = parseJSDocIdentifierName();
|
||||
skipWhitespace();
|
||||
if (!tagName) {
|
||||
return false;
|
||||
}
|
||||
@@ -6498,21 +6635,21 @@ namespace ts {
|
||||
// already has a @type tag, terminate the parent tag now.
|
||||
return false;
|
||||
}
|
||||
parentTag.jsDocTypeTag = handleTypeTag(atToken, tagName);
|
||||
parentTag.jsDocTypeTag = parseTypeTag(atToken, tagName);
|
||||
return true;
|
||||
case "prop":
|
||||
case "property":
|
||||
if (!parentTag.jsDocPropertyTags) {
|
||||
parentTag.jsDocPropertyTags = <NodeArray<JSDocPropertyTag>>[];
|
||||
}
|
||||
const propertyTag = handlePropertyTag(atToken, tagName);
|
||||
const propertyTag = parsePropertyTag(atToken, tagName);
|
||||
parentTag.jsDocPropertyTags.push(propertyTag);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function handleTemplateTag(atToken: Node, tagName: Identifier): JSDocTemplateTag {
|
||||
function parseTemplateTag(atToken: Node, tagName: Identifier): JSDocTemplateTag {
|
||||
if (forEach(tags, t => t.kind === SyntaxKind.JSDocTemplateTag)) {
|
||||
parseErrorAtPosition(tagName.pos, scanner.getTokenPos() - tagName.pos, Diagnostics._0_tag_already_specified, tagName.text);
|
||||
}
|
||||
@@ -6523,6 +6660,7 @@ namespace ts {
|
||||
|
||||
while (true) {
|
||||
const name = parseJSDocIdentifierName();
|
||||
skipWhitespace();
|
||||
if (!name) {
|
||||
parseErrorAtPosition(scanner.getStartPos(), 0, Diagnostics.Identifier_expected);
|
||||
return undefined;
|
||||
@@ -6536,6 +6674,7 @@ namespace ts {
|
||||
|
||||
if (token() === SyntaxKind.CommaToken) {
|
||||
nextJSDocToken();
|
||||
skipWhitespace();
|
||||
}
|
||||
else {
|
||||
break;
|
||||
|
||||
+40
-27
@@ -599,10 +599,11 @@ namespace ts {
|
||||
* If false, whitespace is skipped until the first line break and comments between that location
|
||||
* and the next token are returned.
|
||||
* If true, comments occurring between the given position and the next line break are returned.
|
||||
* If there is no line break, then no comments are returned.
|
||||
*/
|
||||
function getCommentRanges(text: string, pos: number, trailing: boolean): CommentRange[] {
|
||||
let result: CommentRange[];
|
||||
let collecting = trailing || pos === 0;
|
||||
let needToSkipTrailingComment = pos > 0;
|
||||
while (pos < text.length) {
|
||||
const ch = text.charCodeAt(pos);
|
||||
switch (ch) {
|
||||
@@ -615,7 +616,11 @@ namespace ts {
|
||||
if (trailing) {
|
||||
return result;
|
||||
}
|
||||
collecting = true;
|
||||
else if (needToSkipTrailingComment) {
|
||||
// skip the first line if not trailing (it'll be part of the trailing comment).
|
||||
needToSkipTrailingComment = false;
|
||||
result = undefined;
|
||||
}
|
||||
if (result && result.length) {
|
||||
lastOrUndefined(result).hasTrailingNewLine = true;
|
||||
}
|
||||
@@ -651,11 +656,13 @@ namespace ts {
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
if (collecting) {
|
||||
|
||||
// if we are at the end of the file, don't add trailing comments.
|
||||
// end-of-file comments are added as leading comment of the end-of-file token.
|
||||
if (pos < text.length || !trailing) {
|
||||
if (!result) {
|
||||
result = [];
|
||||
}
|
||||
|
||||
result.push({ pos: startPos, end: pos, hasTrailingNewLine, kind });
|
||||
}
|
||||
continue;
|
||||
@@ -671,10 +678,10 @@ namespace ts {
|
||||
}
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
return !trailing ? result : undefined;
|
||||
}
|
||||
|
||||
return result;
|
||||
return !trailing ? result : undefined;
|
||||
}
|
||||
|
||||
export function getLeadingCommentRanges(text: string, pos: number): CommentRange[] {
|
||||
@@ -1689,40 +1696,46 @@ namespace ts {
|
||||
}
|
||||
|
||||
startPos = pos;
|
||||
|
||||
// Eat leading whitespace
|
||||
let ch = text.charCodeAt(pos);
|
||||
while (pos < end) {
|
||||
ch = text.charCodeAt(pos);
|
||||
if (isWhiteSpaceSingleLine(ch)) {
|
||||
pos++;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
tokenPos = pos;
|
||||
|
||||
const ch = text.charCodeAt(pos);
|
||||
switch (ch) {
|
||||
case CharacterCodes.tab:
|
||||
case CharacterCodes.verticalTab:
|
||||
case CharacterCodes.formFeed:
|
||||
case CharacterCodes.space:
|
||||
while (pos < end && isWhiteSpaceSingleLine(text.charCodeAt(pos))) {
|
||||
pos++;
|
||||
}
|
||||
return token = SyntaxKind.WhitespaceTrivia;
|
||||
case CharacterCodes.at:
|
||||
return pos += 1, token = SyntaxKind.AtToken;
|
||||
pos++;
|
||||
return token = SyntaxKind.AtToken;
|
||||
case CharacterCodes.lineFeed:
|
||||
case CharacterCodes.carriageReturn:
|
||||
return pos += 1, token = SyntaxKind.NewLineTrivia;
|
||||
pos++;
|
||||
return token = SyntaxKind.NewLineTrivia;
|
||||
case CharacterCodes.asterisk:
|
||||
return pos += 1, token = SyntaxKind.AsteriskToken;
|
||||
pos++;
|
||||
return token = SyntaxKind.AsteriskToken;
|
||||
case CharacterCodes.openBrace:
|
||||
return pos += 1, token = SyntaxKind.OpenBraceToken;
|
||||
pos++;
|
||||
return token = SyntaxKind.OpenBraceToken;
|
||||
case CharacterCodes.closeBrace:
|
||||
return pos += 1, token = SyntaxKind.CloseBraceToken;
|
||||
pos++;
|
||||
return token = SyntaxKind.CloseBraceToken;
|
||||
case CharacterCodes.openBracket:
|
||||
return pos += 1, token = SyntaxKind.OpenBracketToken;
|
||||
pos++;
|
||||
return token = SyntaxKind.OpenBracketToken;
|
||||
case CharacterCodes.closeBracket:
|
||||
return pos += 1, token = SyntaxKind.CloseBracketToken;
|
||||
pos++;
|
||||
return token = SyntaxKind.CloseBracketToken;
|
||||
case CharacterCodes.equals:
|
||||
return pos += 1, token = SyntaxKind.EqualsToken;
|
||||
pos++;
|
||||
return token = SyntaxKind.EqualsToken;
|
||||
case CharacterCodes.comma:
|
||||
return pos += 1, token = SyntaxKind.CommaToken;
|
||||
pos++;
|
||||
return token = SyntaxKind.CommaToken;
|
||||
}
|
||||
|
||||
if (isIdentifierStart(ch, ScriptTarget.Latest)) {
|
||||
|
||||
@@ -464,7 +464,7 @@ namespace ts {
|
||||
modifiers?: ModifiersArray; // Array of modifiers
|
||||
/* @internal */ id?: number; // Unique id (used to look up NodeLinks)
|
||||
parent?: Node; // Parent node (initialized by binding
|
||||
/* @internal */ jsDocComments?: JSDocComment[]; // JSDoc for the node, if it has any. Only for .js files.
|
||||
/* @internal */ jsDocComments?: JSDoc[]; // JSDoc for the node, if it has any. Only for .js files.
|
||||
/* @internal */ symbol?: Symbol; // Symbol declared by node (initialized by binding)
|
||||
/* @internal */ locals?: SymbolTable; // Locals associated with node (initialized by binding)
|
||||
/* @internal */ nextContainer?: Node; // Next container in declaration order (initialized by binding)
|
||||
@@ -1471,7 +1471,7 @@ namespace ts {
|
||||
|
||||
// @kind(SyntaxKind.JSDocRecordType)
|
||||
export interface JSDocRecordType extends JSDocType, TypeLiteralNode {
|
||||
members: NodeArray<JSDocRecordMember>;
|
||||
literal: TypeLiteralNode;
|
||||
}
|
||||
|
||||
// @kind(SyntaxKind.JSDocTypeReference)
|
||||
@@ -1519,14 +1519,16 @@ namespace ts {
|
||||
}
|
||||
|
||||
// @kind(SyntaxKind.JSDocComment)
|
||||
export interface JSDocComment extends Node {
|
||||
export interface JSDoc extends Node {
|
||||
tags: NodeArray<JSDocTag>;
|
||||
comment: string | undefined;
|
||||
}
|
||||
|
||||
// @kind(SyntaxKind.JSDocTag)
|
||||
export interface JSDocTag extends Node {
|
||||
atToken: Node;
|
||||
tagName: Identifier;
|
||||
comment: string | undefined;
|
||||
}
|
||||
|
||||
// @kind(SyntaxKind.JSDocTemplateTag)
|
||||
@@ -1565,9 +1567,13 @@ namespace ts {
|
||||
|
||||
// @kind(SyntaxKind.JSDocParameterTag)
|
||||
export interface JSDocParameterTag extends JSDocTag {
|
||||
/** the parameter name, if provided *before* the type (TypeScript-style) */
|
||||
preParameterName?: Identifier;
|
||||
typeExpression?: JSDocTypeExpression;
|
||||
/** the parameter name, if provided *after* the type (JSDoc-standard) */
|
||||
postParameterName?: Identifier;
|
||||
/** the parameter name, regardless of the location it was provided */
|
||||
parameterName: Identifier;
|
||||
isBracketed: boolean;
|
||||
}
|
||||
|
||||
|
||||
+118
-44
@@ -596,13 +596,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
export function getJsDocCommentsFromText(node: Node, text: string) {
|
||||
const commentRanges = (node.kind === SyntaxKind.Parameter ||
|
||||
node.kind === SyntaxKind.TypeParameter ||
|
||||
node.kind === SyntaxKind.FunctionExpression ||
|
||||
node.kind === SyntaxKind.ArrowFunction) ?
|
||||
concatenate(getTrailingCommentRanges(text, node.pos), getLeadingCommentRanges(text, node.pos)) :
|
||||
getLeadingCommentRangesOfNodeFromText(node, text);
|
||||
return filter(commentRanges, isJsDocComment);
|
||||
return filter(getLeadingCommentRanges(text, node.pos), isJsDocComment);
|
||||
|
||||
function isJsDocComment(comment: CommentRange) {
|
||||
// True if the comment starts with '/**' but not if it is '/**/'
|
||||
@@ -1356,39 +1350,75 @@ namespace ts {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const jsDocComments = getJSDocComments(node, checkParentVariableStatement);
|
||||
if (!jsDocComments) {
|
||||
const jsDocTags = getJSDocTags(node, checkParentVariableStatement);
|
||||
if (!jsDocTags) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
for (const jsDocComment of jsDocComments) {
|
||||
for (const tag of jsDocComment.tags) {
|
||||
if (tag.kind === kind) {
|
||||
return tag;
|
||||
}
|
||||
for (const tag of jsDocTags) {
|
||||
if (tag.kind === kind) {
|
||||
return tag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getJSDocComments(node: Node, checkParentVariableStatement: boolean): JSDocComment[] {
|
||||
if (node.jsDocComments) {
|
||||
return node.jsDocComments;
|
||||
function append<T>(previous: T[] | undefined, additional: T[] | undefined): T[] | undefined {
|
||||
if (additional) {
|
||||
if (!previous) {
|
||||
previous = [];
|
||||
}
|
||||
for (const x of additional) {
|
||||
previous.push(x);
|
||||
}
|
||||
}
|
||||
// Try to recognize this pattern when node is initializer of variable declaration and JSDoc comments are on containing variable statement.
|
||||
// /**
|
||||
// * @param {number} name
|
||||
// * @returns {number}
|
||||
// */
|
||||
// var x = function(name) { return name.length; }
|
||||
if (checkParentVariableStatement) {
|
||||
const isInitializerOfVariableDeclarationInStatement =
|
||||
node.parent.kind === SyntaxKind.VariableDeclaration &&
|
||||
(<VariableDeclaration>node.parent).initializer === node &&
|
||||
node.parent.parent.parent.kind === SyntaxKind.VariableStatement;
|
||||
return previous;
|
||||
}
|
||||
|
||||
const variableStatementNode = isInitializerOfVariableDeclarationInStatement ? node.parent.parent.parent : undefined;
|
||||
export function getJSDocComments(node: Node, checkParentVariableStatement: boolean): string[] {
|
||||
return getJSDocs(node, checkParentVariableStatement, docs => map(docs, doc => doc.comment), tags => map(tags, tag => tag.comment));
|
||||
}
|
||||
|
||||
function getJSDocTags(node: Node, checkParentVariableStatement: boolean): JSDocTag[] {
|
||||
return getJSDocs(node, checkParentVariableStatement, docs => {
|
||||
let result: JSDocTag[] = [];
|
||||
for (const doc of docs) {
|
||||
if (doc.tags) {
|
||||
result.push(...doc.tags);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}, tags => tags);
|
||||
}
|
||||
|
||||
function getJSDocs<T>(node: Node, checkParentVariableStatement: boolean, getDocs: (docs: JSDoc[]) => T[], getTags: (tags: JSDocTag[]) => T[]): T[] {
|
||||
// TODO: Get rid of getJsDocComments and friends (note the lowercase 's' in Js)
|
||||
// TODO: A lot of this work should be cached, maybe. I guess it's only used in services right now...
|
||||
let result: T[] = undefined;
|
||||
// prepend documentation from parent sources
|
||||
if (checkParentVariableStatement) {
|
||||
// Try to recognize this pattern when node is initializer of variable declaration and JSDoc comments are on containing variable statement.
|
||||
// /**
|
||||
// * @param {number} name
|
||||
// * @returns {number}
|
||||
// */
|
||||
// var x = function(name) { return name.length; }
|
||||
const isInitializerOfVariableDeclarationInStatement =
|
||||
isVariableLike(node.parent) &&
|
||||
(node.parent).initializer === node &&
|
||||
node.parent.parent.parent.kind === SyntaxKind.VariableStatement;
|
||||
const isVariableOfVariableDeclarationStatement = isVariableLike(node) &&
|
||||
node.parent.parent.kind === SyntaxKind.VariableStatement;
|
||||
|
||||
const variableStatementNode =
|
||||
isInitializerOfVariableDeclarationInStatement ? node.parent.parent.parent :
|
||||
isVariableOfVariableDeclarationStatement ? node.parent.parent :
|
||||
undefined;
|
||||
if (variableStatementNode) {
|
||||
return variableStatementNode.jsDocComments;
|
||||
result = append(result, getJSDocs(variableStatementNode, checkParentVariableStatement, getDocs, getTags));
|
||||
}
|
||||
if (node.kind === SyntaxKind.ModuleDeclaration &&
|
||||
node.parent && node.parent.kind === SyntaxKind.ModuleDeclaration) {
|
||||
result = append(result, getJSDocs(node.parent, checkParentVariableStatement, getDocs, getTags));
|
||||
}
|
||||
|
||||
// Also recognize when the node is the RHS of an assignment expression
|
||||
@@ -1399,16 +1429,62 @@ namespace ts {
|
||||
(parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken &&
|
||||
parent.parent.kind === SyntaxKind.ExpressionStatement;
|
||||
if (isSourceOfAssignmentExpressionStatement) {
|
||||
return parent.parent.jsDocComments;
|
||||
result = append(result, getJSDocs(parent.parent, checkParentVariableStatement, getDocs, getTags));
|
||||
}
|
||||
|
||||
const isPropertyAssignmentExpression = parent && parent.kind === SyntaxKind.PropertyAssignment;
|
||||
if (isPropertyAssignmentExpression) {
|
||||
return parent.jsDocComments;
|
||||
result = append(result, getJSDocs(parent, checkParentVariableStatement, getDocs, getTags));
|
||||
}
|
||||
|
||||
// Pull parameter comments from declaring function as well
|
||||
if (node.kind === SyntaxKind.Parameter) {
|
||||
const paramTags = getJSDocParameterTag(node as ParameterDeclaration, checkParentVariableStatement);
|
||||
if (paramTags) {
|
||||
result = append(result, getTags(paramTags));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
if (isVariableLike(node) && node.initializer) {
|
||||
result = append(result, getJSDocs(node.initializer, /*checkParentVariableStatement*/ false, getDocs, getTags));
|
||||
}
|
||||
|
||||
if (node.jsDocComments) {
|
||||
if (result) {
|
||||
result = append(result, getDocs(node.jsDocComments));
|
||||
}
|
||||
else {
|
||||
return getDocs(node.jsDocComments);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function getJSDocParameterTag(param: ParameterDeclaration, checkParentVariableStatement: boolean): JSDocTag[] {
|
||||
const func = param.parent as FunctionLikeDeclaration;
|
||||
const tags = getJSDocTags(func, checkParentVariableStatement);
|
||||
if (!param.name) {
|
||||
// this is an anonymous jsdoc param from a `function(type1, type2): type3` specification
|
||||
const i = func.parameters.indexOf(param);
|
||||
const paramTags = filter(tags, tag => tag.kind === SyntaxKind.JSDocParameterTag);
|
||||
if (paramTags && 0 <= i && i < paramTags.length) {
|
||||
return [paramTags[i]];
|
||||
}
|
||||
}
|
||||
else if (param.name.kind === SyntaxKind.Identifier) {
|
||||
const name = (param.name as Identifier).text;
|
||||
const paramTags = filter(tags, tag => tag.kind === SyntaxKind.JSDocParameterTag && (tag as JSDocParameterTag).parameterName.text === name);
|
||||
if (paramTags) {
|
||||
return paramTags;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// TODO: it's a destructured parameter, so it should look up an "object type" series of multiple lines
|
||||
// But multi-line object types aren't supported yet either
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function getJSDocTypeTag(node: Node): JSDocTypeTag {
|
||||
@@ -1429,17 +1505,15 @@ namespace ts {
|
||||
// annotation.
|
||||
const parameterName = (<Identifier>parameter.name).text;
|
||||
|
||||
const jsDocComments = getJSDocComments(parameter.parent, /*checkParentVariableStatement*/ true);
|
||||
if (jsDocComments) {
|
||||
for (const jsDocComment of jsDocComments) {
|
||||
for (const tag of jsDocComment.tags) {
|
||||
if (tag.kind === SyntaxKind.JSDocParameterTag) {
|
||||
const parameterTag = <JSDocParameterTag>tag;
|
||||
const name = parameterTag.preParameterName || parameterTag.postParameterName;
|
||||
if (name.text === parameterName) {
|
||||
return parameterTag;
|
||||
}
|
||||
}
|
||||
const jsDocTags = getJSDocTags(parameter.parent, /*checkParentVariableStatement*/ true);
|
||||
if (!jsDocTags) {
|
||||
return undefined;
|
||||
}
|
||||
for (const tag of jsDocTags) {
|
||||
if (tag.kind === SyntaxKind.JSDocParameterTag) {
|
||||
const parameterTag = <JSDocParameterTag>tag;
|
||||
if (parameterTag.parameterName.text === parameterName) {
|
||||
return parameterTag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+52
-352
@@ -194,7 +194,7 @@ namespace ts {
|
||||
public end: number;
|
||||
public flags: NodeFlags;
|
||||
public parent: Node;
|
||||
public jsDocComments: JSDocComment[];
|
||||
public jsDocComments: JSDoc[];
|
||||
private _children: Node[];
|
||||
|
||||
constructor(kind: SyntaxKind, pos: number, end: number) {
|
||||
@@ -355,7 +355,7 @@ namespace ts {
|
||||
public end: number;
|
||||
public flags: NodeFlags;
|
||||
public parent: Node;
|
||||
public jsDocComments: JSDocComment[];
|
||||
public jsDocComments: JSDoc[];
|
||||
public __tokenTag: any;
|
||||
|
||||
constructor(pos: number, end: number) {
|
||||
@@ -474,349 +474,48 @@ namespace ts {
|
||||
}
|
||||
|
||||
function getJsDocCommentsFromDeclarations(declarations: Declaration[], name: string, canUseParsedParamTagComments: boolean) {
|
||||
// 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>
|
||||
const documentationComment = <SymbolDisplayPart[]>[];
|
||||
const docComments = getJsDocCommentsSeparatedByNewLines();
|
||||
ts.forEach(docComments, docComment => {
|
||||
if (documentationComment.length) {
|
||||
documentationComment.push(lineBreakPart());
|
||||
forEachUnique(declarations, declaration => {
|
||||
const comments = getJSDocComments(declaration, /*checkParentVariableStatement*/ true);
|
||||
if (!comments) {
|
||||
return;
|
||||
}
|
||||
for (const comment of comments) {
|
||||
if (comment) {
|
||||
if (documentationComment.length) {
|
||||
documentationComment.push(lineBreakPart());
|
||||
}
|
||||
documentationComment.push(textPart(comment));
|
||||
}
|
||||
}
|
||||
documentationComment.push(docComment);
|
||||
});
|
||||
|
||||
return documentationComment;
|
||||
}
|
||||
|
||||
function getJsDocCommentsSeparatedByNewLines() {
|
||||
const paramTag = "@param";
|
||||
const jsDocCommentParts: SymbolDisplayPart[] = [];
|
||||
|
||||
ts.forEach(declarations, (declaration, indexOfDeclaration) => {
|
||||
// Make sure we are collecting doc comment from declaration once,
|
||||
// In case of 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>
|
||||
if (indexOf(declarations, declaration) === indexOfDeclaration) {
|
||||
const sourceFileOfDeclaration = getSourceFileOfNode(declaration);
|
||||
// If it is parameter - try and get the jsDoc comment with @param tag from function declaration's jsDoc comments
|
||||
if (canUseParsedParamTagComments && declaration.kind === SyntaxKind.Parameter) {
|
||||
if ((declaration.parent.kind === SyntaxKind.FunctionExpression || declaration.parent.kind === SyntaxKind.ArrowFunction) &&
|
||||
declaration.parent.parent.kind === SyntaxKind.VariableDeclaration) {
|
||||
addCommentParts(declaration.parent.parent.parent, sourceFileOfDeclaration, getCleanedParamJsDocComment);
|
||||
}
|
||||
addCommentParts(declaration.parent, sourceFileOfDeclaration, getCleanedParamJsDocComment);
|
||||
}
|
||||
|
||||
// If this is left side of dotted module declaration, there is no doc comments associated with this node
|
||||
if (declaration.kind === SyntaxKind.ModuleDeclaration && (<ModuleDeclaration>declaration).body && (<ModuleDeclaration>declaration).body.kind === SyntaxKind.ModuleDeclaration) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((declaration.kind === SyntaxKind.FunctionExpression || declaration.kind === SyntaxKind.ArrowFunction) &&
|
||||
declaration.parent.kind === SyntaxKind.VariableDeclaration) {
|
||||
addCommentParts(declaration.parent.parent, sourceFileOfDeclaration, getCleanedJsDocComment);
|
||||
}
|
||||
|
||||
// If this is dotted module name, get the doc comments from the parent
|
||||
while (declaration.kind === SyntaxKind.ModuleDeclaration && declaration.parent.kind === SyntaxKind.ModuleDeclaration) {
|
||||
declaration = <ModuleDeclaration>declaration.parent;
|
||||
}
|
||||
addCommentParts(declaration.kind === SyntaxKind.VariableDeclaration ? declaration.parent.parent : declaration,
|
||||
sourceFileOfDeclaration,
|
||||
getCleanedJsDocComment);
|
||||
|
||||
if (declaration.kind === SyntaxKind.VariableDeclaration) {
|
||||
const init = (declaration as VariableDeclaration).initializer;
|
||||
if (init && (init.kind === SyntaxKind.FunctionExpression || init.kind === SyntaxKind.ArrowFunction)) {
|
||||
// Get the cleaned js doc comment text from the initializer
|
||||
addCommentParts(init, sourceFileOfDeclaration, getCleanedJsDocComment);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return jsDocCommentParts;
|
||||
|
||||
function addCommentParts(commented: Node,
|
||||
sourceFileOfDeclaration: SourceFile,
|
||||
getCommentPart: (pos: number, end: number, file: SourceFile) => SymbolDisplayPart[]): void {
|
||||
const ranges = getJsDocCommentTextRange(commented, sourceFileOfDeclaration);
|
||||
// Get the cleaned js doc comment text from the declaration
|
||||
ts.forEach(ranges, jsDocCommentTextRange => {
|
||||
const cleanedComment = getCommentPart(jsDocCommentTextRange.pos, jsDocCommentTextRange.end, sourceFileOfDeclaration);
|
||||
if (cleanedComment) {
|
||||
addRange(jsDocCommentParts, cleanedComment);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getJsDocCommentTextRange(node: Node, sourceFile: SourceFile): TextRange[] {
|
||||
return ts.map(getJsDocComments(node, sourceFile),
|
||||
jsDocComment => {
|
||||
return {
|
||||
pos: jsDocComment.pos + "/*".length, // Consume /* from the comment
|
||||
end: jsDocComment.end - "*/".length // Trim off comment end indicator
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function consumeWhiteSpacesOnTheLine(pos: number, end: number, sourceFile: SourceFile, maxSpacesToRemove?: number) {
|
||||
if (maxSpacesToRemove !== undefined) {
|
||||
end = Math.min(end, pos + maxSpacesToRemove);
|
||||
}
|
||||
|
||||
for (; pos < end; pos++) {
|
||||
const ch = sourceFile.text.charCodeAt(pos);
|
||||
if (!isWhiteSpaceSingleLine(ch)) {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
return end;
|
||||
}
|
||||
|
||||
function consumeLineBreaks(pos: number, end: number, sourceFile: SourceFile) {
|
||||
while (pos < end && isLineBreak(sourceFile.text.charCodeAt(pos))) {
|
||||
pos++;
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
function isName(pos: number, end: number, sourceFile: SourceFile, name: string) {
|
||||
return pos + name.length < end &&
|
||||
sourceFile.text.substr(pos, name.length) === name &&
|
||||
isWhiteSpace(sourceFile.text.charCodeAt(pos + name.length));
|
||||
}
|
||||
|
||||
function isParamTag(pos: number, end: number, sourceFile: SourceFile) {
|
||||
// If it is @param tag
|
||||
return isName(pos, end, sourceFile, paramTag);
|
||||
}
|
||||
|
||||
function pushDocCommentLineText(docComments: SymbolDisplayPart[], text: string, blankLineCount: number) {
|
||||
// Add the empty lines in between texts
|
||||
while (blankLineCount) {
|
||||
blankLineCount--;
|
||||
docComments.push(textPart(""));
|
||||
}
|
||||
|
||||
docComments.push(textPart(text));
|
||||
}
|
||||
|
||||
function getCleanedJsDocComment(pos: number, end: number, sourceFile: SourceFile) {
|
||||
let spacesToRemoveAfterAsterisk: number;
|
||||
const docComments: SymbolDisplayPart[] = [];
|
||||
let blankLineCount = 0;
|
||||
let isInParamTag = false;
|
||||
|
||||
while (pos < end) {
|
||||
let docCommentTextOfLine = "";
|
||||
// First consume leading white space
|
||||
pos = consumeWhiteSpacesOnTheLine(pos, end, sourceFile);
|
||||
|
||||
// If the comment starts with '*' consume the spaces on this line
|
||||
if (pos < end && sourceFile.text.charCodeAt(pos) === CharacterCodes.asterisk) {
|
||||
const lineStartPos = pos + 1;
|
||||
pos = consumeWhiteSpacesOnTheLine(pos + 1, end, sourceFile, spacesToRemoveAfterAsterisk);
|
||||
|
||||
// Set the spaces to remove after asterisk as margin if not already set
|
||||
if (spacesToRemoveAfterAsterisk === undefined && pos < end && !isLineBreak(sourceFile.text.charCodeAt(pos))) {
|
||||
spacesToRemoveAfterAsterisk = pos - lineStartPos;
|
||||
}
|
||||
}
|
||||
else if (spacesToRemoveAfterAsterisk === undefined) {
|
||||
spacesToRemoveAfterAsterisk = 0;
|
||||
}
|
||||
|
||||
// Analyze text on this line
|
||||
while (pos < end && !isLineBreak(sourceFile.text.charCodeAt(pos))) {
|
||||
const ch = sourceFile.text.charAt(pos);
|
||||
if (ch === "@") {
|
||||
// If it is @param tag
|
||||
if (isParamTag(pos, end, sourceFile)) {
|
||||
isInParamTag = true;
|
||||
pos += paramTag.length;
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
isInParamTag = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the ch to doc text if we arent in param tag
|
||||
if (!isInParamTag) {
|
||||
docCommentTextOfLine += ch;
|
||||
}
|
||||
|
||||
// Scan next character
|
||||
pos++;
|
||||
}
|
||||
|
||||
// Continue with next line
|
||||
pos = consumeLineBreaks(pos, end, sourceFile);
|
||||
if (docCommentTextOfLine) {
|
||||
pushDocCommentLineText(docComments, docCommentTextOfLine, blankLineCount);
|
||||
blankLineCount = 0;
|
||||
}
|
||||
else if (!isInParamTag && docComments.length) {
|
||||
// This is blank line when there is text already parsed
|
||||
blankLineCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return docComments;
|
||||
}
|
||||
|
||||
function getCleanedParamJsDocComment(pos: number, end: number, sourceFile: SourceFile) {
|
||||
let paramHelpStringMargin: number;
|
||||
const paramDocComments: SymbolDisplayPart[] = [];
|
||||
while (pos < end) {
|
||||
if (isParamTag(pos, end, sourceFile)) {
|
||||
let blankLineCount = 0;
|
||||
let recordedParamTag = false;
|
||||
// Consume leading spaces
|
||||
pos = consumeWhiteSpaces(pos + paramTag.length);
|
||||
if (pos >= end) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Ignore type expression
|
||||
if (sourceFile.text.charCodeAt(pos) === CharacterCodes.openBrace) {
|
||||
pos++;
|
||||
for (let curlies = 1; pos < end; pos++) {
|
||||
const charCode = sourceFile.text.charCodeAt(pos);
|
||||
|
||||
// { character means we need to find another } to match the found one
|
||||
if (charCode === CharacterCodes.openBrace) {
|
||||
curlies++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// } char
|
||||
if (charCode === CharacterCodes.closeBrace) {
|
||||
curlies--;
|
||||
if (curlies === 0) {
|
||||
// We do not have any more } to match the type expression is ignored completely
|
||||
pos++;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
// there are more { to be matched with }
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Found start of another tag
|
||||
if (charCode === CharacterCodes.at) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Consume white spaces
|
||||
pos = consumeWhiteSpaces(pos);
|
||||
if (pos >= end) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Parameter name
|
||||
if (isName(pos, end, sourceFile, name)) {
|
||||
// Found the parameter we are looking for consume white spaces
|
||||
pos = consumeWhiteSpaces(pos + name.length);
|
||||
if (pos >= end) {
|
||||
break;
|
||||
}
|
||||
|
||||
let paramHelpString = "";
|
||||
const firstLineParamHelpStringPos = pos;
|
||||
while (pos < end) {
|
||||
const ch = sourceFile.text.charCodeAt(pos);
|
||||
|
||||
// at line break, set this comment line text and go to next line
|
||||
if (isLineBreak(ch)) {
|
||||
if (paramHelpString) {
|
||||
pushDocCommentLineText(paramDocComments, paramHelpString, blankLineCount);
|
||||
paramHelpString = "";
|
||||
blankLineCount = 0;
|
||||
recordedParamTag = true;
|
||||
}
|
||||
else if (recordedParamTag) {
|
||||
blankLineCount++;
|
||||
}
|
||||
|
||||
// Get the pos after cleaning start of the line
|
||||
setPosForParamHelpStringOnNextLine(firstLineParamHelpStringPos);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Done scanning param help string - next tag found
|
||||
if (ch === CharacterCodes.at) {
|
||||
break;
|
||||
}
|
||||
|
||||
paramHelpString += sourceFile.text.charAt(pos);
|
||||
|
||||
// Go to next character
|
||||
pos++;
|
||||
}
|
||||
|
||||
// If there is param help text, add it top the doc comments
|
||||
if (paramHelpString) {
|
||||
pushDocCommentLineText(paramDocComments, paramHelpString, blankLineCount);
|
||||
}
|
||||
paramHelpStringMargin = undefined;
|
||||
}
|
||||
|
||||
// If this is the start of another tag, continue with the loop in search of param tag with symbol name
|
||||
if (sourceFile.text.charCodeAt(pos) === CharacterCodes.at) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Next character
|
||||
pos++;
|
||||
}
|
||||
|
||||
return paramDocComments;
|
||||
|
||||
function consumeWhiteSpaces(pos: number) {
|
||||
while (pos < end && isWhiteSpaceSingleLine(sourceFile.text.charCodeAt(pos))) {
|
||||
pos++;
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
function setPosForParamHelpStringOnNextLine(firstLineParamHelpStringPos: number) {
|
||||
// Get the pos after consuming line breaks
|
||||
pos = consumeLineBreaks(pos, end, sourceFile);
|
||||
if (pos >= end) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (paramHelpStringMargin === undefined) {
|
||||
paramHelpStringMargin = sourceFile.getLineAndCharacterOfPosition(firstLineParamHelpStringPos).character;
|
||||
}
|
||||
|
||||
// Now consume white spaces max
|
||||
const startOfLinePos = pos;
|
||||
pos = consumeWhiteSpacesOnTheLine(pos, end, sourceFile, paramHelpStringMargin);
|
||||
if (pos >= end) {
|
||||
return;
|
||||
}
|
||||
|
||||
const consumedSpaces = pos - startOfLinePos;
|
||||
if (consumedSpaces < paramHelpStringMargin) {
|
||||
const ch = sourceFile.text.charCodeAt(pos);
|
||||
if (ch === CharacterCodes.asterisk) {
|
||||
// Consume more spaces after asterisk
|
||||
pos = consumeWhiteSpacesOnTheLine(pos + 1, end, sourceFile, paramHelpStringMargin - consumedSpaces - 1);
|
||||
}
|
||||
/**
|
||||
* Iterates through 'array' by index and performs the callback on each element of array until the callback
|
||||
* returns a truthy value, then returns that value.
|
||||
* If no such value is found, the callback is applied to each element of array and undefined is returned.
|
||||
*/
|
||||
export function forEachUnique<T, U>(array: T[], callback: (element: T, index: number) => U): U {
|
||||
if (array) {
|
||||
for (let i = 0, len = array.length; i < len; i++) {
|
||||
if (indexOf(array, array[i]) === i) {
|
||||
const result = callback(array[i], i);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
class TypeObject implements Type {
|
||||
@@ -7498,9 +7197,9 @@ namespace ts {
|
||||
// See if this is a doc comment. If so, we'll classify certain portions of it
|
||||
// specially.
|
||||
const docCommentAndDiagnostics = parseIsolatedJSDocComment(sourceFile.text, start, width);
|
||||
if (docCommentAndDiagnostics && docCommentAndDiagnostics.jsDocComment) {
|
||||
docCommentAndDiagnostics.jsDocComment.parent = token;
|
||||
classifyJSDocComment(docCommentAndDiagnostics.jsDocComment);
|
||||
if (docCommentAndDiagnostics && docCommentAndDiagnostics.jsDoc) {
|
||||
docCommentAndDiagnostics.jsDoc.parent = token;
|
||||
classifyJSDocComment(docCommentAndDiagnostics.jsDoc);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -7513,22 +7212,22 @@ namespace ts {
|
||||
pushClassification(start, width, ClassificationType.comment);
|
||||
}
|
||||
|
||||
function classifyJSDocComment(docComment: JSDocComment) {
|
||||
function classifyJSDocComment(docComment: JSDoc) {
|
||||
let pos = docComment.pos;
|
||||
if (docComment.tags) {
|
||||
for (const tag of docComment.tags) {
|
||||
// As we walk through each tag, classify the portion of text from the end of
|
||||
// the last tag (or the start of the entire doc comment) as 'comment'.
|
||||
if (tag.pos !== pos) {
|
||||
pushCommentRange(pos, tag.pos - pos);
|
||||
}
|
||||
|
||||
for (const tag of docComment.tags) {
|
||||
// As we walk through each tag, classify the portion of text from the end of
|
||||
// the last tag (or the start of the entire doc comment) as 'comment'.
|
||||
if (tag.pos !== pos) {
|
||||
pushCommentRange(pos, tag.pos - pos);
|
||||
}
|
||||
pushClassification(tag.atToken.pos, tag.atToken.end - tag.atToken.pos, ClassificationType.punctuation);
|
||||
pushClassification(tag.tagName.pos, tag.tagName.end - tag.tagName.pos, ClassificationType.docCommentTagName);
|
||||
|
||||
pushClassification(tag.atToken.pos, tag.atToken.end - tag.atToken.pos, ClassificationType.punctuation);
|
||||
pushClassification(tag.tagName.pos, tag.tagName.end - tag.tagName.pos, ClassificationType.docCommentTagName);
|
||||
pos = tag.tagName.end;
|
||||
|
||||
pos = tag.tagName.end;
|
||||
|
||||
switch (tag.kind) {
|
||||
switch (tag.kind) {
|
||||
case SyntaxKind.JSDocParameterTag:
|
||||
processJSDocParameterTag(<JSDocParameterTag>tag);
|
||||
break;
|
||||
@@ -7541,9 +7240,10 @@ namespace ts {
|
||||
case SyntaxKind.JSDocReturnTag:
|
||||
processElement((<JSDocReturnTag>tag).typeExpression);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pos = tag.end;
|
||||
pos = tag.end;
|
||||
}
|
||||
}
|
||||
|
||||
if (pos !== docComment.end) {
|
||||
|
||||
@@ -569,9 +569,11 @@ namespace ts {
|
||||
if (node) {
|
||||
if (node.jsDocComments) {
|
||||
for (const jsDocComment of node.jsDocComments) {
|
||||
for (const tag of jsDocComment.tags) {
|
||||
if (tag.pos <= position && position <= tag.end) {
|
||||
return tag;
|
||||
if (jsDocComment.tags) {
|
||||
for (const tag of jsDocComment.tags) {
|
||||
if (tag.pos <= position && position <= tag.end) {
|
||||
return tag;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -946,4 +948,4 @@ namespace ts {
|
||||
}
|
||||
return { configJsonObject, diagnostics };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user