diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5c371e7a344..c807406d534 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(getExportsOfModule(module)) + } + 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 5e5993fe4a2..a81da0eaf74 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/formatting/smartIndenter.ts b/src/services/formatting/smartIndenter.ts index 593e6163e29..6974f160e10 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) { + return 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 { diff --git a/src/services/services.ts b/src/services/services.ts index 2fc154f610f..0b23b134bc5 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 (showCompletionsInImportsClause(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,18 @@ module ts { return result; } + 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; + } + function isNewIdentifierDefinitionLocation(previousToken: Node): boolean { if (previousToken) { var containingNodeKind = previousToken.parent.kind; @@ -2664,6 +2689,28 @@ module ts { return false; } + function filterModuleExports(exports: Symbol[], importDeclaration: ImportDeclaration): Symbol[] { + var exisingImports: Map = {}; + + if (!importDeclaration.importClause) { + return exports; + } + + 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; + } + return filter(exports, e => !lookUp(exisingImports, e.name)); + } + function filterContextualMembersList(contextualMemberSymbols: Symbol[], existingMembers: Declaration[]): Symbol[] { if (!existingMembers || existingMembers.length === 0) { return contextualMemberSymbols; @@ -4694,6 +4741,8 @@ module ts { return SemanticMeaning.Namespace; } + case SyntaxKind.NamedImports: + case SyntaxKind.ImportSpecifier: case SyntaxKind.ImportEqualsDeclaration: return SemanticMeaning.Value | SemanticMeaning.Type | SemanticMeaning.Namespace; diff --git a/tests/baselines/reference/APISample_compile.js b/tests/baselines/reference/APISample_compile.js index f906533da0a..ba9aced3b24 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 970562a7b65..3ad078773da 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 d4708039de0..54bb4a0dd80 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 6f5c1f3e91b..59bb6cc3b8c 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 d63d954288d..42bb19f5b68 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 7ffc3a11f1f..b3aca495547 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 4cd76682393..cafb7f94ce1 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 52385d9f70a..3a987c0296f 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 { diff --git a/tests/cases/fourslash/completionForExports.ts b/tests/cases/fourslash/completionForExports.ts new file mode 100644 index 00000000000..57c0f75efc1 --- /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