From 70f057339d6a8e21a3d2146aff3f60c70bd6158e Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 5 Aug 2020 13:50:16 -0700 Subject: [PATCH 1/4] Basic working version With two testsa, one of which shows that line-joining and current-tag maintenance isn't done yet. --- src/compiler/parser.ts | 62 ++++++++++++++++--- src/compiler/scanner.ts | 3 + src/compiler/utilities.ts | 9 ++- .../conformance/jsdoc/tripleSlashJsdoc.ts | 34 ++++++++++ .../signatureHelpTripleSlashJSDoc.ts | 17 +++++ 5 files changed, 114 insertions(+), 11 deletions(-) create mode 100644 tests/cases/conformance/jsdoc/tripleSlashJsdoc.ts create mode 100644 tests/cases/fourslash/signatureHelpTripleSlashJSDoc.ts diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index c021b87d1c470..a4e19f3674ee7 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1026,7 +1026,10 @@ namespace ts { let hasDeprecatedTag = false; function addJSDocComment(node: T): T { Debug.assert(!node.jsDoc); // Should only be called once per node - const jsDoc = mapDefined(getJSDocCommentRanges(node, sourceText), comment => JSDocParser.parseJSDocComment(node, comment.pos, comment.end - comment.pos)); + const ranges = getJSDocCommentRanges(node, sourceText); + const jsDoc = ranges?.every(r => isTripleSlashComment(r, sourceText)) + ? JSDocParser.parseTripleSlashes(node, ranges) + : mapDefined(ranges, comment => JSDocParser.parseJSDocComment(node, comment.pos, comment.end - comment.pos)); if (jsDoc.length) node.jsDoc = jsDoc; if (hasDeprecatedTag) { hasDeprecatedTag = false; @@ -7158,6 +7161,26 @@ namespace ts { return jsDoc ? { jsDoc, diagnostics } : undefined; } + export function parseTripleSlashes(parent: HasJSDoc, comments: CommentRange[]) { + const saveToken = currentToken; + const saveParseDiagnosticsLength = parseDiagnostics.length; + const saveParseErrorBeforeNextFinishedNode = parseErrorBeforeNextFinishedNode; + + const comment = doInsideOfContext(NodeFlags.JSDoc, () => parseJSDocCommentWorker(comments, /*length*/ undefined)); + setParent(comment, parent); + + if (contextFlags & NodeFlags.JavaScriptFile) { + if (!jsDocDiagnostics) { + jsDocDiagnostics = []; + } + jsDocDiagnostics.push(...parseDiagnostics); + } + currentToken = saveToken; + parseDiagnostics.length = saveParseDiagnosticsLength; + parseErrorBeforeNextFinishedNode = saveParseErrorBeforeNextFinishedNode; + return comment ? [comment] : []; + } + export function parseJSDocComment(parent: HasJSDoc, start: number, length: number): JSDoc | undefined { const saveToken = currentToken; const saveParseDiagnosticsLength = parseDiagnostics.length; @@ -7191,8 +7214,33 @@ namespace ts { CallbackParameter = 1 << 2, } - function parseJSDocCommentWorker(start = 0, length: number | undefined): JSDoc | undefined { - const content = sourceText; + function parseJSDocCommentWorker(startOrRanges: number | CommentRange[] = 0, length: number | undefined): JSDoc | undefined { + const content = sourceText; // TODO: Why alias this? + const comments: string[] = []; + let tags: JSDocTag[]; + let tagsPos: number; + let tagsEnd: number; + if (Array.isArray(startOrRanges)) { + if (!startOrRanges.length) return undefined; + const ranges = startOrRanges; + for (const comment of ranges) { + const { pos: start, end } = comment; + const length = end - start; + scanner.scanRange(start + 3, length - 3, () => { + while (nextTokenJSDoc() !== SyntaxKind.EndOfFileToken) { + if (token() === SyntaxKind.AtToken) { + addTag(parseTag(0)); + } + else { + comments.push(scanner.getTokenText()); + } + } + }); + } + return createJSDocComment(ranges[0].pos, ranges[ranges.length - 1].end); + } + + const start = startOrRanges; const end = length === undefined ? content.length : start + length; length = end - start; @@ -7205,10 +7253,6 @@ namespace ts { return undefined; } - let tags: JSDocTag[]; - let tagsPos: number; - let tagsEnd: number; - const comments: string[] = []; // + 3 for leading /**, - 5 in total for /** */ return scanner.scanRange(start + 3, length - 5, () => { @@ -7292,7 +7336,7 @@ namespace ts { } removeLeadingNewlines(comments); removeTrailingWhitespace(comments); - return createJSDocComment(); + return createJSDocComment(start, end); }); function removeLeadingNewlines(comments: string[]) { @@ -7307,7 +7351,7 @@ namespace ts { } } - function createJSDocComment(): JSDoc { + function createJSDocComment(start: number, end: number): JSDoc { const comment = comments.length ? comments.join("") : undefined; const tagsArray = tags && createNodeArray(tags, tagsPos, tagsEnd); return finishNode(factory.createJSDocComment(comment, tagsArray), start, end); diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 77ec82ffef760..e198102f5eb18 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -1746,6 +1746,9 @@ namespace ts { // Single-line comment if (text.charCodeAt(pos + 1) === CharacterCodes.slash) { pos += 2; + if (text.charCodeAt(pos + 2) === CharacterCodes.slash) { + tokenFlags |= TokenFlags.PrecedingJSDocComment; + } while (pos < end) { if (isLineBreak(text.charCodeAt(pos))) { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index d7c00dbcc14c9..25c1af7fa0fb3 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1094,6 +1094,10 @@ namespace ts { return node.kind !== SyntaxKind.JsxText ? getLeadingCommentRanges(sourceFileOfNode.text, node.pos) : undefined; } + export function isTripleSlashComment(comment: CommentRange, text: string) { + return text.charCodeAt(comment.pos + 1) === CharacterCodes.slash && text.charCodeAt(comment.pos + 2) === CharacterCodes.slash; + } + export function getJSDocCommentRanges(node: Node, text: string) { const commentRanges = (node.kind === SyntaxKind.Parameter || node.kind === SyntaxKind.TypeParameter || @@ -1102,11 +1106,12 @@ namespace ts { node.kind === SyntaxKind.ParenthesizedExpression) ? concatenate(getTrailingCommentRanges(text, node.pos), getLeadingCommentRanges(text, node.pos)) : getLeadingCommentRanges(text, node.pos); - // True if the comment starts with '/**' but not if it is '/**/' + // True if the comment starts with '///' or '/**' but not if it is '/**/' return filter(commentRanges, comment => text.charCodeAt(comment.pos + 1) === CharacterCodes.asterisk && text.charCodeAt(comment.pos + 2) === CharacterCodes.asterisk && - text.charCodeAt(comment.pos + 3) !== CharacterCodes.slash); + text.charCodeAt(comment.pos + 3) !== CharacterCodes.slash + || isTripleSlashComment(comment, text)); } export const fullTripleSlashReferencePathRegEx = /^(\/\/\/\s*/; diff --git a/tests/cases/conformance/jsdoc/tripleSlashJsdoc.ts b/tests/cases/conformance/jsdoc/tripleSlashJsdoc.ts new file mode 100644 index 0000000000000..991afd8a483da --- /dev/null +++ b/tests/cases/conformance/jsdoc/tripleSlashJsdoc.ts @@ -0,0 +1,34 @@ +// @allowJs: true +// @checkJs: true +// @noImplicitAny: true +// @noEmit: true +// @filename: tripleSlash.js + +/// @type {number} - TODO this is still skipped for some reason +var x; + +/// Adds one +/// @param {number} n - this is a long, +/// multiline comment +/// +/// @return {number} +function add1(n) { + return n + 1 +} + +// Should be the same + +/** Adds one + * @param {number} n - this is a long, + * multiline comment + * + * @return {number} +*/ +function add2(n) { + return n + 1 +} + +/// I documented this const +const documented = "" + + diff --git a/tests/cases/fourslash/signatureHelpTripleSlashJSDoc.ts b/tests/cases/fourslash/signatureHelpTripleSlashJSDoc.ts new file mode 100644 index 0000000000000..7dc096d4782f1 --- /dev/null +++ b/tests/cases/fourslash/signatureHelpTripleSlashJSDoc.ts @@ -0,0 +1,17 @@ +/// + +// @allowJs: true +// @checkJs: true + +// @Filename: test.js +//// /// Adds one +//// /// @param {number} n - this is a long, +//// /// multiline comment +//// /// +//// /// @return {number} +//// function add1(n) { +//// return n + 1 +//// } +//// add1/*1*/ + +verify.quickInfoAt('1', 'function add1(n: number): number', ' Adds one') From cf63bce7afa3e92d4b77d64ea2c28cec7bc37370 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 5 Aug 2020 16:17:56 -0700 Subject: [PATCH 2/4] Fix scanner offset and make it easier to read --- src/compiler/scanner.ts | 6 +-- .../reference/tripleSlashJsdoc.symbols | 40 +++++++++++++++++ .../reference/tripleSlashJsdoc.types | 45 +++++++++++++++++++ .../cases/fourslash/commentsCommentParsing.ts | 2 +- 4 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 tests/baselines/reference/tripleSlashJsdoc.symbols create mode 100644 tests/baselines/reference/tripleSlashJsdoc.types diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index e198102f5eb18..2cd3ca63454c6 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -1745,10 +1745,10 @@ namespace ts { case CharacterCodes.slash: // Single-line comment if (text.charCodeAt(pos + 1) === CharacterCodes.slash) { - pos += 2; if (text.charCodeAt(pos + 2) === CharacterCodes.slash) { tokenFlags |= TokenFlags.PrecedingJSDocComment; } + pos += 2; while (pos < end) { if (isLineBreak(text.charCodeAt(pos))) { @@ -1773,10 +1773,10 @@ namespace ts { } // Multi-line comment if (text.charCodeAt(pos + 1) === CharacterCodes.asterisk) { - pos += 2; - if (text.charCodeAt(pos) === CharacterCodes.asterisk && text.charCodeAt(pos + 1) !== CharacterCodes.slash) { + if (text.charCodeAt(pos + 2) === CharacterCodes.asterisk && text.charCodeAt(pos + 3) !== CharacterCodes.slash) { tokenFlags |= TokenFlags.PrecedingJSDocComment; } + pos += 2; let commentClosed = false; let lastLineStart = tokenPos; diff --git a/tests/baselines/reference/tripleSlashJsdoc.symbols b/tests/baselines/reference/tripleSlashJsdoc.symbols new file mode 100644 index 0000000000000..8f5dca5a63b61 --- /dev/null +++ b/tests/baselines/reference/tripleSlashJsdoc.symbols @@ -0,0 +1,40 @@ +=== tests/cases/conformance/jsdoc/tripleSlash.js === +/// @type {number} - TODO this is still skipped for some reason +var x; +>x : Symbol(x, Decl(tripleSlash.js, 1, 3)) + +/// Adds one +/// @param {number} n - this is a long, +/// multiline comment +/// +/// @return {number} +function add1(n) { +>add1 : Symbol(add1, Decl(tripleSlash.js, 1, 6)) +>n : Symbol(n, Decl(tripleSlash.js, 8, 14)) + + return n + 1 +>n : Symbol(n, Decl(tripleSlash.js, 8, 14)) +} + +// Should be the same + +/** Adds one + * @param {number} n - this is a long, + * multiline comment + * + * @return {number} +*/ +function add2(n) { +>add2 : Symbol(add2, Decl(tripleSlash.js, 10, 1)) +>n : Symbol(n, Decl(tripleSlash.js, 20, 14)) + + return n + 1 +>n : Symbol(n, Decl(tripleSlash.js, 20, 14)) +} + +/// I documented this const +const documented = "" +>documented : Symbol(documented, Decl(tripleSlash.js, 25, 5)) + + + diff --git a/tests/baselines/reference/tripleSlashJsdoc.types b/tests/baselines/reference/tripleSlashJsdoc.types new file mode 100644 index 0000000000000..8a58c3170104b --- /dev/null +++ b/tests/baselines/reference/tripleSlashJsdoc.types @@ -0,0 +1,45 @@ +=== tests/cases/conformance/jsdoc/tripleSlash.js === +/// @type {number} - TODO this is still skipped for some reason +var x; +>x : number + +/// Adds one +/// @param {number} n - this is a long, +/// multiline comment +/// +/// @return {number} +function add1(n) { +>add1 : (n: number) => number +>n : number + + return n + 1 +>n + 1 : number +>n : number +>1 : 1 +} + +// Should be the same + +/** Adds one + * @param {number} n - this is a long, + * multiline comment + * + * @return {number} +*/ +function add2(n) { +>add2 : (n: number) => number +>n : number + + return n + 1 +>n + 1 : number +>n : number +>1 : 1 +} + +/// I documented this const +const documented = "" +>documented : "" +>"" : "" + + + diff --git a/tests/cases/fourslash/commentsCommentParsing.ts b/tests/cases/fourslash/commentsCommentParsing.ts index 4731194ba697c..755a1d1cc6332 100644 --- a/tests/cases/fourslash/commentsCommentParsing.ts +++ b/tests/cases/fourslash/commentsCommentParsing.ts @@ -202,7 +202,7 @@ ////class NoQuic/*50*/kInfoClass { ////} -verify.signatureHelp({ marker: "1", docComment: "" }); +verify.signatureHelp({ marker: "1", docComment: " This is simple /// comments" }); verify.quickInfoAt("1q", "function simple(): void"); verify.signatureHelp({ marker: "2", docComment: "" }); From ae4b29c559280e1447eb07928f637652eb7c13cb Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 6 Aug 2020 09:11:46 -0700 Subject: [PATCH 3/4] Skip triple-slash references --- src/compiler/parser.ts | 18 ++++++++++++++---- .../fourslash/signatureHelpTripleSlashJSDoc.ts | 7 ++++--- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index a4e19f3674ee7..dc4c0a5817007 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -7215,6 +7215,7 @@ namespace ts { } function parseJSDocCommentWorker(startOrRanges: number | CommentRange[] = 0, length: number | undefined): JSDoc | undefined { + // TODO: Probably should save a boolean isTripleSlash at the beginning and make all the nested functions change their behaviour. const content = sourceText; // TODO: Why alias this? const comments: string[] = []; let tags: JSDocTag[]; @@ -7223,16 +7224,25 @@ namespace ts { if (Array.isArray(startOrRanges)) { if (!startOrRanges.length) return undefined; const ranges = startOrRanges; - for (const comment of ranges) { - const { pos: start, end } = comment; + let currentTag: JSDocTag | undefined; // TODO: Probably can use tags + for (const { pos: start, end } of ranges) { + if (isRecognizedTripleSlashComment(content, start, end)) continue; const length = end - start; scanner.scanRange(start + 3, length - 3, () => { while (nextTokenJSDoc() !== SyntaxKind.EndOfFileToken) { if (token() === SyntaxKind.AtToken) { - addTag(parseTag(0)); + addTag(currentTag); + currentTag = parseTag(0); } else { - comments.push(scanner.getTokenText()); + if (currentTag) { + // this doesn't update currentTag.end, which will cause problems later + // I think that parseXTag will have to return unfinished tags or something. + (currentTag as any).comment = (currentTag.comment || "") + scanner.getTokenText() + } + else { + comments.push(scanner.getTokenText()); + } } } }); diff --git a/tests/cases/fourslash/signatureHelpTripleSlashJSDoc.ts b/tests/cases/fourslash/signatureHelpTripleSlashJSDoc.ts index 7dc096d4782f1..23feb8652dc76 100644 --- a/tests/cases/fourslash/signatureHelpTripleSlashJSDoc.ts +++ b/tests/cases/fourslash/signatureHelpTripleSlashJSDoc.ts @@ -9,9 +9,10 @@ //// /// multiline comment //// /// //// /// @return {number} -//// function add1(n) { +//// function add1(/*2*/n) { //// return n + 1 //// } -//// add1/*1*/ +//// add1/*1*/(12) -verify.quickInfoAt('1', 'function add1(n: number): number', ' Adds one') +verify.quickInfoAt('1', 'function add1(n: number): number', ' Adds one ') +verify.quickInfoAt('2', '(parameter) n: number', '- this is a long, multiline comment ') From a835635a1832cda94108d2318575390b242eeff2 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 6 Aug 2020 10:53:08 -0700 Subject: [PATCH 4/4] Fix triple-slash reference classification --- src/compiler/parser.ts | 2 +- src/compiler/utilities.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index dc4c0a5817007..475216f6e1c75 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -7215,6 +7215,7 @@ namespace ts { } function parseJSDocCommentWorker(startOrRanges: number | CommentRange[] = 0, length: number | undefined): JSDoc | undefined { + // const isTripleSlash = Array.isArray(startOrRanges); // TODO: Probably should save a boolean isTripleSlash at the beginning and make all the nested functions change their behaviour. const content = sourceText; // TODO: Why alias this? const comments: string[] = []; @@ -7226,7 +7227,6 @@ namespace ts { const ranges = startOrRanges; let currentTag: JSDocTag | undefined; // TODO: Probably can use tags for (const { pos: start, end } of ranges) { - if (isRecognizedTripleSlashComment(content, start, end)) continue; const length = end - start; scanner.scanRange(start + 3, length - 3, () => { while (nextTokenJSDoc() !== SyntaxKind.EndOfFileToken) { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 25c1af7fa0fb3..4a783ebd6e4e2 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1111,7 +1111,7 @@ namespace ts { text.charCodeAt(comment.pos + 1) === CharacterCodes.asterisk && text.charCodeAt(comment.pos + 2) === CharacterCodes.asterisk && text.charCodeAt(comment.pos + 3) !== CharacterCodes.slash - || isTripleSlashComment(comment, text)); + || isTripleSlashComment(comment, text) && !isRecognizedTripleSlashComment(text, comment.pos, comment.end)); } export const fullTripleSlashReferencePathRegEx = /^(\/\/\/\s*/;