From 6055dea93e0b837bcf5c763e75f7e0997e41af46 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Tue, 24 Feb 2015 12:41:54 -0800 Subject: [PATCH 1/7] replace -1 in SmartIndenter with const enum --- src/services/formatting/smartIndenter.ts | 35 +++++++++++++++--------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/services/formatting/smartIndenter.ts b/src/services/formatting/smartIndenter.ts index 593e6163e2..6280649b83 100644 --- a/src/services/formatting/smartIndenter.ts +++ b/src/services/formatting/smartIndenter.ts @@ -2,6 +2,11 @@ module ts.formatting { export module SmartIndenter { + + const enum Value { + Unknown = -1 + } + export function getIndentation(position: number, sourceFile: SourceFile, options: EditorOptions): number { if (position > sourceFile.text.length) { return 0; // past EOF @@ -29,7 +34,7 @@ module ts.formatting { if (precedingToken.kind === SyntaxKind.CommaToken && precedingToken.parent.kind !== SyntaxKind.BinaryExpression) { // previous token is comma that separates items in list - find the previous item and try to derive indentation from it var actualIndentation = getActualIndentationForListItemBeforeComma(precedingToken, sourceFile, options); - if (actualIndentation !== -1) { + if (actualIndentation !== Value.Unknown) { return actualIndentation; } } @@ -57,7 +62,7 @@ module ts.formatting { // check if current node is a list item - if yes, take indentation from it var actualIndentation = getActualIndentationForListItem(current, sourceFile, options); - if (actualIndentation !== -1) { + if (actualIndentation !== Value.Unknown) { return actualIndentation; } @@ -101,7 +106,7 @@ module ts.formatting { if (useActualIndentation) { // check if current node is a list item - if yes, take indentation from it var actualIndentation = getActualIndentationForListItem(current, sourceFile, options); - if (actualIndentation !== -1) { + if (actualIndentation !== Value.Unknown) { return actualIndentation + indentationDelta; } } @@ -113,7 +118,7 @@ module ts.formatting { if (useActualIndentation) { // try to fetch actual indentation for current node from source text var actualIndentation = getActualIndentationForNode(current, parent, currentStart, parentAndChildShareLine, sourceFile, options); - if (actualIndentation !== -1) { + if (actualIndentation !== Value.Unknown) { return actualIndentation + indentationDelta; } } @@ -142,18 +147,22 @@ module ts.formatting { } /* - * Function returns -1 if indentation cannot be determined + * Function returns Value.Unknown if indentation cannot be determined */ function getActualIndentationForListItemBeforeComma(commaToken: Node, sourceFile: SourceFile, options: EditorOptions): number { // previous token is comma that separates items in list - find the previous item and try to derive indentation from it var commaItemInfo = findListItemInfo(commaToken); - Debug.assert(commaItemInfo && commaItemInfo.listItemIndex > 0); - // The item we're interested in is right before the comma - return deriveActualIndentationFromList(commaItemInfo.list.getChildren(), commaItemInfo.listItemIndex - 1, sourceFile, options); + if (commaItemInfo && commaItemInfo.listItemIndex > 0) { + deriveActualIndentationFromList(commaItemInfo.list.getChildren(), commaItemInfo.listItemIndex - 1, sourceFile, options); + } + else { + // handle broken code gracefully + return Value.Unknown; + } } /* - * Function returns -1 if actual indentation for node should not be used (i.e because node is nested expression) + * Function returns Value.Unknown if actual indentation for node should not be used (i.e because node is nested expression) */ function getActualIndentationForNode(current: Node, parent: Node, @@ -170,7 +179,7 @@ module ts.formatting { (parent.kind === SyntaxKind.SourceFile || !parentAndChildShareLine); if (!useActualIndentation) { - return -1; + return Value.Unknown; } return findColumnForFirstNonWhitespaceCharacterInLine(currentLineAndChar, sourceFile, options); @@ -271,11 +280,11 @@ module ts.formatting { function getActualIndentationForListItem(node: Node, sourceFile: SourceFile, options: EditorOptions): number { var containingList = getContainingList(node, sourceFile); - return containingList ? getActualIndentationFromList(containingList) : -1; + return containingList ? getActualIndentationFromList(containingList) : Value.Unknown; function getActualIndentationFromList(list: Node[]): number { var index = indexOf(list, node); - return index !== -1 ? deriveActualIndentationFromList(list, index, sourceFile, options) : -1; + return index !== -1 ? deriveActualIndentationFromList(list, index, sourceFile, options) : Value.Unknown; } } @@ -298,7 +307,7 @@ module ts.formatting { lineAndCharacter = getStartLineAndCharacterForNode(list[i], sourceFile); } - return -1; + return Value.Unknown; } function findColumnForFirstNonWhitespaceCharacterInLine(lineAndCharacter: LineAndCharacter, sourceFile: SourceFile, options: EditorOptions): number { From caabb7d99bcfdb0d3c2f685521099dfd4d0f7533 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Tue, 24 Feb 2015 15:37:13 -0800 Subject: [PATCH 2/7] added completion for exports in named imports section --- src/compiler/checker.ts | 14 +++++++++++ src/compiler/types.ts | 1 + src/services/services.ts | 53 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index acb90c4767..9ec8774357 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -56,6 +56,7 @@ module ts { isImplementationOfOverload, getAliasedSymbol: resolveImport, getEmitResolver, + getExportsOfExternalModule, }; var undefinedSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "undefined"); @@ -2754,6 +2755,19 @@ module ts { return result; } + function getExportsOfExternalModule(node: ImportDeclaration): Symbol[]{ + if (!node.moduleSpecifier) { + return emptyArray; + } + + var module = resolveExternalModuleName(node, node.moduleSpecifier); + if (!module || !module.exports) { + return emptyArray; + } + + return mapToArray(module.exports) + } + function getSignatureFromDeclaration(declaration: SignatureDeclaration): Signature { var links = getNodeLinks(declaration); if (!links.resolvedSignature) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 5e5993fe4a..a81da0eaf7 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1100,6 +1100,7 @@ module ts { getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): number; isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean; getAliasedSymbol(symbol: Symbol): Symbol; + getExportsOfExternalModule(node: ImportDeclaration): Symbol[]; // Should not be called directly. Should only be accessed through the Program instance. /* @internal */ getDiagnostics(sourceFile?: SourceFile): Diagnostic[]; diff --git a/src/services/services.ts b/src/services/services.ts index 2fc154f610..3d3a01ad79 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2403,6 +2403,19 @@ module ts { getCompletionEntriesFromSymbols(filteredMembers, activeCompletionSession); } } + else if (getAncestor(previousToken, SyntaxKind.ImportClause)) { + // cursor is in import clause + // try to show exported member for imported module + isMemberCompletion = true; + isNewIdentifierLocation = true; + if (canShowCompletionInImportsClause(previousToken)) { + var importDeclaration = getAncestor(previousToken, SyntaxKind.ImportDeclaration); + Debug.assert(importDeclaration !== undefined); + var exports = typeInfoResolver.getExportsOfExternalModule(importDeclaration); + var filteredExports = filterModuleExports(exports, importDeclaration); + getCompletionEntriesFromSymbols(filteredExports, activeCompletionSession); + } + } else { // Get scope members isMemberCompletion = false; @@ -2453,6 +2466,16 @@ module ts { return result; } + function canShowCompletionInImportsClause(node: Node): boolean { + // import {| + // import {a,| + if (node.kind === SyntaxKind.OpenBraceToken || node.kind === SyntaxKind.CommaToken) { + return node.parent.kind === SyntaxKind.NamedImports; + } + + return false; + } + function isNewIdentifierDefinitionLocation(previousToken: Node): boolean { if (previousToken) { var containingNodeKind = previousToken.parent.kind; @@ -2531,6 +2554,8 @@ module ts { return false; } + + function getContainingObjectLiteralApplicableForCompletion(previousToken: Node): ObjectLiteralExpression { // The locations in an object literal expression that are applicable for completion are property name definition locations. @@ -2664,6 +2689,34 @@ module ts { return false; } + function filterModuleExports(exports: Symbol[], importDeclaration: ImportDeclaration): Symbol[]{ + var exisingImports: Map = {}; + + if (!importDeclaration.importClause) { + return exports; + } + + if (importDeclaration.importClause.name) { + exisingImports[importDeclaration.importClause.name.text] = true; + } + + if (importDeclaration.importClause.namedBindings && + importDeclaration.importClause.namedBindings.kind === SyntaxKind.NamedImports) { + + forEach((importDeclaration.importClause.namedBindings).elements, el => { + var name = el.propertyName || el.name; + exisingImports[name.text] = true; + }); + } + + if (isEmpty(exisingImports)) { + return exports; + } + else { + return filter(exports, e => !lookUp(exisingImports, e.name)); + } + } + function filterContextualMembersList(contextualMemberSymbols: Symbol[], existingMembers: Declaration[]): Symbol[] { if (!existingMembers || existingMembers.length === 0) { return contextualMemberSymbols; From e8830f913c5f128ed7287d82ca478d542428153f Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Tue, 24 Feb 2015 16:11:15 -0800 Subject: [PATCH 3/7] added missing declaration kinds to getMeaningForDeclaration --- src/services/services.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/services/services.ts b/src/services/services.ts index 3d3a01ad79..9c264332fc 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -4747,6 +4747,8 @@ module ts { return SemanticMeaning.Namespace; } + case SyntaxKind.NamedImports: + case SyntaxKind.ImportSpecifier: case SyntaxKind.ImportEqualsDeclaration: return SemanticMeaning.Value | SemanticMeaning.Type | SemanticMeaning.Namespace; From f859d77d9eeb1e77157b39a52981bc43276e92b4 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Tue, 24 Feb 2015 16:11:43 -0800 Subject: [PATCH 4/7] added tests --- tests/cases/fourslash/completionForExports.ts | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 tests/cases/fourslash/completionForExports.ts diff --git a/tests/cases/fourslash/completionForExports.ts b/tests/cases/fourslash/completionForExports.ts new file mode 100644 index 0000000000..57c0f75efc --- /dev/null +++ b/tests/cases/fourslash/completionForExports.ts @@ -0,0 +1,32 @@ +/// + +// @Filename: m1.ts +////export var foo: number = 1; +////export function bar() { return 10; } +////export function baz() { return 10; } + +// @Filename: m2.ts +////import {/*1*/, /*2*/ from "m1" +////import {/*3*/} from "m1" +////import {foo,/*4*/ from "m1" +////import {bar as /*5*/, /*6*/ from "m1" +////import {foo, bar, baz as b,/*7*/} from "m1" +function verifyCompletionAtMarker(marker: string, ...completions: string[]) { + goTo.marker(marker); + if (completions.length) { + for (var i = 0; i < completions.length; ++i) { + verify.completionListContains(completions[i]); + } + } + else { + verify.completionListIsEmpty(); + } +} + +verifyCompletionAtMarker("1", "foo", "bar", "baz"); +verifyCompletionAtMarker("2", "foo", "bar", "baz"); +verifyCompletionAtMarker("3", "foo", "bar", "baz"); +verifyCompletionAtMarker("4", "bar", "baz"); +verifyCompletionAtMarker("5"); +verifyCompletionAtMarker("6", "foo", "baz"); +verifyCompletionAtMarker("7"); \ No newline at end of file From f74a45feae4e0b7a1d53944c3efc870f57d355d3 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Tue, 24 Feb 2015 16:14:37 -0800 Subject: [PATCH 5/7] dropped extra newlines --- src/services/services.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 9c264332fc..a8caa80538 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2554,8 +2554,6 @@ module ts { return false; } - - function getContainingObjectLiteralApplicableForCompletion(previousToken: Node): ObjectLiteralExpression { // The locations in an object literal expression that are applicable for completion are property name definition locations. From 92dddd099af201480b991251c810e82d377e52c9 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Tue, 24 Feb 2015 17:08:55 -0800 Subject: [PATCH 6/7] added missing return, accepted test baselines --- src/services/formatting/smartIndenter.ts | 2 +- tests/baselines/reference/APISample_compile.js | 1 + tests/baselines/reference/APISample_compile.types | 6 ++++++ tests/baselines/reference/APISample_linter.js | 1 + tests/baselines/reference/APISample_linter.types | 6 ++++++ tests/baselines/reference/APISample_transform.js | 1 + tests/baselines/reference/APISample_transform.types | 6 ++++++ tests/baselines/reference/APISample_watcher.js | 1 + tests/baselines/reference/APISample_watcher.types | 6 ++++++ 9 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/services/formatting/smartIndenter.ts b/src/services/formatting/smartIndenter.ts index 6280649b83..6974f160e1 100644 --- a/src/services/formatting/smartIndenter.ts +++ b/src/services/formatting/smartIndenter.ts @@ -153,7 +153,7 @@ module ts.formatting { // previous token is comma that separates items in list - find the previous item and try to derive indentation from it var commaItemInfo = findListItemInfo(commaToken); if (commaItemInfo && commaItemInfo.listItemIndex > 0) { - deriveActualIndentationFromList(commaItemInfo.list.getChildren(), commaItemInfo.listItemIndex - 1, sourceFile, options); + return deriveActualIndentationFromList(commaItemInfo.list.getChildren(), commaItemInfo.listItemIndex - 1, sourceFile, options); } else { // handle broken code gracefully diff --git a/tests/baselines/reference/APISample_compile.js b/tests/baselines/reference/APISample_compile.js index f906533da0..ba9aced3b2 100644 --- a/tests/baselines/reference/APISample_compile.js +++ b/tests/baselines/reference/APISample_compile.js @@ -869,6 +869,7 @@ declare module "typescript" { getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): number; isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean; getAliasedSymbol(symbol: Symbol): Symbol; + getExportsOfExternalModule(node: ImportDeclaration): Symbol[]; } interface SymbolDisplayBuilder { buildTypeDisplay(type: Type, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; diff --git a/tests/baselines/reference/APISample_compile.types b/tests/baselines/reference/APISample_compile.types index 970562a7b6..3ad078773d 100644 --- a/tests/baselines/reference/APISample_compile.types +++ b/tests/baselines/reference/APISample_compile.types @@ -2718,6 +2718,12 @@ declare module "typescript" { >getAliasedSymbol : (symbol: Symbol) => Symbol >symbol : Symbol >Symbol : Symbol +>Symbol : Symbol + + getExportsOfExternalModule(node: ImportDeclaration): Symbol[]; +>getExportsOfExternalModule : (node: ImportDeclaration) => Symbol[] +>node : ImportDeclaration +>ImportDeclaration : ImportDeclaration >Symbol : Symbol } interface SymbolDisplayBuilder { diff --git a/tests/baselines/reference/APISample_linter.js b/tests/baselines/reference/APISample_linter.js index d4708039de..54bb4a0dd8 100644 --- a/tests/baselines/reference/APISample_linter.js +++ b/tests/baselines/reference/APISample_linter.js @@ -900,6 +900,7 @@ declare module "typescript" { getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): number; isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean; getAliasedSymbol(symbol: Symbol): Symbol; + getExportsOfExternalModule(node: ImportDeclaration): Symbol[]; } interface SymbolDisplayBuilder { buildTypeDisplay(type: Type, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; diff --git a/tests/baselines/reference/APISample_linter.types b/tests/baselines/reference/APISample_linter.types index 6f5c1f3e91..59bb6cc3b8 100644 --- a/tests/baselines/reference/APISample_linter.types +++ b/tests/baselines/reference/APISample_linter.types @@ -2864,6 +2864,12 @@ declare module "typescript" { >getAliasedSymbol : (symbol: Symbol) => Symbol >symbol : Symbol >Symbol : Symbol +>Symbol : Symbol + + getExportsOfExternalModule(node: ImportDeclaration): Symbol[]; +>getExportsOfExternalModule : (node: ImportDeclaration) => Symbol[] +>node : ImportDeclaration +>ImportDeclaration : ImportDeclaration >Symbol : Symbol } interface SymbolDisplayBuilder { diff --git a/tests/baselines/reference/APISample_transform.js b/tests/baselines/reference/APISample_transform.js index d63d954288..42bb19f5b6 100644 --- a/tests/baselines/reference/APISample_transform.js +++ b/tests/baselines/reference/APISample_transform.js @@ -901,6 +901,7 @@ declare module "typescript" { getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): number; isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean; getAliasedSymbol(symbol: Symbol): Symbol; + getExportsOfExternalModule(node: ImportDeclaration): Symbol[]; } interface SymbolDisplayBuilder { buildTypeDisplay(type: Type, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; diff --git a/tests/baselines/reference/APISample_transform.types b/tests/baselines/reference/APISample_transform.types index 7ffc3a11f1..b3aca49554 100644 --- a/tests/baselines/reference/APISample_transform.types +++ b/tests/baselines/reference/APISample_transform.types @@ -2814,6 +2814,12 @@ declare module "typescript" { >getAliasedSymbol : (symbol: Symbol) => Symbol >symbol : Symbol >Symbol : Symbol +>Symbol : Symbol + + getExportsOfExternalModule(node: ImportDeclaration): Symbol[]; +>getExportsOfExternalModule : (node: ImportDeclaration) => Symbol[] +>node : ImportDeclaration +>ImportDeclaration : ImportDeclaration >Symbol : Symbol } interface SymbolDisplayBuilder { diff --git a/tests/baselines/reference/APISample_watcher.js b/tests/baselines/reference/APISample_watcher.js index 4cd7668239..cafb7f94ce 100644 --- a/tests/baselines/reference/APISample_watcher.js +++ b/tests/baselines/reference/APISample_watcher.js @@ -938,6 +938,7 @@ declare module "typescript" { getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): number; isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean; getAliasedSymbol(symbol: Symbol): Symbol; + getExportsOfExternalModule(node: ImportDeclaration): Symbol[]; } interface SymbolDisplayBuilder { buildTypeDisplay(type: Type, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; diff --git a/tests/baselines/reference/APISample_watcher.types b/tests/baselines/reference/APISample_watcher.types index 52385d9f70..3a987c0296 100644 --- a/tests/baselines/reference/APISample_watcher.types +++ b/tests/baselines/reference/APISample_watcher.types @@ -2987,6 +2987,12 @@ declare module "typescript" { >getAliasedSymbol : (symbol: Symbol) => Symbol >symbol : Symbol >Symbol : Symbol +>Symbol : Symbol + + getExportsOfExternalModule(node: ImportDeclaration): Symbol[]; +>getExportsOfExternalModule : (node: ImportDeclaration) => Symbol[] +>node : ImportDeclaration +>ImportDeclaration : ImportDeclaration >Symbol : Symbol } interface SymbolDisplayBuilder { From 0d781d8b29d529d4000563c9d047f02b9a2ad8a8 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Tue, 24 Feb 2015 18:31:53 -0800 Subject: [PATCH 7/7] addressed CR feedback --- src/compiler/checker.ts | 2 +- src/services/services.ts | 24 ++++++++++-------------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9ec8774357..309c3116b3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2765,7 +2765,7 @@ module ts { return emptyArray; } - return mapToArray(module.exports) + return mapToArray(getExportsOfModule(module)) } function getSignatureFromDeclaration(declaration: SignatureDeclaration): Signature { diff --git a/src/services/services.ts b/src/services/services.ts index a8caa80538..0b23b134bc 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2408,7 +2408,7 @@ module ts { // try to show exported member for imported module isMemberCompletion = true; isNewIdentifierLocation = true; - if (canShowCompletionInImportsClause(previousToken)) { + if (showCompletionsInImportsClause(previousToken)) { var importDeclaration = getAncestor(previousToken, SyntaxKind.ImportDeclaration); Debug.assert(importDeclaration !== undefined); var exports = typeInfoResolver.getExportsOfExternalModule(importDeclaration); @@ -2466,11 +2466,13 @@ module ts { return result; } - function canShowCompletionInImportsClause(node: Node): boolean { - // import {| - // import {a,| - if (node.kind === SyntaxKind.OpenBraceToken || node.kind === SyntaxKind.CommaToken) { - return node.parent.kind === SyntaxKind.NamedImports; + function showCompletionsInImportsClause(node: Node): boolean { + if (node) { + // import {| + // import {a,| + if (node.kind === SyntaxKind.OpenBraceToken || node.kind === SyntaxKind.CommaToken) { + return node.parent.kind === SyntaxKind.NamedImports; + } } return false; @@ -2687,17 +2689,13 @@ module ts { return false; } - function filterModuleExports(exports: Symbol[], importDeclaration: ImportDeclaration): Symbol[]{ + function filterModuleExports(exports: Symbol[], importDeclaration: ImportDeclaration): Symbol[] { var exisingImports: Map = {}; if (!importDeclaration.importClause) { return exports; } - if (importDeclaration.importClause.name) { - exisingImports[importDeclaration.importClause.name.text] = true; - } - if (importDeclaration.importClause.namedBindings && importDeclaration.importClause.namedBindings.kind === SyntaxKind.NamedImports) { @@ -2710,9 +2708,7 @@ module ts { if (isEmpty(exisingImports)) { return exports; } - else { - return filter(exports, e => !lookUp(exisingImports, e.name)); - } + return filter(exports, e => !lookUp(exisingImports, e.name)); } function filterContextualMembersList(contextualMemberSymbols: Symbol[], existingMembers: Declaration[]): Symbol[] {