From 0b6aeeca9d201d7611efdcb0595cf1da4b21add3 Mon Sep 17 00:00:00 2001 From: zhengbli Date: Tue, 11 Aug 2015 20:18:05 -0700 Subject: [PATCH 01/11] Add JsDoc intellisense for JavaScript files --- src/services/services.ts | 54 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/services/services.ts b/src/services/services.ts index 07ba30f4e1..51ac758a28 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3653,6 +3653,10 @@ namespace ts { let completionData = getCompletionData(fileName, position); if (!completionData) { + let entries = getJsDocCompletionEntries(fileName, position); + if (entries) { + return { isMemberCompletion: false, isNewIdentifierLocation: false, entries }; + } return undefined; } @@ -3705,6 +3709,56 @@ namespace ts { return entries; } + function getJsDocCompletionEntries(fileName: string, position: number): CompletionEntry[] { + let sourceFile = getValidSourceFile(fileName); + let hasJsDocComment = ts.hasDocComment(sourceFile, position); + if (!hasJsDocComment) { + return undefined; + } + + // If the current position is right after an At sign + if (sourceFile.text.charCodeAt(position - 1) === CharacterCodes.at) { + return getAllJsDocCompletionEntries(); + } + + // Or if the current position is in a tag name + let jsDocCommentNode: JSDocComment; + let node = ts.getTokenAtPosition(sourceFile, position); + while (true) { + if (node === sourceFile || + node.kind === SyntaxKind.VariableStatement || + node.kind === SyntaxKind.FunctionDeclaration || + node.kind === SyntaxKind.Parameter || + node.jsDocComment) { + jsDocCommentNode = node.jsDocComment; + break; + } + node = node.parent; + } + if (jsDocCommentNode) { + for (let tag of jsDocCommentNode.tags) { + if (position >= tag.atToken.pos && position <= tag.tagName.end) { + return getAllJsDocCompletionEntries(); + } + } + return undefined; + } + } + + function getAllJsDocCompletionEntries(): CompletionEntry[] { + let tagNames = ["augments", "author", "argument", "borrows", "class", "constant", "constructor", "constructs", "default", "deprecated", "description", "event", "example", "extends", "field", "fileOverview", "function", "ignore", "inner", "lends", "link", "memberOf", "name", "namespace", "param", "private", "property", "public", "requires", "returns", "see", "since", "static", "throws", "type", "version"]; + let entries: CompletionEntry[] = []; + for (let tagName of tagNames) { + entries.push({ + name: tagName, + kind: ScriptElementKind.keyword, + kindModifiers: "", + sortText: "0", + }); + } + return entries; + } + function createCompletionEntry(symbol: Symbol, location: Node): CompletionEntry { // Try to get a valid display name for this symbol, if we could not find one, then ignore it. // We would like to only show things that can be added after a dot, so for instance numeric properties can From 5e9186b9ed060b104e2414d6d3c1c14a9ea6b144 Mon Sep 17 00:00:00 2001 From: zhengbli Date: Tue, 11 Aug 2015 21:40:37 -0700 Subject: [PATCH 02/11] Enable intellisense in jsDoc tag @type brackets --- src/services/services.ts | 49 +++++++++++++++++---------------------- src/services/utilities.ts | 27 +++++++++++++++++++++ 2 files changed, 48 insertions(+), 28 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 51ac758a28..d541cf1e4b 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2928,8 +2928,22 @@ namespace ts { log("getCompletionData: Is inside comment: " + (new Date().getTime() - start)); if (insideComment) { - log("Returning an empty list because completion was inside a comment."); - return undefined; + // Completion should work inside certain JsDoc tags. For example: + // /** @type {number | string} */ + // Completion should work in the brackets + let insideJsDocTagExpression = false; + let tag = getJsDocTagAtPosition(sourceFile, position); + if (tag) { + switch (tag.kind) { + case SyntaxKind.JSDocTypeTag: + let typeTag = tag; + insideJsDocTagExpression = position > typeTag.typeExpression.pos && position < typeTag.typeExpression.end; + } + } + if (!insideJsDocTagExpression) { + log("Returning an empty list because completion was inside a comment."); + return undefined; + } } start = new Date().getTime(); @@ -3716,33 +3730,12 @@ namespace ts { return undefined; } - // If the current position is right after an At sign - if (sourceFile.text.charCodeAt(position - 1) === CharacterCodes.at) { - return getAllJsDocCompletionEntries(); - } - + // The current position is right after an At sign // Or if the current position is in a tag name - let jsDocCommentNode: JSDocComment; - let node = ts.getTokenAtPosition(sourceFile, position); - while (true) { - if (node === sourceFile || - node.kind === SyntaxKind.VariableStatement || - node.kind === SyntaxKind.FunctionDeclaration || - node.kind === SyntaxKind.Parameter || - node.jsDocComment) { - jsDocCommentNode = node.jsDocComment; - break; - } - node = node.parent; - } - if (jsDocCommentNode) { - for (let tag of jsDocCommentNode.tags) { - if (position >= tag.atToken.pos && position <= tag.tagName.end) { - return getAllJsDocCompletionEntries(); - } - } - return undefined; - } + if (sourceFile.text.charCodeAt(position - 1) === CharacterCodes.at || + getJsDocTagAtPosition(sourceFile, position)) { + return getAllJsDocCompletionEntries(); + } } function getAllJsDocCompletionEntries(): CompletionEntry[] { diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 04bfd04057..9c475694a1 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -469,6 +469,33 @@ namespace ts { } } + /** + * Get the corresponding JSDocTag node if the position is in a jsDoc commet + */ + export function getJsDocTagAtPosition(sourceFile: SourceFile, position: number): JSDocTag { + let node = ts.getTokenAtPosition(sourceFile, position); + let jsDocComment: JSDocComment; + while (true) { + if (node === sourceFile || + node.kind === SyntaxKind.VariableStatement || + node.kind === SyntaxKind.FunctionDeclaration || + node.kind === SyntaxKind.Parameter || + node.jsDocComment) { + jsDocComment = node.jsDocComment; + break; + } + node = node.parent; + } + if (jsDocComment) { + for (let tag of jsDocComment.tags) { + if (position >= tag.pos && position <= tag.end) { + return tag; + } + } + } + return undefined; + } + function nodeHasTokens(n: Node): boolean { // If we have a token or node that has a non-zero width, it must have tokens. // Note, that getWidth() does not take trivia into account. From 03ea38f31fe49e31218ef17d77173b492ddf4d79 Mon Sep 17 00:00:00 2001 From: zhengbli Date: Tue, 11 Aug 2015 23:20:03 -0700 Subject: [PATCH 03/11] Fix bugs and add support for @param expressions --- src/services/services.ts | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index d541cf1e4b..4b618e71c4 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2937,7 +2937,16 @@ namespace ts { switch (tag.kind) { case SyntaxKind.JSDocTypeTag: let typeTag = tag; - insideJsDocTagExpression = position > typeTag.typeExpression.pos && position < typeTag.typeExpression.end; + if (typeTag.typeExpression) { + insideJsDocTagExpression = position > typeTag.typeExpression.pos && position < typeTag.typeExpression.end; + }; + break; + case SyntaxKind.JSDocParameterTag: + let paramTag = tag; + if (paramTag.typeExpression) { + insideJsDocTagExpression = position > paramTag.typeExpression.pos && position < paramTag.typeExpression.end; + }; + break; } } if (!insideJsDocTagExpression) { @@ -3731,11 +3740,17 @@ namespace ts { } // The current position is right after an At sign - // Or if the current position is in a tag name - if (sourceFile.text.charCodeAt(position - 1) === CharacterCodes.at || - getJsDocTagAtPosition(sourceFile, position)) { + if (sourceFile.text.charCodeAt(position - 1) === CharacterCodes.at) { return getAllJsDocCompletionEntries(); - } + } + + // Or if the current position is in a tag name + let tag = getJsDocTagAtPosition(sourceFile, position); + if (tag) { + if (position >= tag.atToken.end && position <= tag.tagName.end) { + return getAllJsDocCompletionEntries(); + } + } } function getAllJsDocCompletionEntries(): CompletionEntry[] { From ffaf8e08d9aa9623384e4eeff71a20203032a70a Mon Sep 17 00:00:00 2001 From: zhengbli Date: Tue, 11 Aug 2015 23:54:25 -0700 Subject: [PATCH 04/11] Add test --- tests/cases/fourslash/completionInJsDoc.ts | 39 ++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 tests/cases/fourslash/completionInJsDoc.ts diff --git a/tests/cases/fourslash/completionInJsDoc.ts b/tests/cases/fourslash/completionInJsDoc.ts new file mode 100644 index 0000000000..45631c79ab --- /dev/null +++ b/tests/cases/fourslash/completionInJsDoc.ts @@ -0,0 +1,39 @@ +/// + +// @allowNonTsExtensions: true +// @Filename: Foo.js +/////** @/*1*/ */ +////var v1; +//// +/////** @p/*2*/ */ +////var v2; +//// +/////** @param /*3*/ */ +////var v3; +//// +/////** @param { n/*4*/ } bar */ +////var v4; +//// +/////** @type { n/*5*/ } */ +////var v5; + +goTo.marker('1'); +verify.completionListContains("constructor"); +verify.completionListContains("param"); +verify.completionListContains("type"); + +goTo.marker('2'); +verify.completionListContains("constructor"); +verify.completionListContains("param"); +verify.completionListContains("type"); + +goTo.marker('3'); +verify.completionListIsEmpty(); + +goTo.marker('4'); +verify.completionListContains('number'); + +goTo.marker('5'); +verify.completionListContains('number'); + + From 9dac516b26a0ded9926920f55eb2920f65871da4 Mon Sep 17 00:00:00 2001 From: zhengbli Date: Thu, 13 Aug 2015 11:06:36 -0700 Subject: [PATCH 05/11] CR feedback --- src/services/services.ts | 70 ++++++++++++++++++++++++++++----------- src/services/utilities.ts | 4 +-- 2 files changed, 53 insertions(+), 21 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 4b618e71c4..dd2f0025c5 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -131,6 +131,45 @@ namespace ts { let scanner: Scanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ true); let emptyArray: any[] = []; + + const JsDocTagNames: string[] = [ + "augments", + "author", + "argument", + "borrows", + "class", + "constant", + "constructor", + "constructs", + "default", + "deprecated", + "description", + "event", + "example", + "extends", + "field", + "fileOverview", + "function", + "ignore", + "inner", + "lends", + "link", + "memberOf", + "name", + "namespace", + "param", + "private", + "property", + "public", + "requires", + "returns", + "see", + "since", + "static", + "throws", + "type", + "version" + ]; function createNode(kind: SyntaxKind, pos: number, end: number, flags: NodeFlags, parent?: Node): NodeObject { let node = new (getNodeConstructor(kind))(); @@ -2936,21 +2975,16 @@ namespace ts { if (tag) { switch (tag.kind) { case SyntaxKind.JSDocTypeTag: - let typeTag = tag; - if (typeTag.typeExpression) { - insideJsDocTagExpression = position > typeTag.typeExpression.pos && position < typeTag.typeExpression.end; - }; - break; case SyntaxKind.JSDocParameterTag: - let paramTag = tag; - if (paramTag.typeExpression) { - insideJsDocTagExpression = position > paramTag.typeExpression.pos && position < paramTag.typeExpression.end; + let tagWithExpression = tag; + if (tagWithExpression.typeExpression) { + insideJsDocTagExpression = tagWithExpression.typeExpression.pos < position && position < tagWithExpression.typeExpression.end; }; break; } } if (!insideJsDocTagExpression) { - log("Returning an empty list because completion was inside a comment."); + log("Returning an empty list because completion was inside a regular comment or plain text part of a JsDoc comment."); return undefined; } } @@ -3739,32 +3773,30 @@ namespace ts { return undefined; } - // The current position is right after an At sign + // Return a list of JsDoc tag names for completion if the current position is right after an '@' sign if (sourceFile.text.charCodeAt(position - 1) === CharacterCodes.at) { return getAllJsDocCompletionEntries(); } - // Or if the current position is in a tag name + // Also return tag names if the current position is inside a tag name, e.g. (at the location of '^') + // /** @par^ */ let tag = getJsDocTagAtPosition(sourceFile, position); if (tag) { - if (position >= tag.atToken.end && position <= tag.tagName.end) { + if (tag.atToken.end <= position && position <= tag.tagName.end) { return getAllJsDocCompletionEntries(); } } } function getAllJsDocCompletionEntries(): CompletionEntry[] { - let tagNames = ["augments", "author", "argument", "borrows", "class", "constant", "constructor", "constructs", "default", "deprecated", "description", "event", "example", "extends", "field", "fileOverview", "function", "ignore", "inner", "lends", "link", "memberOf", "name", "namespace", "param", "private", "property", "public", "requires", "returns", "see", "since", "static", "throws", "type", "version"]; - let entries: CompletionEntry[] = []; - for (let tagName of tagNames) { - entries.push({ + return ts.map(JsDocTagNames, tagName => { + return { name: tagName, kind: ScriptElementKind.keyword, kindModifiers: "", sortText: "0", - }); - } - return entries; + } + }); } function createCompletionEntry(symbol: Symbol, location: Node): CompletionEntry { diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 9c475694a1..e48eefd92a 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -486,9 +486,10 @@ namespace ts { } node = node.parent; } + if (jsDocComment) { for (let tag of jsDocComment.tags) { - if (position >= tag.pos && position <= tag.end) { + if (tag.pos <= position && position <= tag.end) { return tag; } } @@ -667,7 +668,6 @@ namespace ts { else if (flags & SymbolFlags.TypeAlias) { return SymbolDisplayPartKind.aliasName; } else if (flags & SymbolFlags.Alias) { return SymbolDisplayPartKind.aliasName; } - return SymbolDisplayPartKind.text; } } From 2a3867a74243d50c1253b79a0ec6af8c92cf73d7 Mon Sep 17 00:00:00 2001 From: zhengbli Date: Mon, 17 Aug 2015 15:35:27 -0700 Subject: [PATCH 06/11] CR feedback. Add more tests. --- src/services/services.ts | 63 +++++++++++----------- src/services/utilities.ts | 26 +++++---- tests/cases/fourslash/completionInJsDoc.ts | 11 ++++ 3 files changed, 57 insertions(+), 43 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index dd2f0025c5..4cb9e6b994 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -132,7 +132,7 @@ namespace ts { let emptyArray: any[] = []; - const JsDocTagNames: string[] = [ + const jsDocTagNames: string[] = [ "augments", "author", "argument", @@ -170,6 +170,7 @@ namespace ts { "type", "version" ]; + let jsDocCompletionEntries: CompletionEntry[]; function createNode(kind: SyntaxKind, pos: number, end: number, flags: NodeFlags, parent?: Node): NodeObject { let node = new (getNodeConstructor(kind))(); @@ -2957,6 +2958,8 @@ namespace ts { let sourceFile = getValidSourceFile(fileName); let isJavaScriptFile = isJavaScript(fileName); + let isJsDocTagName = false; + let start = new Date().getTime(); let currentToken = getTokenAtPosition(sourceFile, position); log("getCompletionData: Get current token: " + (new Date().getTime() - start)); @@ -2967,12 +2970,22 @@ namespace ts { log("getCompletionData: Is inside comment: " + (new Date().getTime() - start)); if (insideComment) { + // The current position is next to the '@' sign, when no tag name being provided yet. + // Provide a full list of tag names + if (hasDocComment(sourceFile, position) && sourceFile.text.charCodeAt(position - 1) === CharacterCodes.at) { + isJsDocTagName = true; + } + // Completion should work inside certain JsDoc tags. For example: // /** @type {number | string} */ // Completion should work in the brackets let insideJsDocTagExpression = false; let tag = getJsDocTagAtPosition(sourceFile, position); if (tag) { + if (tag.tagName.pos <= position && position <= tag.tagName.end) { + isJsDocTagName = true; + } + switch (tag.kind) { case SyntaxKind.JSDocTypeTag: case SyntaxKind.JSDocParameterTag: @@ -2983,7 +2996,14 @@ namespace ts { break; } } + + if (isJsDocTagName) { + return { symbols: undefined, isMemberCompletion: false, isNewIdentifierLocation: false, location: undefined, isRightOfDot: false, isJsDocTagName }; + } + if (!insideJsDocTagExpression) { + // Proceed if the current position is in jsDoc tag expression; otherwise it is a normal + // comment or the plain text part of a jsDoc comment, so no completion should be available log("Returning an empty list because completion was inside a regular comment or plain text part of a JsDoc comment."); return undefined; } @@ -3072,7 +3092,7 @@ namespace ts { log("getCompletionData: Semantic work: " + (new Date().getTime() - semanticStart)); - return { symbols, isMemberCompletion, isNewIdentifierLocation, location, isRightOfDot: (isRightOfDot || isRightOfOpenTag) }; + return { symbols, isMemberCompletion, isNewIdentifierLocation, location, isRightOfDot: (isRightOfDot || isRightOfOpenTag), isJsDocTagName }; function getTypeScriptMemberSymbols(): void { // Right of dot member completion list @@ -3710,16 +3730,17 @@ namespace ts { let completionData = getCompletionData(fileName, position); if (!completionData) { - let entries = getJsDocCompletionEntries(fileName, position); - if (entries) { - return { isMemberCompletion: false, isNewIdentifierLocation: false, entries }; - } return undefined; } - let { symbols, isMemberCompletion, isNewIdentifierLocation, location, isRightOfDot } = completionData; + let { symbols, isMemberCompletion, isNewIdentifierLocation, location, isRightOfDot, isJsDocTagName } = completionData; let entries: CompletionEntry[]; + if (isJsDocTagName) { + // If the current position is a jsDoc tag name, only tag names should be provided for completion + return { isMemberCompletion: false, isNewIdentifierLocation: false, entries: getAllJsDocCompletionEntries() }; + } + if (isRightOfDot && isJavaScript(fileName)) { entries = getCompletionEntriesFromSymbols(symbols); addRange(entries, getJavaScriptCompletionEntries()); @@ -3733,7 +3754,7 @@ namespace ts { } // Add keywords if this is not a member completion list - if (!isMemberCompletion) { + if (!isMemberCompletion && !isJsDocTagName) { addRange(entries, keywordCompletions); } @@ -3766,37 +3787,15 @@ namespace ts { return entries; } - function getJsDocCompletionEntries(fileName: string, position: number): CompletionEntry[] { - let sourceFile = getValidSourceFile(fileName); - let hasJsDocComment = ts.hasDocComment(sourceFile, position); - if (!hasJsDocComment) { - return undefined; - } - - // Return a list of JsDoc tag names for completion if the current position is right after an '@' sign - if (sourceFile.text.charCodeAt(position - 1) === CharacterCodes.at) { - return getAllJsDocCompletionEntries(); - } - - // Also return tag names if the current position is inside a tag name, e.g. (at the location of '^') - // /** @par^ */ - let tag = getJsDocTagAtPosition(sourceFile, position); - if (tag) { - if (tag.atToken.end <= position && position <= tag.tagName.end) { - return getAllJsDocCompletionEntries(); - } - } - } - function getAllJsDocCompletionEntries(): CompletionEntry[] { - return ts.map(JsDocTagNames, tagName => { + return jsDocCompletionEntries || (jsDocCompletionEntries = ts.map(jsDocTagNames, tagName => { return { name: tagName, kind: ScriptElementKind.keyword, kindModifiers: "", sortText: "0", } - }); + })); } function createCompletionEntry(symbol: Symbol, location: Node): CompletionEntry { diff --git a/src/services/utilities.ts b/src/services/utilities.ts index e48eefd92a..67d82f5e42 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -475,18 +475,21 @@ namespace ts { export function getJsDocTagAtPosition(sourceFile: SourceFile, position: number): JSDocTag { let node = ts.getTokenAtPosition(sourceFile, position); let jsDocComment: JSDocComment; - while (true) { - if (node === sourceFile || - node.kind === SyntaxKind.VariableStatement || - node.kind === SyntaxKind.FunctionDeclaration || - node.kind === SyntaxKind.Parameter || - node.jsDocComment) { - jsDocComment = node.jsDocComment; - break; - } - node = node.parent; + if (node.jsDocComment) { + jsDocComment = node.jsDocComment; } - + else { + while (node) { + if (node.kind === SyntaxKind.VariableStatement || + node.kind === SyntaxKind.FunctionDeclaration || + node.kind === SyntaxKind.Parameter) { + jsDocComment = node.jsDocComment; + break; + } + node = node.parent; + } + } + if (jsDocComment) { for (let tag of jsDocComment.tags) { if (tag.pos <= position && position <= tag.end) { @@ -494,6 +497,7 @@ namespace ts { } } } + return undefined; } diff --git a/tests/cases/fourslash/completionInJsDoc.ts b/tests/cases/fourslash/completionInJsDoc.ts index 45631c79ab..493e63ec1b 100644 --- a/tests/cases/fourslash/completionInJsDoc.ts +++ b/tests/cases/fourslash/completionInJsDoc.ts @@ -16,6 +16,12 @@ //// /////** @type { n/*5*/ } */ ////var v5; +//// +////// @/*6*/ +////var v6; +//// +////// @pa/*7*/ +////var v7; goTo.marker('1'); verify.completionListContains("constructor"); @@ -36,4 +42,9 @@ verify.completionListContains('number'); goTo.marker('5'); verify.completionListContains('number'); +goTo.marker('6'); +verify.completionListIsEmpty(); + +goTo.marker('7'); +verify.completionListIsEmpty(); From b16536b60d00e37fadeac6027f1c0819edcc284a Mon Sep 17 00:00:00 2001 From: zhengbli Date: Wed, 19 Aug 2015 16:02:36 -0700 Subject: [PATCH 07/11] CR feedback --- src/services/services.ts | 4 ++-- src/services/utilities.ts | 27 ++++++++++++++------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 4cb9e6b994..954183907b 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -132,7 +132,7 @@ namespace ts { let emptyArray: any[] = []; - const jsDocTagNames: string[] = [ + const jsDocTagNames = [ "augments", "author", "argument", @@ -2992,7 +2992,7 @@ namespace ts { let tagWithExpression = tag; if (tagWithExpression.typeExpression) { insideJsDocTagExpression = tagWithExpression.typeExpression.pos < position && position < tagWithExpression.typeExpression.end; - }; + } break; } } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 67d82f5e42..9d8865be19 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -470,26 +470,27 @@ namespace ts { } /** - * Get the corresponding JSDocTag node if the position is in a jsDoc commet + * Get the corresponding JSDocTag node if the position is in a jsDoc comment */ export function getJsDocTagAtPosition(sourceFile: SourceFile, position: number): JSDocTag { let node = ts.getTokenAtPosition(sourceFile, position); - let jsDocComment: JSDocComment; - if (node.jsDocComment) { - jsDocComment = node.jsDocComment; - } - else { - while (node) { - if (node.kind === SyntaxKind.VariableStatement || - node.kind === SyntaxKind.FunctionDeclaration || - node.kind === SyntaxKind.Parameter) { - jsDocComment = node.jsDocComment; + if (isToken(node)) { + switch (node.kind) { + case SyntaxKind.VarKeyword: + case SyntaxKind.LetKeyword: + case SyntaxKind.ConstKeyword: + // if the current token is var, let or const, skip the VariableDeclarationList + node = node.parent.parent; break; - } + default: + node = node.parent; + break; + } + if (!node.jsDocComment) { node = node.parent; } } - + let jsDocComment = node.jsDocComment; if (jsDocComment) { for (let tag of jsDocComment.tags) { if (tag.pos <= position && position <= tag.end) { From d1253d56ac284dcdded2bab09568610f24c0ed05 Mon Sep 17 00:00:00 2001 From: zhengbli Date: Wed, 19 Aug 2015 20:47:02 -0700 Subject: [PATCH 08/11] Added null checks --- src/services/utilities.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 9d8865be19..d5ad93260c 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -480,21 +480,21 @@ namespace ts { case SyntaxKind.LetKeyword: case SyntaxKind.ConstKeyword: // if the current token is var, let or const, skip the VariableDeclarationList - node = node.parent.parent; + node = node.parent === undefined ? undefined : node.parent.parent; break; default: node = node.parent; break; } - if (!node.jsDocComment) { - node = node.parent; - } } - let jsDocComment = node.jsDocComment; - if (jsDocComment) { - for (let tag of jsDocComment.tags) { - if (tag.pos <= position && position <= tag.end) { - return tag; + + if (node) { + let jsDocComment = node.jsDocComment; + if (jsDocComment) { + for (let tag of jsDocComment.tags) { + if (tag.pos <= position && position <= tag.end) { + return tag; + } } } } From 582b0aa31e2e17ca9634a87e00ab1c7359d5ae99 Mon Sep 17 00:00:00 2001 From: zhengbli Date: Fri, 21 Aug 2015 13:07:41 -0700 Subject: [PATCH 09/11] parse jsdoc param tag even without a param name --- src/compiler/parser.ts | 1 - src/services/services.ts | 3 ++- tests/cases/fourslash/completionInJsDoc.ts | 12 ++++++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index def08e8175..25b3827799 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -5831,7 +5831,6 @@ namespace ts { if (!name) { parseErrorAtPosition(pos, 0, Diagnostics.Identifier_expected); - return undefined; } let preName: Identifier, postName: Identifier; diff --git a/src/services/services.ts b/src/services/services.ts index 954183907b..26f6492809 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2989,7 +2989,8 @@ namespace ts { switch (tag.kind) { case SyntaxKind.JSDocTypeTag: case SyntaxKind.JSDocParameterTag: - let tagWithExpression = tag; + case SyntaxKind.JSDocReturnTag: + let tagWithExpression = tag; if (tagWithExpression.typeExpression) { insideJsDocTagExpression = tagWithExpression.typeExpression.pos < position && position < tagWithExpression.typeExpression.end; } diff --git a/tests/cases/fourslash/completionInJsDoc.ts b/tests/cases/fourslash/completionInJsDoc.ts index 493e63ec1b..5424c8e79f 100644 --- a/tests/cases/fourslash/completionInJsDoc.ts +++ b/tests/cases/fourslash/completionInJsDoc.ts @@ -22,6 +22,12 @@ //// ////// @pa/*7*/ ////var v7; +//// +/////** @param { n/*8*/ } */ +////var v8; +//// +/////** @return { n/*9*/ } */ +////var v9; goTo.marker('1'); verify.completionListContains("constructor"); @@ -48,3 +54,9 @@ verify.completionListIsEmpty(); goTo.marker('7'); verify.completionListIsEmpty(); +goTo.marker('8'); +verify.completionListContains('number'); + +goTo.marker('9'); +verify.completionListContains('number'); + From 69bc569b2f60d03fab487a86423bbc0959d6fb62 Mon Sep 17 00:00:00 2001 From: zhengbli Date: Fri, 21 Aug 2015 14:33:59 -0700 Subject: [PATCH 10/11] Fix issue when the newly added files in tsconfig is not opened --- src/server/editorServices.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index b9f76db4c9..dd29d3cf2c 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1116,13 +1116,15 @@ namespace ts.server { else { // if the root file was opened by client, it would belong to either // openFileRoots or openFileReferenced. - if (this.openFileRoots.indexOf(info) >= 0) { - this.openFileRoots = copyListRemovingItem(info, this.openFileRoots); + if (info.isOpen) { + if (this.openFileRoots.indexOf(info) >= 0) { + this.openFileRoots = copyListRemovingItem(info, this.openFileRoots); + } + if (this.openFilesReferenced.indexOf(info) >= 0) { + this.openFilesReferenced = copyListRemovingItem(info, this.openFilesReferenced); + } + this.openFileRootsConfigured.push(info); } - if (this.openFilesReferenced.indexOf(info) >= 0) { - this.openFilesReferenced = copyListRemovingItem(info, this.openFilesReferenced); - } - this.openFileRootsConfigured.push(info); } project.addRoot(info); } From fb403dad91053a8bbabbd66e439048bf4dc58c9c Mon Sep 17 00:00:00 2001 From: zhengbli Date: Fri, 21 Aug 2015 16:01:35 -0700 Subject: [PATCH 11/11] Move getElementsByClassName from HTMLElement to Element --- src/lib/dom.generated.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/dom.generated.d.ts b/src/lib/dom.generated.d.ts index a12154bb04..19907d2edf 100644 --- a/src/lib/dom.generated.d.ts +++ b/src/lib/dom.generated.d.ts @@ -2916,6 +2916,7 @@ interface Element extends Node, GlobalEventHandlers, ElementTraversal, NodeSelec webkitMatchesSelector(selectors: string): boolean; webkitRequestFullScreen(): void; webkitRequestFullscreen(): void; + getElementsByClassName(classNames: string): NodeListOf; addEventListener(type: "MSGestureChange", listener: (ev: MSGestureEvent) => any, useCapture?: boolean): void; addEventListener(type: "MSGestureDoubleTap", listener: (ev: MSGestureEvent) => any, useCapture?: boolean): void; addEventListener(type: "MSGestureEnd", listener: (ev: MSGestureEvent) => any, useCapture?: boolean): void; @@ -3903,7 +3904,6 @@ interface HTMLElement extends Element { contains(child: HTMLElement): boolean; dragDrop(): boolean; focus(): void; - getElementsByClassName(classNames: string): NodeListOf; insertAdjacentElement(position: string, insertedElement: Element): Element; insertAdjacentHTML(where: string, html: string): void; insertAdjacentText(where: string, text: string): void;