From 443752ace77a0d38711bf3e339ce3841dd7bef9e Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Thu, 9 Jul 2015 16:57:52 -0700 Subject: [PATCH 01/17] Added tests for completion lists in export clauses. --- .../completionListInExportClause01.ts | 39 +++++++++++++++++++ .../completionListInExportClause02.ts | 14 +++++++ 2 files changed, 53 insertions(+) create mode 100644 tests/cases/fourslash/completionListInExportClause01.ts create mode 100644 tests/cases/fourslash/completionListInExportClause02.ts diff --git a/tests/cases/fourslash/completionListInExportClause01.ts b/tests/cases/fourslash/completionListInExportClause01.ts new file mode 100644 index 00000000000..6e6cd6c678b --- /dev/null +++ b/tests/cases/fourslash/completionListInExportClause01.ts @@ -0,0 +1,39 @@ +/// + +// @Filename: m1.ts +////export var foo: number = 1; +////export function bar() { return 10; } +////export function baz() { return 10; } + +// @Filename: m2.ts +////export {/*1*/, /*2*/ from "m1" +////export {/*3*/} from "m1" +////export {foo,/*4*/ from "m1" +////export {bar as /*5*/, /*6*/ from "m1" +////export {foo, bar, baz as b,/*7*/} from "m1" +function verifyCompletionAtMarker(marker: string, showBuilder: boolean, ...completions: string[]) { + goTo.marker(marker); + if (completions.length) { + for (let completion of completions) { + verify.completionListContains(completion); + } + } + else { + verify.completionListIsEmpty(); + } + + if (showBuilder) { + verify.completionListAllowsNewIdentifier(); + } + else { + verify.not.completionListAllowsNewIdentifier(); + } +} + +verifyCompletionAtMarker("1", /*showBuilder*/ false, "foo", "bar", "baz"); +verifyCompletionAtMarker("2", /*showBuilder*/ false, "foo", "bar", "baz"); +verifyCompletionAtMarker("3", /*showBuilder*/ false, "foo", "bar", "baz"); +verifyCompletionAtMarker("4", /*showBuilder*/ false, "bar", "baz"); +verifyCompletionAtMarker("5", /*showBuilder*/ true); +verifyCompletionAtMarker("6", /*showBuilder*/ false, "foo", "baz"); +verifyCompletionAtMarker("7", /*showBuilder*/ false); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListInExportClause02.ts b/tests/cases/fourslash/completionListInExportClause02.ts new file mode 100644 index 00000000000..4b40976f521 --- /dev/null +++ b/tests/cases/fourslash/completionListInExportClause02.ts @@ -0,0 +1,14 @@ +/// + +////declare module "M1" { +//// export var V; +////} +////var W; +////declare module "M2" { +//// export { /**/ } from "M1" +////} + +goTo.marker(); +verify.completionListContains("V"); +verify.not.completionListContains("W"); +verify.not.completionListAllowsNewIdentifier(); \ No newline at end of file From 123e02974ed79a4e16b83d7a60c8a23af6ce48d2 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 10 Jul 2015 15:22:32 -0700 Subject: [PATCH 02/17] Newline. --- tests/cases/fourslash/completionListInExportClause01.ts | 1 + tests/cases/fourslash/completionListInImportClause01.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/cases/fourslash/completionListInExportClause01.ts b/tests/cases/fourslash/completionListInExportClause01.ts index 6e6cd6c678b..12726444bd2 100644 --- a/tests/cases/fourslash/completionListInExportClause01.ts +++ b/tests/cases/fourslash/completionListInExportClause01.ts @@ -11,6 +11,7 @@ ////export {foo,/*4*/ from "m1" ////export {bar as /*5*/, /*6*/ from "m1" ////export {foo, bar, baz as b,/*7*/} from "m1" + function verifyCompletionAtMarker(marker: string, showBuilder: boolean, ...completions: string[]) { goTo.marker(marker); if (completions.length) { diff --git a/tests/cases/fourslash/completionListInImportClause01.ts b/tests/cases/fourslash/completionListInImportClause01.ts index 1191216baf1..2b679ffe550 100644 --- a/tests/cases/fourslash/completionListInImportClause01.ts +++ b/tests/cases/fourslash/completionListInImportClause01.ts @@ -11,6 +11,7 @@ ////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, showBuilder: boolean, ...completions: string[]) { goTo.marker(marker); if (completions.length) { From a309b1dd6705fe7deb2e774af2f43a3396cd9fef Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 10 Jul 2015 15:38:06 -0700 Subject: [PATCH 03/17] Handle export declarations with module specifiers as well. --- src/services/services.ts | 99 +++++++++++++++++++++------------------- 1 file changed, 53 insertions(+), 46 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 3f656fe75ff..b311898f38d 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3014,17 +3014,17 @@ namespace ts { function tryGetGlobalSymbols(): boolean { let objectLikeContainer: ObjectLiteralExpression | BindingPattern; - let importClause: ImportClause; + let namedImportsOrExports: NamedImportsOrExports; let jsxContainer: JsxOpeningLikeElement; if (objectLikeContainer = tryGetObjectLikeCompletionContainer(contextToken)) { return tryGetObjectLikeCompletionSymbols(objectLikeContainer); } - if (importClause = getAncestor(contextToken, SyntaxKind.ImportClause)) { + if (namedImportsOrExports = tryGetNamedImportsOrExportsForCompletion(contextToken)) { // cursor is in an import clause // try to show exported member for imported module - return tryGetImportClauseCompletionSymbols(importClause); + return tryGetImportOrExportClauseCompletionSymbols(namedImportsOrExports); } if (jsxContainer = tryGetContainingJsxElement(contextToken)) { @@ -3109,18 +3109,6 @@ namespace ts { return result; } - function shouldShowCompletionsInImportsClause(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) { let containingNodeKind = previousToken.parent.kind; @@ -3252,38 +3240,44 @@ namespace ts { } /** - * Aggregates relevant symbols for completion in import clauses; for instance, + * Aggregates relevant symbols for completion in import clauses and export clauses + * whose declarations have a module specifier; for instance, symbols will be aggregated for * - * import { $ } from "moduleName"; + * import { | } from "moduleName"; + * export { a as foo, | } from "moduleName"; + * + * but not for + * + * export { | }; * * Relevant symbols are stored in the captured 'symbols' variable. * * @returns true if 'symbols' was successfully populated; false otherwise. */ - function tryGetImportClauseCompletionSymbols(importClause: ImportClause): boolean { + function tryGetImportOrExportClauseCompletionSymbols(namedImportsOrExports: NamedImportsOrExports): boolean { // cursor is in import clause // try to show exported member for imported module - if (shouldShowCompletionsInImportsClause(contextToken)) { - isMemberCompletion = true; - isNewIdentifierLocation = false; + isMemberCompletion = true; + isNewIdentifierLocation = false; - let importDeclaration = importClause.parent; - Debug.assert(importDeclaration !== undefined && importDeclaration.kind === SyntaxKind.ImportDeclaration); + let declarationKind = namedImportsOrExports.kind === SyntaxKind.NamedImports ? + SyntaxKind.ImportDeclaration : + SyntaxKind.ExportDeclaration; + let importOrExportDeclaration = getAncestor(namedImportsOrExports, declarationKind); + let moduleSpecifier = importOrExportDeclaration.moduleSpecifier; - let exports: Symbol[]; - let moduleSpecifierSymbol = typeChecker.getSymbolAtLocation(importDeclaration.moduleSpecifier); - if (moduleSpecifierSymbol) { - exports = typeChecker.getExportsOfModule(moduleSpecifierSymbol); - } - - //let exports = typeInfoResolver.getExportsOfImportDeclaration(importDeclaration); - symbols = exports ? filterModuleExports(exports, importDeclaration) : emptyArray; + if (!moduleSpecifier) { + return false; } - else { - isMemberCompletion = false; - isNewIdentifierLocation = true; + + let exports: Symbol[]; + let moduleSpecifierSymbol = typeChecker.getSymbolAtLocation(importOrExportDeclaration.moduleSpecifier); + if (moduleSpecifierSymbol) { + exports = typeChecker.getExportsOfModule(moduleSpecifierSymbol); } + symbols = exports ? filterModuleExports(exports, namedImportsOrExports) : emptyArray; + return true; } @@ -3307,6 +3301,26 @@ namespace ts { return undefined; } + /** + * Returns the containing list of named imports or exports of a context token, + * on the condition that one exists and that the context implies completion should be given. + */ + function tryGetNamedImportsOrExportsForCompletion(contextToken: Node): NamedImportsOrExports { + if (contextToken) { + switch (contextToken.kind) { + case SyntaxKind.OpenBraceToken: // import { | + case SyntaxKind.CommaToken: // import { a as 0, | + switch (contextToken.parent.kind) { + case SyntaxKind.NamedImports: + case SyntaxKind.NamedExports: + return contextToken.parent; + } + } + } + + return undefined; + } + function tryGetContainingJsxElement(contextToken: Node): JsxOpeningLikeElement { if (contextToken) { let parent = contextToken.parent; @@ -3452,25 +3466,18 @@ namespace ts { return false; } - function filterModuleExports(exports: Symbol[], importDeclaration: ImportDeclaration): Symbol[] { + function filterModuleExports(exports: Symbol[], namedImportsOrExports: NamedImportsOrExports): Symbol[] { let exisingImports: Map = {}; - if (!importDeclaration.importClause) { - return exports; - } - - if (importDeclaration.importClause.namedBindings && - importDeclaration.importClause.namedBindings.kind === SyntaxKind.NamedImports) { - - forEach((importDeclaration.importClause.namedBindings).elements, el => { - let name = el.propertyName || el.name; - exisingImports[name.text] = true; - }); + for (let element of namedImportsOrExports.elements) { + let name = element.propertyName || element.name; + exisingImports[name.text] = true; } if (isEmpty(exisingImports)) { return exports; } + return filter(exports, e => !lookUp(exisingImports, e.name)); } From 7a0b224ba5b066bf89402a0b86c1e8ebf2160e13 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 10 Jul 2015 16:20:12 -0700 Subject: [PATCH 04/17] Added tests. --- ...ationWithModuleSpecifierNameOnNextLine1.ts | 20 +++++++++++++++++++ .../cases/compiler/unclosedExportClause01.ts | 16 +++++++++++++++ .../cases/compiler/unclosedExportClause02.ts | 20 +++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 tests/cases/compiler/exportDeclarationWithModuleSpecifierNameOnNextLine1.ts create mode 100644 tests/cases/compiler/unclosedExportClause01.ts create mode 100644 tests/cases/compiler/unclosedExportClause02.ts diff --git a/tests/cases/compiler/exportDeclarationWithModuleSpecifierNameOnNextLine1.ts b/tests/cases/compiler/exportDeclarationWithModuleSpecifierNameOnNextLine1.ts new file mode 100644 index 00000000000..ea3f55db16b --- /dev/null +++ b/tests/cases/compiler/exportDeclarationWithModuleSpecifierNameOnNextLine1.ts @@ -0,0 +1,20 @@ +// @module: commonjs + +// @filename: t1.ts +export var x = "x"; + +// @filename: t2.ts +export { x } from + "./t1"; + +// @filename: t3.ts +export { } from + "./t1"; + +// @filename: t4.ts +export { x as a } from + "./t1"; + +// @filename: t5.ts +export { x as a, } from + "./t1"; \ No newline at end of file diff --git a/tests/cases/compiler/unclosedExportClause01.ts b/tests/cases/compiler/unclosedExportClause01.ts new file mode 100644 index 00000000000..3bcbc713d61 --- /dev/null +++ b/tests/cases/compiler/unclosedExportClause01.ts @@ -0,0 +1,16 @@ +// @module: commonjs + +// @filename: t1.ts +export var x = "x"; + +// @filename: t2.ts +export { x, from "./t1" + +// @filename: t3.ts +export { from "./t1" + +// @filename: t4.ts +export { x as a from "./t1" + +// @filename: t5.ts +export { x as a, from "./t1" \ No newline at end of file diff --git a/tests/cases/compiler/unclosedExportClause02.ts b/tests/cases/compiler/unclosedExportClause02.ts new file mode 100644 index 00000000000..45dcb65a4cb --- /dev/null +++ b/tests/cases/compiler/unclosedExportClause02.ts @@ -0,0 +1,20 @@ +// @module: commonjs + +// @filename: t1.ts +export var x = "x"; + +// @filename: t2.ts +export { x, from + "./t1"; + +// @filename: t3.ts +export { from + "./t1"; + +// @filename: t4.ts +export { x as a from + "./t1"; + +// @filename: t5.ts +export { x as a, from + "./t1"; \ No newline at end of file From 120d0d11a0c2f2d2ec474461d945477a3ed041ae Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 10 Jul 2015 16:20:25 -0700 Subject: [PATCH 05/17] Accepted baselines. --- ...ationWithModuleSpecifierNameOnNextLine1.js | 34 +++++++++++ ...WithModuleSpecifierNameOnNextLine1.symbols | 28 +++++++++ ...onWithModuleSpecifierNameOnNextLine1.types | 29 ++++++++++ .../unclosedExportClause01.errors.txt | 53 +++++++++++++++++ .../reference/unclosedExportClause01.js | 28 +++++++++ .../unclosedExportClause02.errors.txt | 57 +++++++++++++++++++ .../reference/unclosedExportClause02.js | 32 +++++++++++ 7 files changed, 261 insertions(+) create mode 100644 tests/baselines/reference/exportDeclarationWithModuleSpecifierNameOnNextLine1.js create mode 100644 tests/baselines/reference/exportDeclarationWithModuleSpecifierNameOnNextLine1.symbols create mode 100644 tests/baselines/reference/exportDeclarationWithModuleSpecifierNameOnNextLine1.types create mode 100644 tests/baselines/reference/unclosedExportClause01.errors.txt create mode 100644 tests/baselines/reference/unclosedExportClause01.js create mode 100644 tests/baselines/reference/unclosedExportClause02.errors.txt create mode 100644 tests/baselines/reference/unclosedExportClause02.js diff --git a/tests/baselines/reference/exportDeclarationWithModuleSpecifierNameOnNextLine1.js b/tests/baselines/reference/exportDeclarationWithModuleSpecifierNameOnNextLine1.js new file mode 100644 index 00000000000..a6700fb5e59 --- /dev/null +++ b/tests/baselines/reference/exportDeclarationWithModuleSpecifierNameOnNextLine1.js @@ -0,0 +1,34 @@ +//// [tests/cases/compiler/exportDeclarationWithModuleSpecifierNameOnNextLine1.ts] //// + +//// [t1.ts] + +export var x = "x"; + +//// [t2.ts] +export { x } from + "./t1"; + +//// [t3.ts] +export { } from + "./t1"; + +//// [t4.ts] +export { x as a } from + "./t1"; + +//// [t5.ts] +export { x as a, } from + "./t1"; + +//// [t1.js] +exports.x = "x"; +//// [t2.js] +var t1_1 = require("./t1"); +exports.x = t1_1.x; +//// [t3.js] +//// [t4.js] +var t1_1 = require("./t1"); +exports.a = t1_1.x; +//// [t5.js] +var t1_1 = require("./t1"); +exports.a = t1_1.x; diff --git a/tests/baselines/reference/exportDeclarationWithModuleSpecifierNameOnNextLine1.symbols b/tests/baselines/reference/exportDeclarationWithModuleSpecifierNameOnNextLine1.symbols new file mode 100644 index 00000000000..331766f7712 --- /dev/null +++ b/tests/baselines/reference/exportDeclarationWithModuleSpecifierNameOnNextLine1.symbols @@ -0,0 +1,28 @@ +=== tests/cases/compiler/t1.ts === + +export var x = "x"; +>x : Symbol(x, Decl(t1.ts, 1, 10)) + +=== tests/cases/compiler/t2.ts === +export { x } from +>x : Symbol(x, Decl(t2.ts, 0, 8)) + + "./t1"; + +=== tests/cases/compiler/t3.ts === +export { } from +No type information for this code. "./t1"; +No type information for this code. +No type information for this code.=== tests/cases/compiler/t4.ts === +export { x as a } from +>x : Symbol(a, Decl(t4.ts, 0, 8)) +>a : Symbol(a, Decl(t4.ts, 0, 8)) + + "./t1"; + +=== tests/cases/compiler/t5.ts === +export { x as a, } from +>x : Symbol(a, Decl(t5.ts, 0, 8)) +>a : Symbol(a, Decl(t5.ts, 0, 8)) + + "./t1"; diff --git a/tests/baselines/reference/exportDeclarationWithModuleSpecifierNameOnNextLine1.types b/tests/baselines/reference/exportDeclarationWithModuleSpecifierNameOnNextLine1.types new file mode 100644 index 00000000000..1ad841fe947 --- /dev/null +++ b/tests/baselines/reference/exportDeclarationWithModuleSpecifierNameOnNextLine1.types @@ -0,0 +1,29 @@ +=== tests/cases/compiler/t1.ts === + +export var x = "x"; +>x : string +>"x" : string + +=== tests/cases/compiler/t2.ts === +export { x } from +>x : string + + "./t1"; + +=== tests/cases/compiler/t3.ts === +export { } from +No type information for this code. "./t1"; +No type information for this code. +No type information for this code.=== tests/cases/compiler/t4.ts === +export { x as a } from +>x : string +>a : string + + "./t1"; + +=== tests/cases/compiler/t5.ts === +export { x as a, } from +>x : string +>a : string + + "./t1"; diff --git a/tests/baselines/reference/unclosedExportClause01.errors.txt b/tests/baselines/reference/unclosedExportClause01.errors.txt new file mode 100644 index 00000000000..22c38f4af8e --- /dev/null +++ b/tests/baselines/reference/unclosedExportClause01.errors.txt @@ -0,0 +1,53 @@ +tests/cases/compiler/t2.ts(1,10): error TS2304: Cannot find name 'x'. +tests/cases/compiler/t2.ts(1,13): error TS2304: Cannot find name 'from'. +tests/cases/compiler/t2.ts(1,18): error TS1005: ',' expected. +tests/cases/compiler/t3.ts(1,10): error TS2304: Cannot find name 'from'. +tests/cases/compiler/t3.ts(1,15): error TS1005: ',' expected. +tests/cases/compiler/t4.ts(1,10): error TS2304: Cannot find name 'x'. +tests/cases/compiler/t4.ts(1,17): error TS1005: ',' expected. +tests/cases/compiler/t4.ts(1,17): error TS2304: Cannot find name 'from'. +tests/cases/compiler/t4.ts(1,22): error TS1005: ',' expected. +tests/cases/compiler/t5.ts(1,10): error TS2304: Cannot find name 'x'. +tests/cases/compiler/t5.ts(1,18): error TS2304: Cannot find name 'from'. +tests/cases/compiler/t5.ts(1,23): error TS1005: ',' expected. + + +==== tests/cases/compiler/t1.ts (0 errors) ==== + + export var x = "x"; + +==== tests/cases/compiler/t2.ts (3 errors) ==== + export { x, from "./t1" + ~ +!!! error TS2304: Cannot find name 'x'. + ~~~~ +!!! error TS2304: Cannot find name 'from'. + ~~~~~~ +!!! error TS1005: ',' expected. + +==== tests/cases/compiler/t3.ts (2 errors) ==== + export { from "./t1" + ~~~~ +!!! error TS2304: Cannot find name 'from'. + ~~~~~~ +!!! error TS1005: ',' expected. + +==== tests/cases/compiler/t4.ts (4 errors) ==== + export { x as a from "./t1" + ~ +!!! error TS2304: Cannot find name 'x'. + ~~~~ +!!! error TS1005: ',' expected. + ~~~~ +!!! error TS2304: Cannot find name 'from'. + ~~~~~~ +!!! error TS1005: ',' expected. + +==== tests/cases/compiler/t5.ts (3 errors) ==== + export { x as a, from "./t1" + ~ +!!! error TS2304: Cannot find name 'x'. + ~~~~ +!!! error TS2304: Cannot find name 'from'. + ~~~~~~ +!!! error TS1005: ',' expected. \ No newline at end of file diff --git a/tests/baselines/reference/unclosedExportClause01.js b/tests/baselines/reference/unclosedExportClause01.js new file mode 100644 index 00000000000..2316fca36ec --- /dev/null +++ b/tests/baselines/reference/unclosedExportClause01.js @@ -0,0 +1,28 @@ +//// [tests/cases/compiler/unclosedExportClause01.ts] //// + +//// [t1.ts] + +export var x = "x"; + +//// [t2.ts] +export { x, from "./t1" + +//// [t3.ts] +export { from "./t1" + +//// [t4.ts] +export { x as a from "./t1" + +//// [t5.ts] +export { x as a, from "./t1" + +//// [t1.js] +exports.x = "x"; +//// [t2.js] +"./t1"; +//// [t3.js] +"./t1"; +//// [t4.js] +"./t1"; +//// [t5.js] +"./t1"; diff --git a/tests/baselines/reference/unclosedExportClause02.errors.txt b/tests/baselines/reference/unclosedExportClause02.errors.txt new file mode 100644 index 00000000000..a4809e2c0c3 --- /dev/null +++ b/tests/baselines/reference/unclosedExportClause02.errors.txt @@ -0,0 +1,57 @@ +tests/cases/compiler/t2.ts(1,10): error TS2304: Cannot find name 'x'. +tests/cases/compiler/t2.ts(1,13): error TS2304: Cannot find name 'from'. +tests/cases/compiler/t2.ts(2,5): error TS1005: ',' expected. +tests/cases/compiler/t3.ts(1,10): error TS2304: Cannot find name 'from'. +tests/cases/compiler/t3.ts(2,5): error TS1005: ',' expected. +tests/cases/compiler/t4.ts(1,10): error TS2304: Cannot find name 'x'. +tests/cases/compiler/t4.ts(1,17): error TS1005: ',' expected. +tests/cases/compiler/t4.ts(1,17): error TS2304: Cannot find name 'from'. +tests/cases/compiler/t4.ts(2,5): error TS1005: ',' expected. +tests/cases/compiler/t5.ts(1,10): error TS2304: Cannot find name 'x'. +tests/cases/compiler/t5.ts(1,18): error TS2304: Cannot find name 'from'. +tests/cases/compiler/t5.ts(2,5): error TS1005: ',' expected. + + +==== tests/cases/compiler/t1.ts (0 errors) ==== + + export var x = "x"; + +==== tests/cases/compiler/t2.ts (3 errors) ==== + export { x, from + ~ +!!! error TS2304: Cannot find name 'x'. + ~~~~ +!!! error TS2304: Cannot find name 'from'. + "./t1"; + ~~~~~~ +!!! error TS1005: ',' expected. + +==== tests/cases/compiler/t3.ts (2 errors) ==== + export { from + ~~~~ +!!! error TS2304: Cannot find name 'from'. + "./t1"; + ~~~~~~ +!!! error TS1005: ',' expected. + +==== tests/cases/compiler/t4.ts (4 errors) ==== + export { x as a from + ~ +!!! error TS2304: Cannot find name 'x'. + ~~~~ +!!! error TS1005: ',' expected. + ~~~~ +!!! error TS2304: Cannot find name 'from'. + "./t1"; + ~~~~~~ +!!! error TS1005: ',' expected. + +==== tests/cases/compiler/t5.ts (3 errors) ==== + export { x as a, from + ~ +!!! error TS2304: Cannot find name 'x'. + ~~~~ +!!! error TS2304: Cannot find name 'from'. + "./t1"; + ~~~~~~ +!!! error TS1005: ',' expected. \ No newline at end of file diff --git a/tests/baselines/reference/unclosedExportClause02.js b/tests/baselines/reference/unclosedExportClause02.js new file mode 100644 index 00000000000..964cc29899c --- /dev/null +++ b/tests/baselines/reference/unclosedExportClause02.js @@ -0,0 +1,32 @@ +//// [tests/cases/compiler/unclosedExportClause02.ts] //// + +//// [t1.ts] + +export var x = "x"; + +//// [t2.ts] +export { x, from + "./t1"; + +//// [t3.ts] +export { from + "./t1"; + +//// [t4.ts] +export { x as a from + "./t1"; + +//// [t5.ts] +export { x as a, from + "./t1"; + +//// [t1.js] +exports.x = "x"; +//// [t2.js] +"./t1"; +//// [t3.js] +"./t1"; +//// [t4.js] +"./t1"; +//// [t5.js] +"./t1"; From 69f93fe116b95acd0e4072fc9b2ebe0c17c0ee1f Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 10 Jul 2015 16:27:40 -0700 Subject: [PATCH 06/17] Added error recovery for missing 'from' keyword in an export declaration. --- src/compiler/parser.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 3635a88b608..c01e1036a0d 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -4345,6 +4345,10 @@ namespace ts { } } + function nextTokenIsStringLiteralOnSameLine() { + return !scanner.hasPrecedingLineBreak() && token === SyntaxKind.StringLiteral; + } + function nextTokenIsIdentifierOrStringLiteralOnSameLine() { nextToken(); return !scanner.hasPrecedingLineBreak() && (isIdentifier() || token === SyntaxKind.StringLiteral); @@ -5135,7 +5139,12 @@ namespace ts { } else { node.exportClause = parseNamedImportsOrExports(SyntaxKind.NamedExports); - if (parseOptional(SyntaxKind.FromKeyword)) { + + // It is not uncommon to accidentally omit the 'from' keyword. Additionally, in editing scenarios, + // the 'from' keyword be parsed as a named export when the export clause is unterminated (i.e. `export { from "moduleName";`) + // If we don't have a 'from' keyword, see if we have a string literal such that ASI won't take effect. + if (token === SyntaxKind.FromKeyword || lookAhead(nextTokenIsStringLiteralOnSameLine)) { + parseExpected(SyntaxKind.FromKeyword) node.moduleSpecifier = parseModuleSpecifier(); } } From 5c03b8ef122e374a3f4547149a1dd0961cf77852 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 10 Jul 2015 16:27:55 -0700 Subject: [PATCH 07/17] Updated baselines. --- .../unclosedExportClause01.errors.txt | 31 +++++++------------ .../reference/unclosedExportClause01.js | 10 +++--- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/tests/baselines/reference/unclosedExportClause01.errors.txt b/tests/baselines/reference/unclosedExportClause01.errors.txt index 22c38f4af8e..6590bc5cfed 100644 --- a/tests/baselines/reference/unclosedExportClause01.errors.txt +++ b/tests/baselines/reference/unclosedExportClause01.errors.txt @@ -1,14 +1,11 @@ -tests/cases/compiler/t2.ts(1,10): error TS2304: Cannot find name 'x'. -tests/cases/compiler/t2.ts(1,13): error TS2304: Cannot find name 'from'. +tests/cases/compiler/t2.ts(1,13): error TS2305: Module '"tests/cases/compiler/t1"' has no exported member 'from'. tests/cases/compiler/t2.ts(1,18): error TS1005: ',' expected. -tests/cases/compiler/t3.ts(1,10): error TS2304: Cannot find name 'from'. +tests/cases/compiler/t3.ts(1,10): error TS2305: Module '"tests/cases/compiler/t1"' has no exported member 'from'. tests/cases/compiler/t3.ts(1,15): error TS1005: ',' expected. -tests/cases/compiler/t4.ts(1,10): error TS2304: Cannot find name 'x'. tests/cases/compiler/t4.ts(1,17): error TS1005: ',' expected. -tests/cases/compiler/t4.ts(1,17): error TS2304: Cannot find name 'from'. +tests/cases/compiler/t4.ts(1,17): error TS2305: Module '"tests/cases/compiler/t1"' has no exported member 'from'. tests/cases/compiler/t4.ts(1,22): error TS1005: ',' expected. -tests/cases/compiler/t5.ts(1,10): error TS2304: Cannot find name 'x'. -tests/cases/compiler/t5.ts(1,18): error TS2304: Cannot find name 'from'. +tests/cases/compiler/t5.ts(1,18): error TS2305: Module '"tests/cases/compiler/t1"' has no exported member 'from'. tests/cases/compiler/t5.ts(1,23): error TS1005: ',' expected. @@ -16,38 +13,32 @@ tests/cases/compiler/t5.ts(1,23): error TS1005: ',' expected. export var x = "x"; -==== tests/cases/compiler/t2.ts (3 errors) ==== +==== tests/cases/compiler/t2.ts (2 errors) ==== export { x, from "./t1" - ~ -!!! error TS2304: Cannot find name 'x'. ~~~~ -!!! error TS2304: Cannot find name 'from'. +!!! error TS2305: Module '"tests/cases/compiler/t1"' has no exported member 'from'. ~~~~~~ !!! error TS1005: ',' expected. ==== tests/cases/compiler/t3.ts (2 errors) ==== export { from "./t1" ~~~~ -!!! error TS2304: Cannot find name 'from'. +!!! error TS2305: Module '"tests/cases/compiler/t1"' has no exported member 'from'. ~~~~~~ !!! error TS1005: ',' expected. -==== tests/cases/compiler/t4.ts (4 errors) ==== +==== tests/cases/compiler/t4.ts (3 errors) ==== export { x as a from "./t1" - ~ -!!! error TS2304: Cannot find name 'x'. ~~~~ !!! error TS1005: ',' expected. ~~~~ -!!! error TS2304: Cannot find name 'from'. +!!! error TS2305: Module '"tests/cases/compiler/t1"' has no exported member 'from'. ~~~~~~ !!! error TS1005: ',' expected. -==== tests/cases/compiler/t5.ts (3 errors) ==== +==== tests/cases/compiler/t5.ts (2 errors) ==== export { x as a, from "./t1" - ~ -!!! error TS2304: Cannot find name 'x'. ~~~~ -!!! error TS2304: Cannot find name 'from'. +!!! error TS2305: Module '"tests/cases/compiler/t1"' has no exported member 'from'. ~~~~~~ !!! error TS1005: ',' expected. \ No newline at end of file diff --git a/tests/baselines/reference/unclosedExportClause01.js b/tests/baselines/reference/unclosedExportClause01.js index 2316fca36ec..a683f8d987d 100644 --- a/tests/baselines/reference/unclosedExportClause01.js +++ b/tests/baselines/reference/unclosedExportClause01.js @@ -19,10 +19,12 @@ export { x as a, from "./t1" //// [t1.js] exports.x = "x"; //// [t2.js] -"./t1"; +var t1_1 = require("./t1"); +exports.x = t1_1.x; //// [t3.js] -"./t1"; //// [t4.js] -"./t1"; +var t1_1 = require("./t1"); +exports.a = t1_1.x; //// [t5.js] -"./t1"; +var t1_1 = require("./t1"); +exports.a = t1_1.x; From afec273e8746e7b34e93ff0febf5fb2ebf762403 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 10 Jul 2015 16:49:02 -0700 Subject: [PATCH 08/17] Clarify function name. --- src/services/services.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index b311898f38d..6e728784a54 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3103,7 +3103,7 @@ namespace ts { function isCompletionListBlocker(contextToken: Node): boolean { let start = new Date().getTime(); let result = isInStringOrRegularExpressionOrTemplateLiteral(contextToken) || - isIdentifierDefinitionLocation(contextToken) || + isSolelyIdentifierDefinitionLocation(contextToken) || isDotOfNumericLiteral(contextToken); log("getCompletionsAtPosition: isCompletionListBlocker: " + (new Date().getTime() - start)); return result; @@ -3368,7 +3368,10 @@ namespace ts { return false; } - function isIdentifierDefinitionLocation(contextToken: Node): boolean { + /** + * @returns true if we are certain that the currently edited location must define a new location; false otherwise. + */ + function isSolelyIdentifierDefinitionLocation(contextToken: Node): boolean { let containingNodeKind = contextToken.parent.kind; switch (contextToken.kind) { case SyntaxKind.CommaToken: From d394f25763f1b7e3ce56e62f9d5d5f92d04ea3b4 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 10 Jul 2015 17:20:47 -0700 Subject: [PATCH 09/17] Added test for completion in namespace imports. --- .../completionListInNamespaceImportName01.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 tests/cases/fourslash/completionListInNamespaceImportName01.ts diff --git a/tests/cases/fourslash/completionListInNamespaceImportName01.ts b/tests/cases/fourslash/completionListInNamespaceImportName01.ts new file mode 100644 index 00000000000..ac1c0e13e07 --- /dev/null +++ b/tests/cases/fourslash/completionListInNamespaceImportName01.ts @@ -0,0 +1,11 @@ +/// + +// @Filename: m1.ts +////export var foo: number = 1; + +// @Filename: m2.ts +////import * as /**/ from "m1" + +goTo.marker(); +verify.completionListIsEmpty(); +verify.completionListAllowsNewIdentifier(); \ No newline at end of file From 49af6605953077b97d33838b7511097c90dc3d9c Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 10 Jul 2015 17:23:11 -0700 Subject: [PATCH 10/17] Account for export/import specifier renames and namespace import names in completion. --- src/services/services.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/services/services.ts b/src/services/services.ts index 6e728784a54..bff222f388b 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3428,6 +3428,11 @@ namespace ts { case SyntaxKind.ProtectedKeyword: return containingNodeKind === SyntaxKind.Parameter; + case SyntaxKind.AsKeyword: + containingNodeKind === SyntaxKind.ImportSpecifier || + containingNodeKind === SyntaxKind.ExportSpecifier || + containingNodeKind === SyntaxKind.NamespaceImport; + case SyntaxKind.ClassKeyword: case SyntaxKind.EnumKeyword: case SyntaxKind.InterfaceKeyword: From 55b39d45f640dfde8329f5369156a13a82d06d74 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 10 Jul 2015 17:33:56 -0700 Subject: [PATCH 11/17] Add analogous export test. --- .../fourslash/completionListInExportClause03.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 tests/cases/fourslash/completionListInExportClause03.ts diff --git a/tests/cases/fourslash/completionListInExportClause03.ts b/tests/cases/fourslash/completionListInExportClause03.ts new file mode 100644 index 00000000000..426c85d02ea --- /dev/null +++ b/tests/cases/fourslash/completionListInExportClause03.ts @@ -0,0 +1,16 @@ +/// + +////declare module "M1" { +//// export var abc: number; +//// export var def: string; +////} +//// +////declare module "M2" { +//// export { abc/**/ } from "M1"; +////} + +// Ensure we don't filter out the current item. +goTo.marker(); +verify.completionListContains("abc"); +verify.completionListContains("def"); +verify.not.completionListAllowsNewIdentifier(); \ No newline at end of file From 616d84e4a09e9cb1ee9869106f66410bc86390f7 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 10 Jul 2015 17:42:33 -0700 Subject: [PATCH 12/17] Fix merge mistake. --- src/services/services.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 1fe2b784a2d..1c27fe500ec 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3477,10 +3477,6 @@ namespace ts { function filterModuleExports(exports: Symbol[], namedImportsOrExports: NamedImportsOrExports): Symbol[] { let exisingImports: Map = {}; - if (!importDeclaration.importClause) { - return exports; - } - for (let element of namedImportsOrExports.elements) { // If this is the current item we are editing right now, do not filter it out if (element.getStart() <= position && position <= element.getEnd()) { From 48ca1c49c45147443052a9a97d77b735c676d822 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 10 Jul 2015 17:55:04 -0700 Subject: [PATCH 13/17] Fixed conversion from forEach to for-of --- src/services/services.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 1c27fe500ec..6c5deec9cd3 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3479,8 +3479,8 @@ namespace ts { for (let element of namedImportsOrExports.elements) { // If this is the current item we are editing right now, do not filter it out - if (element.getStart() <= position && position <= element.getEnd()) { - return; + if (element.getStart() <= position && poition <= element.getEnd()) { + continue; } let name = element.propertyName || element.name; From a6d17d82c2befda41ea742333759dbffa256d88b Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 10 Jul 2015 17:58:49 -0700 Subject: [PATCH 14/17] Pick your poition. --- src/services/services.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/services.ts b/src/services/services.ts index 6c5deec9cd3..dff8e46e2b5 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3479,7 +3479,7 @@ namespace ts { for (let element of namedImportsOrExports.elements) { // If this is the current item we are editing right now, do not filter it out - if (element.getStart() <= position && poition <= element.getEnd()) { + if (element.getStart() <= position && position <= element.getEnd()) { continue; } From 30c6947ae3ad737bf89ec52e5de2a2e0080fb4e2 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Mon, 13 Jul 2015 17:55:48 -0700 Subject: [PATCH 15/17] Addressed CR feedback. --- src/compiler/parser.ts | 8 ++----- src/services/services.ts | 45 ++++++++++++++++++++++++++++------------ 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 986967edb45..8c220604e89 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -4345,10 +4345,6 @@ namespace ts { } } - function nextTokenIsStringLiteralOnSameLine() { - return !scanner.hasPrecedingLineBreak() && token === SyntaxKind.StringLiteral; - } - function nextTokenIsIdentifierOrStringLiteralOnSameLine() { nextToken(); return !scanner.hasPrecedingLineBreak() && (isIdentifier() || token === SyntaxKind.StringLiteral); @@ -5141,9 +5137,9 @@ namespace ts { node.exportClause = parseNamedImportsOrExports(SyntaxKind.NamedExports); // It is not uncommon to accidentally omit the 'from' keyword. Additionally, in editing scenarios, - // the 'from' keyword be parsed as a named export when the export clause is unterminated (i.e. `export { from "moduleName";`) + // the 'from' keyword can be parsed as a named export when the export clause is unterminated (i.e. `export { from "moduleName";`) // If we don't have a 'from' keyword, see if we have a string literal such that ASI won't take effect. - if (token === SyntaxKind.FromKeyword || lookAhead(nextTokenIsStringLiteralOnSameLine)) { + if (token === SyntaxKind.FromKeyword || (token === SyntaxKind.StringLiteral && !scanner.hasPrecedingLineBreak())) { parseExpected(SyntaxKind.FromKeyword) node.moduleSpecifier = parseModuleSpecifier(); } diff --git a/src/services/services.ts b/src/services/services.ts index dff8e46e2b5..0229a356b97 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3255,11 +3255,6 @@ namespace ts { * @returns true if 'symbols' was successfully populated; false otherwise. */ function tryGetImportOrExportClauseCompletionSymbols(namedImportsOrExports: NamedImportsOrExports): boolean { - // cursor is in import clause - // try to show exported member for imported module - isMemberCompletion = true; - isNewIdentifierLocation = false; - let declarationKind = namedImportsOrExports.kind === SyntaxKind.NamedImports ? SyntaxKind.ImportDeclaration : SyntaxKind.ExportDeclaration; @@ -3270,13 +3265,16 @@ namespace ts { return false; } + isMemberCompletion = true; + isNewIdentifierLocation = false; + let exports: Symbol[]; let moduleSpecifierSymbol = typeChecker.getSymbolAtLocation(importOrExportDeclaration.moduleSpecifier); if (moduleSpecifierSymbol) { exports = typeChecker.getExportsOfModule(moduleSpecifierSymbol); } - symbols = exports ? filterModuleExports(exports, namedImportsOrExports) : emptyArray; + symbols = exports ? filterNamedImportOrExportCompletionItems(exports, namedImportsOrExports.elements) : emptyArray; return true; } @@ -3474,26 +3472,41 @@ namespace ts { return false; } - function filterModuleExports(exports: Symbol[], namedImportsOrExports: NamedImportsOrExports): Symbol[] { - let exisingImports: Map = {}; + /** + * Filters out completion suggestions for named imports or exports. + * + * @param exportsOfModule The list of symbols which a module exposes. + * @param namedImportsOrExports The list of existing import/export specifiers in the import/export clause. + * + * @returns Symbols to be suggested at an import/export clause, barring those whose named imports/exports + * do not occur at the current position and have not otherwise been typed. + */ + function filterNamedImportOrExportCompletionItems(exportsOfModule: Symbol[], namedImportsOrExports: ImportOrExportSpecifier[]): Symbol[] { + let exisingImportsOrExports: Map = {}; - for (let element of namedImportsOrExports.elements) { + for (let element of namedImportsOrExports) { // If this is the current item we are editing right now, do not filter it out if (element.getStart() <= position && position <= element.getEnd()) { continue; } let name = element.propertyName || element.name; - exisingImports[name.text] = true; + exisingImportsOrExports[name.text] = true; } - if (isEmpty(exisingImports)) { - return exports; + if (isEmpty(exisingImportsOrExports)) { + return exportsOfModule; } - return filter(exports, e => !lookUp(exisingImports, e.name)); + return filter(exportsOfModule, e => !lookUp(exisingImportsOrExports, e.name)); } + /** + * Filters out completion suggestions for named imports or exports. + * + * @returns Symbols to be suggested in an object binding pattern or object literal expression, barring those whose declarations + * do not occur at the current position and have not otherwise been typed. + */ function filterObjectMembersList(contextualMemberSymbols: Symbol[], existingMembers: Declaration[]): Symbol[] { if (!existingMembers || existingMembers.length === 0) { return contextualMemberSymbols; @@ -3538,6 +3551,12 @@ namespace ts { return filteredMembers; } + /** + * Filters out completion suggestions from 'symbols' according to existing JSX attributes. + * + * @returns Symbols to be suggested in a JSX element, barring those whose attributes + * do not occur at the current position and have not otherwise been typed. + */ function filterJsxAttributes(attributes: NodeArray, symbols: Symbol[]): Symbol[] { let seenNames: Map = {}; for (let attr of attributes) { From 4f7c62482a1ca8e01f38c468d60ccd77bfc51833 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Mon, 13 Jul 2015 17:59:42 -0700 Subject: [PATCH 16/17] Just use 'filter' --- src/services/services.ts | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 0229a356b97..13fba85d93f 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3541,14 +3541,7 @@ namespace ts { existingMemberNames[existingName] = true; } - let filteredMembers: Symbol[] = []; - forEach(contextualMemberSymbols, s => { - if (!existingMemberNames[s.name]) { - filteredMembers.push(s); - } - }); - - return filteredMembers; + return filter(contextualMemberSymbols, m => !lookUp(existingMemberNames, m.name)); } /** @@ -3569,13 +3562,8 @@ namespace ts { seenNames[(attr).name.text] = true; } } - let result: Symbol[] = []; - for (let sym of symbols) { - if (!seenNames[sym.name]) { - result.push(sym); - } - } - return result; + + return filter(symbols, a => !lookUp(seenNames, a.name)); } } From 076028c57dbafdfab8c561823c22168ca39274ff Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Mon, 13 Jul 2015 18:03:06 -0700 Subject: [PATCH 17/17] Changed order of parameters. --- src/services/services.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 13fba85d93f..eb778bce0c7 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3034,7 +3034,7 @@ namespace ts { attrsType = typeChecker.getJsxElementAttributesType(jsxContainer); if (attrsType) { - symbols = filterJsxAttributes((jsxContainer).attributes, typeChecker.getPropertiesOfType(attrsType)); + symbols = filterJsxAttributes(typeChecker.getPropertiesOfType(attrsType), (jsxContainer).attributes); isMemberCompletion = true; isNewIdentifierLocation = false; return true; @@ -3550,7 +3550,7 @@ namespace ts { * @returns Symbols to be suggested in a JSX element, barring those whose attributes * do not occur at the current position and have not otherwise been typed. */ - function filterJsxAttributes(attributes: NodeArray, symbols: Symbol[]): Symbol[] { + function filterJsxAttributes(symbols: Symbol[], attributes: NodeArray): Symbol[] { let seenNames: Map = {}; for (let attr of attributes) { // If this is the current item we are editing right now, do not filter it out