diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 2757c15c5241d..8eaa005b62e3d 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -6441,7 +6441,6 @@ namespace ts { // for malformed examples like `/** @param {string} x @returns {number} the length */` state = JSDocState.BeginningOfLine; margin = undefined; - indent++; } else { pushComment(scanner.getTokenText()); @@ -6536,32 +6535,38 @@ namespace ts { } } - function skipWhitespaceOrAsterisk(): void { + function skipWhitespaceOrAsterisk(): string { if (token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia) { if (lookAhead(isNextNonwhitespaceTokenEndOfFile)) { - return; // Don't skip whitespace prior to EoF (or end of comment) - that shouldn't be included in any node's range + return ""; // Don't skip whitespace prior to EoF (or end of comment) - that shouldn't be included in any node's range } } let precedingLineBreak = scanner.hasPrecedingLineBreak(); + let seenLineBreak = false; + let indentText = ""; while ((precedingLineBreak && token() === SyntaxKind.AsteriskToken) || token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia) { + indentText += scanner.getTokenText(); if (token() === SyntaxKind.NewLineTrivia) { precedingLineBreak = true; + seenLineBreak = true; + indentText = ""; } else if (token() === SyntaxKind.AsteriskToken) { precedingLineBreak = false; } nextJSDocToken(); } + return seenLineBreak ? indentText : ""; } - function parseTag(indent: number) { + function parseTag(margin: number) { Debug.assert(token() === SyntaxKind.AtToken); const start = scanner.getTokenPos(); nextJSDocToken(); const tagName = parseJSDocIdentifierName(/*message*/ undefined); - skipWhitespaceOrAsterisk(); + const indentText = skipWhitespaceOrAsterisk(); let tag: JSDocTag | undefined; switch (tagName.escapedText) { @@ -6582,7 +6587,7 @@ namespace ts { case "arg": case "argument": case "param": - return parseParameterOrPropertyTag(start, tagName, PropertyLikeParse.Parameter, indent); + return parseParameterOrPropertyTag(start, tagName, PropertyLikeParse.Parameter, margin); case "return": case "returns": tag = parseReturnTag(start, tagName); @@ -6594,10 +6599,10 @@ namespace ts { tag = parseTypeTag(start, tagName); break; case "typedef": - tag = parseTypedefTag(start, tagName, indent); + tag = parseTypedefTag(start, tagName, margin); break; case "callback": - tag = parseCallbackTag(start, tagName, indent); + tag = parseCallbackTag(start, tagName, margin); break; default: tag = parseUnknownTag(start, tagName); @@ -6606,12 +6611,15 @@ namespace ts { if (!tag.comment) { // some tags, like typedef and callback, have already parsed their comments earlier - tag.comment = parseTagComments(indent + tag.end - tag.pos); + if (!indentText) { + margin += tag.end - tag.pos; + } + tag.comment = parseTagComments(margin, indentText.slice(margin)); } return tag; } - function parseTagComments(indent: number): string | undefined { + function parseTagComments(indent: number, initialMargin?: string): string | undefined { const comments: string[] = []; let state = JSDocState.BeginningOfLine; let margin: number | undefined; @@ -6622,6 +6630,11 @@ namespace ts { comments.push(text); indent += text.length; } + if (initialMargin) { + // jump straight to saving comments if there is some initial indentation + pushComment(initialMargin); + state = JSDocState.SavingComments; + } let tok = token() as JsDocSyntaxKind; loop: while (true) { switch (tok) { @@ -6646,7 +6659,7 @@ namespace ts { const whitespace = scanner.getTokenText(); // if the whitespace crosses the margin, take only the whitespace that passes the margin if (margin !== undefined && indent + whitespace.length > margin) { - comments.push(whitespace.slice(margin - indent - 1)); + comments.push(whitespace.slice(margin - indent)); } indent += whitespace.length; } diff --git a/tests/baselines/reference/quickInfoForJSDocUnknownTag.baseline b/tests/baselines/reference/quickInfoForJSDocUnknownTag.baseline new file mode 100644 index 0000000000000..25b2f8fe9c0fa --- /dev/null +++ b/tests/baselines/reference/quickInfoForJSDocUnknownTag.baseline @@ -0,0 +1,283 @@ +[ + { + "marker": { + "fileName": "/tests/cases/fourslash/quickInfoForJSDocUnknownTag.ts", + "position": 64 + }, + "quickInfo": { + "kind": "function", + "kindModifiers": "", + "textSpan": { + "start": 62, + "length": 3 + }, + "displayParts": [ + { + "text": "function", + "kind": "keyword" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "foo", + "kind": "functionName" + }, + { + "text": "(", + "kind": "punctuation" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "string", + "kind": "keyword" + } + ], + "documentation": [], + "tags": [ + { + "name": "example", + "text": "if (true) {\n foo()\n}" + } + ] + } + }, + { + "marker": { + "fileName": "/tests/cases/fourslash/quickInfoForJSDocUnknownTag.ts", + "position": 134 + }, + "quickInfo": { + "kind": "function", + "kindModifiers": "", + "textSpan": { + "start": 132, + "length": 4 + }, + "displayParts": [ + { + "text": "function", + "kind": "keyword" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "foo2", + "kind": "functionName" + }, + { + "text": "(", + "kind": "punctuation" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "string", + "kind": "keyword" + } + ], + "documentation": [], + "tags": [ + { + "name": "example", + "text": "{\n foo()\n}" + } + ] + } + }, + { + "marker": { + "fileName": "/tests/cases/fourslash/quickInfoForJSDocUnknownTag.ts", + "position": 219 + }, + "quickInfo": { + "kind": "function", + "kindModifiers": "", + "textSpan": { + "start": 218, + "length": 3 + }, + "displayParts": [ + { + "text": "function", + "kind": "keyword" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "moo", + "kind": "functionName" + }, + { + "text": "(", + "kind": "punctuation" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "string", + "kind": "keyword" + } + ], + "documentation": [], + "tags": [ + { + "name": "example", + "text": " x y\n 12345\n b" + } + ] + } + }, + { + "marker": { + "fileName": "/tests/cases/fourslash/quickInfoForJSDocUnknownTag.ts", + "position": 313 + }, + "quickInfo": { + "kind": "function", + "kindModifiers": "", + "textSpan": { + "start": 312, + "length": 3 + }, + "displayParts": [ + { + "text": "function", + "kind": "keyword" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "boo", + "kind": "functionName" + }, + { + "text": "(", + "kind": "punctuation" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "string", + "kind": "keyword" + } + ], + "documentation": [], + "tags": [ + { + "name": "func" + }, + { + "name": "example", + "text": " x y\n 12345\n b" + } + ] + } + }, + { + "marker": { + "fileName": "/tests/cases/fourslash/quickInfoForJSDocUnknownTag.ts", + "position": 426 + }, + "quickInfo": { + "kind": "function", + "kindModifiers": "", + "textSpan": { + "start": 424, + "length": 3 + }, + "displayParts": [ + { + "text": "function", + "kind": "keyword" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "goo", + "kind": "functionName" + }, + { + "text": "(", + "kind": "punctuation" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "string", + "kind": "keyword" + } + ], + "documentation": [], + "tags": [ + { + "name": "func" + }, + { + "name": "example", + "text": "x y\n12345\n b" + } + ] + } + } +] \ No newline at end of file diff --git a/tests/cases/fourslash/quickInfoForJSDocUnknownTag.ts b/tests/cases/fourslash/quickInfoForJSDocUnknownTag.ts new file mode 100644 index 0000000000000..57597898373cd --- /dev/null +++ b/tests/cases/fourslash/quickInfoForJSDocUnknownTag.ts @@ -0,0 +1,49 @@ +/// + +/////** +//// * @example +//// * if (true) { +//// * foo() +//// * } +//// */ +////function fo/*1*/o() { +//// return '2'; +////} +/////** +//// @example +//// { +//// foo() +//// } +//// */ +////function fo/*2*/o2() { +//// return '2'; +////} +/////** +//// * @example +//// * x y +//// * 12345 +//// * b +//// */ +////function m/*3*/oo() { +//// return '2'; +////} +/////** +//// * @func +//// * @example +//// * x y +//// * 12345 +//// * b +//// */ +////function b/*4*/oo() { +//// return '2'; +////} +/////** +//// * @func +//// * @example x y +//// * 12345 +//// * b +//// */ +////function go/*5*/o() { +//// return '2'; +////} +verify.baselineQuickInfo();