Compare commits

...

5 commits

Author SHA1 Message Date
Nathan Shively-Sanders e2ca945569 Merge branch 'master' into triple-slash-jsdoc 2020-08-06 11:04:27 -07:00
Nathan Shively-Sanders a835635a18 Fix triple-slash reference classification 2020-08-06 10:53:08 -07:00
Nathan Shively-Sanders ae4b29c559 Skip triple-slash references 2020-08-06 09:11:46 -07:00
Nathan Shively-Sanders cf63bce7af Fix scanner offset and make it easier to read 2020-08-05 16:17:56 -07:00
Nathan Shively-Sanders 70f057339d Basic working version
With two testsa, one of which shows that line-joining and current-tag
maintenance isn't done yet.
2020-08-05 13:50:16 -07:00
8 changed files with 213 additions and 14 deletions

View file

@ -1026,7 +1026,10 @@ namespace ts {
let hasDeprecatedTag = false;
function addJSDocComment<T extends HasJSDoc>(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,43 @@ 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 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[] = [];
let tags: JSDocTag[];
let tagsPos: number;
let tagsEnd: number;
if (Array.isArray(startOrRanges)) {
if (!startOrRanges.length) return undefined;
const ranges = startOrRanges;
let currentTag: JSDocTag | undefined; // TODO: Probably can use tags
for (const { pos: start, end } of ranges) {
const length = end - start;
scanner.scanRange(start + 3, length - 3, () => {
while (nextTokenJSDoc() !== SyntaxKind.EndOfFileToken) {
if (token() === SyntaxKind.AtToken) {
addTag(currentTag);
currentTag = parseTag(0);
}
else {
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());
}
}
}
});
}
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 +7263,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 +7346,7 @@ namespace ts {
}
removeLeadingNewlines(comments);
removeTrailingWhitespace(comments);
return createJSDocComment();
return createJSDocComment(start, end);
});
function removeLeadingNewlines(comments: string[]) {
@ -7307,7 +7361,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);

View file

@ -1745,6 +1745,9 @@ namespace ts {
case CharacterCodes.slash:
// Single-line comment
if (text.charCodeAt(pos + 1) === CharacterCodes.slash) {
if (text.charCodeAt(pos + 2) === CharacterCodes.slash) {
tokenFlags |= TokenFlags.PrecedingJSDocComment;
}
pos += 2;
while (pos < end) {
@ -1770,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;

View file

@ -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) && !isRecognizedTripleSlashComment(text, comment.pos, comment.end));
}
export const fullTripleSlashReferencePathRegEx = /^(\/\/\/\s*<reference\s+path\s*=\s*)('|")(.+?)\2.*?\/>/;

View file

@ -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))

View file

@ -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 : ""
>"" : ""

View file

@ -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 = ""

View file

@ -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: "" });

View file

@ -0,0 +1,18 @@
/// <reference path="fourslash.ts" />
// @allowJs: true
// @checkJs: true
// @Filename: test.js
//// /// Adds one
//// /// @param {number} n - this is a long,
//// /// multiline comment
//// ///
//// /// @return {number}
//// function add1(/*2*/n) {
//// return n + 1
//// }
//// add1/*1*/(12)
verify.quickInfoAt('1', 'function add1(n: number): number', ' Adds one ')
verify.quickInfoAt('2', '(parameter) n: number', '- this is a long, multiline comment ')