diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index b3c9c118e97ef..46a18ff079081 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -662,7 +662,16 @@ module FourSlash { Harness.IO.log(errorMsg); this.raiseError("Completion list is not empty at Caret"); + } + } + + public verifyCompletionListAllowsNewIdentifier(negative: boolean) { + var completions = this.getCompletionListAtCaret(); + if ((completions && !completions.isNewIdentifierLocation) && !negative) { + this.raiseError("Expected builder completion entry"); + } else if ((completions && completions.isNewIdentifierLocation) && negative) { + this.raiseError("Un-expected builder completion entry"); } } diff --git a/src/services/services.ts b/src/services/services.ts index 00084f510813c..677af16bdc4de 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -407,7 +407,7 @@ module ts { return pos + name.length < end && sourceFile.text.substr(pos, name.length) === name && (isWhiteSpace(sourceFile.text.charCodeAt(pos + name.length)) || - isLineBreak(sourceFile.text.charCodeAt(pos + name.length))); + isLineBreak(sourceFile.text.charCodeAt(pos + name.length))); } function isParamTag(pos: number, end: number, sourceFile: SourceFile) { @@ -812,7 +812,7 @@ module ts { if ((node).name) { namedDeclarations.push(node); } - // fall through + // fall through case SyntaxKind.Constructor: case SyntaxKind.VariableStatement: case SyntaxKind.VariableDeclarationList: @@ -833,7 +833,7 @@ module ts { if (!(node.flags & NodeFlags.AccessibilityModifier)) { break; } - // fall through + // fall through case SyntaxKind.VariableDeclaration: case SyntaxKind.BindingElement: if (isBindingPattern((node).name)) { @@ -900,7 +900,7 @@ module ts { getRenameInfo(fileName: string, position: number): RenameInfo; findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[]; - + getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[]; getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[]; getOccurrencesAtPosition(fileName: string, position: number): ReferenceEntry[]; @@ -925,7 +925,7 @@ module ts { dispose(): void; } - + export interface ClassifiedSpan { textSpan: TextSpan; classificationType: string; // ClassificationTypeNames @@ -1036,7 +1036,7 @@ module ts { text: string; kind: string; } - + export interface QuickInfo { kind: string; kindModifiers: string; @@ -1091,6 +1091,7 @@ module ts { export interface CompletionInfo { isMemberCompletion: boolean; + isNewIdentifierLocation: boolean; // true when the current location also allows for a new identifier entries: CompletionEntry[]; } @@ -1616,7 +1617,7 @@ module ts { function setSourceFileFields(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string) { sourceFile.version = version; sourceFile.scriptSnapshot = scriptSnapshot; - } + } export function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean): SourceFile { var sourceFile = createSourceFile(fileName, scriptSnapshot.getText(0, scriptSnapshot.getLength()), scriptTarget, setNodeParents); @@ -1939,7 +1940,7 @@ module ts { // The position has to be: 1. in the leading trivia (before token.getStart()), and 2. within a comment return position <= token.getStart(sourceFile) && (isInsideCommentRange(getTrailingCommentRanges(sourceFile.text, token.getFullStart())) || - isInsideCommentRange(getLeadingCommentRanges(sourceFile.text, token.getFullStart()))); + isInsideCommentRange(getLeadingCommentRanges(sourceFile.text, token.getFullStart()))); function isInsideCommentRange(comments: CommentRange[]): boolean { return forEach(comments, comment => { @@ -1981,7 +1982,7 @@ module ts { } // A cache of completion entries for keywords, these do not change between sessions - var keywordCompletions:CompletionEntry[] = []; + var keywordCompletions: CompletionEntry[] = []; for (var i = SyntaxKind.FirstKeyword; i <= SyntaxKind.LastKeyword; i++) { keywordCompletions.push({ name: tokenToString(i), @@ -2332,6 +2333,7 @@ module ts { // Right of dot member completion list var symbols: Symbol[] = []; var isMemberCompletion = true; + var isNewIdentifierLocation = false; if (node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName || node.kind === SyntaxKind.PropertyAccessExpression) { var symbol = typeInfoResolver.getSymbolAtLocation(node); @@ -2368,6 +2370,7 @@ module ts { if (containingObjectLiteral) { // Object literal expression, look up possible property names from contextual type isMemberCompletion = true; + isNewIdentifierLocation = true; var contextualType = typeInfoResolver.getContextualType(containingObjectLiteral); if (!contextualType) { @@ -2384,6 +2387,7 @@ module ts { else { // Get scope members isMemberCompletion = false; + isNewIdentifierLocation = isNewIdentifierDefinitionLocation(previousToken); /// TODO filter meaning based on the current context var symbolMeanings = SymbolFlags.Type | SymbolFlags.Value | SymbolFlags.Namespace | SymbolFlags.Import; @@ -2401,6 +2405,8 @@ module ts { return { isMemberCompletion, + isNewIdentifierLocation, + isBuilder : isNewIdentifierDefinitionLocation, // temporary property used to match VS implementation entries: activeCompletionSession.entries }; @@ -2428,6 +2434,64 @@ module ts { return result; } + function isNewIdentifierDefinitionLocation(previousToken: Node): boolean { + if (previousToken) { + var containingNodeKind = previousToken.parent.kind; + switch (previousToken.kind) { + case SyntaxKind.CommaToken: + return containingNodeKind === SyntaxKind.CallExpression // func( a, | + || containingNodeKind === SyntaxKind.Constructor // constructor( a, | public, protected, private keywords are allowed here, so show completion + || containingNodeKind === SyntaxKind.NewExpression // new C(a, | + || containingNodeKind === SyntaxKind.ArrayLiteralExpression // [a, | + || containingNodeKind === SyntaxKind.BinaryExpression; // var x = (a, | + + + case SyntaxKind.OpenParenToken: + return containingNodeKind === SyntaxKind.CallExpression // func( | + || containingNodeKind === SyntaxKind.Constructor // constructor( | + || containingNodeKind === SyntaxKind.NewExpression // new C(a| + || containingNodeKind === SyntaxKind.ParenthesizedExpression; // var x = (a| + + case SyntaxKind.OpenBracketToken: + return containingNodeKind === SyntaxKind.ArrayLiteralExpression; // [ | + + case SyntaxKind.ModuleKeyword: // module | + return true; + + case SyntaxKind.DotToken: + return containingNodeKind === SyntaxKind.ModuleDeclaration; // module A.| + + case SyntaxKind.OpenBraceToken: + return containingNodeKind === SyntaxKind.ClassDeclaration; // class A{ | + + case SyntaxKind.EqualsToken: + return containingNodeKind === SyntaxKind.VariableDeclaration // var x = a| + || containingNodeKind === SyntaxKind.BinaryExpression; // x = a| + + case SyntaxKind.TemplateHead: + return containingNodeKind === SyntaxKind.TemplateExpression; // `aa ${| + + case SyntaxKind.TemplateMiddle: + return containingNodeKind === SyntaxKind.TemplateSpan; // `aa ${10} dd ${| + + case SyntaxKind.PublicKeyword: + case SyntaxKind.PrivateKeyword: + case SyntaxKind.ProtectedKeyword: + return containingNodeKind === SyntaxKind.PropertyDeclaration; // class A{ public | + } + + // Previous token may have been a keyword that was converted to an identifier. + switch (previousToken.getText()) { + case "public": + case "protected": + case "private": + return true; + } + } + + return false; + } + function isInStringOrRegularExpressionOrTemplateLiteral(previousToken: Node): boolean { if (previousToken.kind === SyntaxKind.StringLiteral || previousToken.kind === SyntaxKind.RegularExpressionLiteral @@ -2474,7 +2538,6 @@ module ts { case SyntaxKind.FunctionDeclaration: case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: - case SyntaxKind.Constructor: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.CallSignature: @@ -2494,28 +2557,54 @@ module ts { containingNodeKind === SyntaxKind.VariableDeclarationList || containingNodeKind === SyntaxKind.VariableStatement || containingNodeKind === SyntaxKind.EnumDeclaration || // enum a { foo, | - isFunction(containingNodeKind); + isFunction(containingNodeKind) || + containingNodeKind === SyntaxKind.ClassDeclaration || // class A 0) { - // If we think we're currently in something generic, then mark that that - // generic entity is complete. - angleBracketStack--; + // If we think we're currently in something generic, then mark that that + // generic entity is complete. + angleBracketStack--; } else if (token === SyntaxKind.AnyKeyword || token === SyntaxKind.StringKeyword || token === SyntaxKind.NumberKeyword || token === SyntaxKind.BooleanKeyword) { - if (angleBracketStack > 0 && !classifyKeywordsInGenerics) { - // If it looks like we're could be in something generic, don't classify this - // as a keyword. We may just get overwritten by the syntactic classifier, - // causing a noisy experience for the user. - token = SyntaxKind.Identifier; - } + if (angleBracketStack > 0 && !classifyKeywordsInGenerics) { + // If it looks like we're could be in something generic, don't classify this + // as a keyword. We may just get overwritten by the syntactic classifier, + // causing a noisy experience for the user. + token = SyntaxKind.Identifier; + } } lastNonTriviaToken = token; diff --git a/tests/baselines/reference/APISample_compile.js b/tests/baselines/reference/APISample_compile.js index 7fc7622ea17a5..88013c709164c 100644 --- a/tests/baselines/reference/APISample_compile.js +++ b/tests/baselines/reference/APISample_compile.js @@ -1694,6 +1694,7 @@ declare module "typescript" { } interface CompletionInfo { isMemberCompletion: boolean; + isNewIdentifierLocation: boolean; entries: CompletionEntry[]; } interface CompletionEntry { diff --git a/tests/baselines/reference/APISample_compile.types b/tests/baselines/reference/APISample_compile.types index 2859c021a374b..024434be63b66 100644 --- a/tests/baselines/reference/APISample_compile.types +++ b/tests/baselines/reference/APISample_compile.types @@ -5464,6 +5464,9 @@ declare module "typescript" { isMemberCompletion: boolean; >isMemberCompletion : boolean + isNewIdentifierLocation: boolean; +>isNewIdentifierLocation : boolean + entries: CompletionEntry[]; >entries : CompletionEntry[] >CompletionEntry : CompletionEntry diff --git a/tests/baselines/reference/APISample_linter.js b/tests/baselines/reference/APISample_linter.js index 9cc9c2ffd50bc..c429cec1d1c77 100644 --- a/tests/baselines/reference/APISample_linter.js +++ b/tests/baselines/reference/APISample_linter.js @@ -1723,6 +1723,7 @@ declare module "typescript" { } interface CompletionInfo { isMemberCompletion: boolean; + isNewIdentifierLocation: boolean; entries: CompletionEntry[]; } interface CompletionEntry { diff --git a/tests/baselines/reference/APISample_linter.types b/tests/baselines/reference/APISample_linter.types index aaa07714c6153..89411159d9acc 100644 --- a/tests/baselines/reference/APISample_linter.types +++ b/tests/baselines/reference/APISample_linter.types @@ -5594,6 +5594,9 @@ declare module "typescript" { isMemberCompletion: boolean; >isMemberCompletion : boolean + isNewIdentifierLocation: boolean; +>isNewIdentifierLocation : boolean + entries: CompletionEntry[]; >entries : CompletionEntry[] >CompletionEntry : CompletionEntry diff --git a/tests/baselines/reference/APISample_transform.js b/tests/baselines/reference/APISample_transform.js index 201053584acde..2ed4242460fc9 100644 --- a/tests/baselines/reference/APISample_transform.js +++ b/tests/baselines/reference/APISample_transform.js @@ -1724,6 +1724,7 @@ declare module "typescript" { } interface CompletionInfo { isMemberCompletion: boolean; + isNewIdentifierLocation: boolean; entries: CompletionEntry[]; } interface CompletionEntry { diff --git a/tests/baselines/reference/APISample_transform.types b/tests/baselines/reference/APISample_transform.types index 408285caaaa90..f44d2f787eb16 100644 --- a/tests/baselines/reference/APISample_transform.types +++ b/tests/baselines/reference/APISample_transform.types @@ -5542,6 +5542,9 @@ declare module "typescript" { isMemberCompletion: boolean; >isMemberCompletion : boolean + isNewIdentifierLocation: boolean; +>isNewIdentifierLocation : boolean + entries: CompletionEntry[]; >entries : CompletionEntry[] >CompletionEntry : CompletionEntry diff --git a/tests/baselines/reference/APISample_watcher.js b/tests/baselines/reference/APISample_watcher.js index 47f40c01d8e86..80dc949ade195 100644 --- a/tests/baselines/reference/APISample_watcher.js +++ b/tests/baselines/reference/APISample_watcher.js @@ -1761,6 +1761,7 @@ declare module "typescript" { } interface CompletionInfo { isMemberCompletion: boolean; + isNewIdentifierLocation: boolean; entries: CompletionEntry[]; } interface CompletionEntry { diff --git a/tests/baselines/reference/APISample_watcher.types b/tests/baselines/reference/APISample_watcher.types index dfdc133817e76..16df3df10c6c9 100644 --- a/tests/baselines/reference/APISample_watcher.types +++ b/tests/baselines/reference/APISample_watcher.types @@ -5720,6 +5720,9 @@ declare module "typescript" { isMemberCompletion: boolean; >isMemberCompletion : boolean + isNewIdentifierLocation: boolean; +>isNewIdentifierLocation : boolean + entries: CompletionEntry[]; >entries : CompletionEntry[] >CompletionEntry : CompletionEntry diff --git a/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_Generics.ts b/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_Generics.ts new file mode 100644 index 0000000000000..0e92a3ae9950b --- /dev/null +++ b/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_Generics.ts @@ -0,0 +1,18 @@ +/// + +////interface A { + goTo.position(m.position, m.fileName); + verify.completionListIsEmpty(); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_destructuring.ts b/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_destructuring.ts new file mode 100644 index 0000000000000..143c884e17782 --- /dev/null +++ b/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_destructuring.ts @@ -0,0 +1,22 @@ +/// + +//// var [x/*variable1*/ + +//// var [x, y/*variable2*/ + +//// var [./*variable3*/ + +//// var [x, ...z/*variable4*/ + +//// var {x/*variable5*/ + +//// var {x, y/*variable6*/ + +//// function func1({ a/*parameter1*/ + +//// function func2({ a, b/*parameter2*/ + +test.markers().forEach((m) => { + goTo.position(m.position, m.fileName); + verify.completionListIsEmpty(); +}); diff --git a/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_parameters.ts b/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_parameters.ts index 0fd66e9d0916a..10293c41a641e 100644 --- a/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_parameters.ts +++ b/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_parameters.ts @@ -10,23 +10,17 @@ ////function testFunction(a, b/*parameterName4*/ -////class bar1{ constructor(/*constructorParamter1*/ +////class bar5{ constructor(public /*constructorParamter1*/ -////class bar2{ constructor(a/*constructorParamter2*/ +////class bar6{ constructor(public a/*constructorParamter2*/ -////class bar3{ constructor(a, /*constructorParamter3*/ +////class bar7{ constructor(protected a/*constructorParamter3*/ -////class bar4{ constructor(a, b/*constructorParamter4*/ +////class bar8{ constructor(private a/*constructorParamter4*/ -////class bar5{ constructor(public /*constructorParamter5*/ +////class bar9{ constructor(.../*constructorParamter5*/ -////class bar6{ constructor(public a/*constructorParamter6*/ - -////class bar7{ constructor(private a/*constructorParamter7*/ - -////class bar8{ constructor(.../*constructorParamter8*/ - -////class bar9{ constructor(...a/*constructorParamter9*/ +////class bar10{ constructor(...a/*constructorParamter6*/ test.markers().forEach((m) => { diff --git a/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_properties.ts b/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_properties.ts new file mode 100644 index 0000000000000..58fdc1d23421b --- /dev/null +++ b/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_properties.ts @@ -0,0 +1,37 @@ +/// + +////var aa = 1; + +////class A1 { +//// /*property1*/ +////} + +////class A2 { +//// p/*property2*/ +////} + +////class A3 { +//// public s/*property3*/ +////} + +////class A4 { +//// a/*property4*/ +////} + +////class A5 { +//// public a/*property5*/ +////} + +////class A6 { +//// protected a/*property6*/ +////} + +////class A7 { +//// private a/*property7*/ +////} + +test.markers().forEach((m) => { + goTo.position(m.position, m.fileName); + verify.not.completionListIsEmpty(); + verify.completionListAllowsNewIdentifier(); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_varDeclarations.ts b/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_varDeclarations.ts index 346a102faa8c0..2e85364ce3c2f 100644 --- a/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_varDeclarations.ts +++ b/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_varDeclarations.ts @@ -11,7 +11,6 @@ ////var a2, a/*varName4*/ -debugger; test.markers().forEach((m) => { goTo.position(m.position, m.fileName); verify.completionListIsEmpty(); diff --git a/tests/cases/fourslash/completionListBuilderLocations_Modules.ts b/tests/cases/fourslash/completionListBuilderLocations_Modules.ts new file mode 100644 index 0000000000000..eac7085ab6668 --- /dev/null +++ b/tests/cases/fourslash/completionListBuilderLocations_Modules.ts @@ -0,0 +1,13 @@ +/// + +////module A/*moduleName1*/ + + +////module A./*moduleName2*/ + + +test.markers().forEach((m) => { + goTo.position(m.position, m.fileName); + verify.not.completionListIsEmpty(); + verify.completionListAllowsNewIdentifier(); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListBuilderLocations_VariableDeclarations.ts b/tests/cases/fourslash/completionListBuilderLocations_VariableDeclarations.ts new file mode 100644 index 0000000000000..45f16090fd89a --- /dev/null +++ b/tests/cases/fourslash/completionListBuilderLocations_VariableDeclarations.ts @@ -0,0 +1,34 @@ +/// + +////var x = a/*var1*/ + +////var x = (b/*var2*/ + +////var x = (c, d/*var3*/ + +//// var y : any = "", x = a/*var4*/ + +//// var y : any = "", x = (a/*var5*/ + +////class C{} +////var y = new C( + +//// class C{} +//// var y = new C(0, /*var7*/ + +////var y = [/*var8*/ + +////var y = [0, /*var9*/ + +////var y = `${/*var10*/ + +////var y = `${10} dd ${ /*var11*/ + +////var y = 10; y=/*var12*/ + +test.markers().forEach((m) => { + goTo.position(m.position, m.fileName); + verify.completionListAllowsNewIdentifier(); +}); + + diff --git a/tests/cases/fourslash/completionListBuilderLocations_parameters.ts b/tests/cases/fourslash/completionListBuilderLocations_parameters.ts new file mode 100644 index 0000000000000..12dba953d3db8 --- /dev/null +++ b/tests/cases/fourslash/completionListBuilderLocations_parameters.ts @@ -0,0 +1,22 @@ +/// + +////var aa = 1; + +////class bar1{ constructor(/*constructorParamter1*/ + +////class bar2{ constructor(a/*constructorParamter2*/ + +////class bar3{ constructor(a, /*constructorParamter3*/ + +////class bar4{ constructor(a, b/*constructorParamter4*/ + +////class bar6{ constructor(public a, /*constructorParamter5*/ + +////class bar7{ constructor(private a, /*constructorParamter6*/ + + +test.markers().forEach((m) => { + goTo.position(m.position, m.fileName); + verify.not.completionListIsEmpty(); + verify.completionListAllowsNewIdentifier(); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_modules.ts b/tests/cases/fourslash/completionListBuilderLocations_properties.ts similarity index 55% rename from tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_modules.ts rename to tests/cases/fourslash/completionListBuilderLocations_properties.ts index 4ebdf029a8998..287ffd6746b74 100644 --- a/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_modules.ts +++ b/tests/cases/fourslash/completionListBuilderLocations_properties.ts @@ -2,12 +2,15 @@ ////var aa = 1; -////module /*moduleName1*/ - -////module a/*moduleName2*/ +////class A1 { +//// public static /*property1*/ +////} +////class A2 { +//// public static a/*property2*/ +////} test.markers().forEach((m) => { goTo.position(m.position, m.fileName); verify.completionListIsEmpty(); -}); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 5b88b15f9fab0..267f2c1434120 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -172,6 +172,10 @@ module FourSlashInterface { FourSlash.currentTestState.verifyCompletionListIsEmpty(this.negative); } + public completionListAllowsNewIdentifier() { + FourSlash.currentTestState.verifyCompletionListAllowsNewIdentifier(this.negative); + } + public memberListIsEmpty() { FourSlash.currentTestState.verifyMemberListIsEmpty(this.negative); }