From cbc112a7617528c9449ba3c4ababacdb4baa725a Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Mon, 21 Mar 2016 08:26:56 -0700 Subject: [PATCH 01/72] Support this.prop = expr; assignments as declarations for ES6 JS classes --- src/compiler/binder.ts | 8 ++++++ src/compiler/checker.ts | 5 ++++ tests/cases/fourslash/javaScriptClass1.ts | 32 +++++++++++++++++++++++ tests/cases/fourslash/javaScriptClass2.ts | 22 ++++++++++++++++ tests/cases/fourslash/javaScriptClass3.ts | 24 +++++++++++++++++ 5 files changed, 91 insertions(+) create mode 100644 tests/cases/fourslash/javaScriptClass1.ts create mode 100644 tests/cases/fourslash/javaScriptClass2.ts create mode 100644 tests/cases/fourslash/javaScriptClass3.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 6957be484f3..dc0ff55b89a 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1478,6 +1478,14 @@ namespace ts { // It's acceptable for multiple 'this' assignments of the same identifier to occur declareSymbol(container.symbol.members, container.symbol, node, SymbolFlags.Property, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property); } + else if (container.kind === SyntaxKind.Constructor && isInJavaScriptFile(node)) { + // this.foo assignment in a JavaScript class + // Bind this property to the containing class + const saveContainer = container; + container = container.parent; + bindPropertyOrMethodOrAccessor(node, SymbolFlags.Property, SymbolFlags.None); + container = saveContainer; + } } function bindPrototypePropertyAssignment(node: BinaryExpression) { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 27b08397205..63d2afe72eb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8037,6 +8037,11 @@ namespace ts { const binaryExpression = node.parent; const operator = binaryExpression.operatorToken.kind; if (operator >= SyntaxKind.FirstAssignment && operator <= SyntaxKind.LastAssignment) { + // Don't do this for special property assignments to avoid circularity + if (getSpecialPropertyAssignmentKind(binaryExpression) !== SpecialPropertyAssignmentKind.None) { + return undefined; + } + // In an assignment expression, the right operand is contextually typed by the type of the left operand. if (node === binaryExpression.right) { return checkExpression(binaryExpression.left); diff --git a/tests/cases/fourslash/javaScriptClass1.ts b/tests/cases/fourslash/javaScriptClass1.ts new file mode 100644 index 00000000000..117fc5be9be --- /dev/null +++ b/tests/cases/fourslash/javaScriptClass1.ts @@ -0,0 +1,32 @@ +/// + +// Classes have their shape inferred from assignments +// to properties of 'this' in the constructor + +// @allowNonTsExtensions: true +// @Filename: Foo.js +//// class Foo { +//// constructor() { +//// this.bar = 'world'; +//// this.thing = 42; +//// this.union = 'foo'; +//// this.union = 100; +//// } +//// } +//// var x = new Foo(); +//// x/**/ + + +goTo.marker(); +edit.insert('.'); +verify.completionListContains("bar", /*displayText*/ undefined, /*documentation*/ undefined, "property"); +verify.completionListContains("thing", /*displayText*/ undefined, /*documentation*/ undefined, "property"); +verify.completionListContains("union", /*displayText*/ undefined, /*documentation*/ undefined, "property"); + +edit.insert('bar.'); +verify.completionListContains("substr", /*displayText*/ undefined, /*documentation*/ undefined, "method"); + +edit.backspace('bar.'.length); + +edit.insert('union.'); +verify.completionListContains("toString", /*displayText*/ undefined, /*documentation*/ undefined, "method"); \ No newline at end of file diff --git a/tests/cases/fourslash/javaScriptClass2.ts b/tests/cases/fourslash/javaScriptClass2.ts new file mode 100644 index 00000000000..d6daa320c5a --- /dev/null +++ b/tests/cases/fourslash/javaScriptClass2.ts @@ -0,0 +1,22 @@ +/// + +// In an inferred class, we can rename successfully + +// @allowNonTsExtensions: true +// @Filename: Foo.js +//// class Foo { +//// constructor() { +//// this.[|union|] = 'foo'; +//// this./*1*/[|union|] = 100; +//// } +//// method() { return this./*2*/[|union|]; } +//// } +//// var x = new Foo(); +//// x./*3*/[|union|]; + +goTo.marker('1'); +verify.renameLocations(/*findInStrings*/false, /*findInComments*/false); +goTo.marker('2'); +verify.renameLocations(/*findInStrings*/false, /*findInComments*/false); +goTo.marker('3'); +verify.renameLocations(/*findInStrings*/false, /*findInComments*/false); diff --git a/tests/cases/fourslash/javaScriptClass3.ts b/tests/cases/fourslash/javaScriptClass3.ts new file mode 100644 index 00000000000..47004d53b04 --- /dev/null +++ b/tests/cases/fourslash/javaScriptClass3.ts @@ -0,0 +1,24 @@ +/// + +// In an inferred class, we can to-to-def successfully + +// @allowNonTsExtensions: true +// @Filename: Foo.js +//// class Foo { +//// constructor() { +//// /*dst1*/this.alpha = 10; +//// /*dst2*/this.beta = 'gamma'; +//// } +//// method() { return this.alpha; } +//// } +//// var x = new Foo(); +//// x.alpha/*src1*/; +//// x.beta/*src2*/; + +goTo.marker('src1'); +goTo.definition(); +verify.caretAtMarker('dst1'); + +goTo.marker('src2'); +goTo.definition(); +verify.caretAtMarker('dst2'); From c8cd748e4807172871764d7726beddcb9deec250 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Mon, 21 Mar 2016 09:23:33 -0700 Subject: [PATCH 02/72] Handle JSDoc tags on 'this' properties --- src/compiler/checker.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 63d2afe72eb..49efcc7dc0b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2937,6 +2937,14 @@ namespace ts { } // Handle module.exports = expr if (declaration.kind === SyntaxKind.BinaryExpression) { + // Use JS Doc type if present on parent expression statement + if (declaration.flags & NodeFlags.JavaScriptFile) { + const typeTag = getJSDocTypeTag(declaration.parent); + if (typeTag && typeTag.typeExpression) { + return links.type = getTypeFromTypeNode(typeTag.typeExpression.type); + } + } + return links.type = getUnionType(map(symbol.declarations, (decl: BinaryExpression) => checkExpressionCached(decl.right))); } if (declaration.kind === SyntaxKind.PropertyAccessExpression) { From 32b4e46c1d2979e43967345b08fd7e135ed1519c Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Mon, 21 Mar 2016 09:23:41 -0700 Subject: [PATCH 03/72] Update tests --- tests/cases/fourslash/javaScriptClass1.ts | 3 +-- tests/cases/fourslash/javaScriptClass4.ts | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 tests/cases/fourslash/javaScriptClass4.ts diff --git a/tests/cases/fourslash/javaScriptClass1.ts b/tests/cases/fourslash/javaScriptClass1.ts index 117fc5be9be..19f7a39b615 100644 --- a/tests/cases/fourslash/javaScriptClass1.ts +++ b/tests/cases/fourslash/javaScriptClass1.ts @@ -8,7 +8,7 @@ //// class Foo { //// constructor() { //// this.bar = 'world'; -//// this.thing = 42; +//// this.thing = () => 0; //// this.union = 'foo'; //// this.union = 100; //// } @@ -25,7 +25,6 @@ verify.completionListContains("union", /*displayText*/ undefined, /*documentatio edit.insert('bar.'); verify.completionListContains("substr", /*displayText*/ undefined, /*documentation*/ undefined, "method"); - edit.backspace('bar.'.length); edit.insert('union.'); diff --git a/tests/cases/fourslash/javaScriptClass4.ts b/tests/cases/fourslash/javaScriptClass4.ts new file mode 100644 index 00000000000..1148d5f320e --- /dev/null +++ b/tests/cases/fourslash/javaScriptClass4.ts @@ -0,0 +1,22 @@ +/// + +// Classes have their shape inferred from assignments +// to properties of 'this' in the constructor + +// @allowNonTsExtensions: true +// @Filename: Foo.js +//// class Foo { +//// constructor() { +//// /** +//// * @type {string} +//// */ +//// this.baz = null; +//// } +//// } +//// var x = new Foo(); +//// x/**/ + +goTo.marker(); +edit.insert('.baz.'); +verify.completionListContains("substr", /*displayText*/ undefined, /*documentation*/ undefined, "method"); + From a6642d68c92ce7ddd16e8da71a1f2eb909fde74d Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Wed, 27 Jul 2016 13:21:42 -0700 Subject: [PATCH 04/72] JSDoc understands string literal types Unfortunately, I didn't find a way to reuse the normal string literal type, so I had to extend the existing JSDoc type hierarchy. Otherwise, this feature is very simple. --- src/compiler/checker.ts | 2 ++ src/compiler/parser.ts | 9 ++++++++- src/compiler/types.ts | 5 +++++ tests/baselines/reference/jsdocStringLiteral.js | 16 ++++++++++++++++ .../reference/jsdocStringLiteral.symbols | 12 ++++++++++++ .../baselines/reference/jsdocStringLiteral.types | 14 ++++++++++++++ .../conformance/jsdoc/jsdocStringLiteral.ts | 12 ++++++++++++ 7 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/jsdocStringLiteral.js create mode 100644 tests/baselines/reference/jsdocStringLiteral.symbols create mode 100644 tests/baselines/reference/jsdocStringLiteral.types create mode 100644 tests/cases/conformance/jsdoc/jsdocStringLiteral.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6da456afcbc..321bfcc74a3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5326,6 +5326,8 @@ namespace ts { return getTypeFromThisTypeNode(node); case SyntaxKind.StringLiteralType: return getTypeFromStringLiteralTypeNode(node); + case SyntaxKind.JSDocStringLiteralType: + return getTypeFromStringLiteralTypeNode((node).stringLiteral); case SyntaxKind.TypeReference: case SyntaxKind.JSDocTypeReference: return getTypeFromTypeReference(node); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 774cea60e09..2e76093cde2 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -5860,9 +5860,10 @@ namespace ts { case SyntaxKind.SymbolKeyword: case SyntaxKind.VoidKeyword: return parseTokenNode(); + case SyntaxKind.StringLiteral: + return parseJSDocStringLiteralType(); } - // TODO (drosen): Parse string literal types in JSDoc as well. return parseJSDocTypeReference(); } @@ -6041,6 +6042,12 @@ namespace ts { return finishNode(result); } + function parseJSDocStringLiteralType(): JSDocStringLiteralType { + const result = createNode(SyntaxKind.JSDocStringLiteralType); + result.stringLiteral = parseStringLiteralTypeNode(); + return finishNode(result); + } + function parseJSDocUnknownOrNullableType(): JSDocUnknownType | JSDocNullableType { const pos = scanner.getStartPos(); // skip the ? diff --git a/src/compiler/types.ts b/src/compiler/types.ts index dc332b24567..17e8a25038d 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -346,6 +346,7 @@ namespace ts { JSDocTypedefTag, JSDocPropertyTag, JSDocTypeLiteral, + JSDocStringLiteralType, // Synthesized list SyntaxList, @@ -1491,6 +1492,10 @@ namespace ts { type: JSDocType; } + export interface JSDocStringLiteralType extends JSDocType { + stringLiteral: StringLiteralTypeNode; + } + export type JSDocTypeReferencingNode = JSDocThisType | JSDocConstructorType | JSDocVariadicType | JSDocOptionalType | JSDocNullableType | JSDocNonNullableType; // @kind(SyntaxKind.JSDocRecordMember) diff --git a/tests/baselines/reference/jsdocStringLiteral.js b/tests/baselines/reference/jsdocStringLiteral.js new file mode 100644 index 00000000000..f74faf3519e --- /dev/null +++ b/tests/baselines/reference/jsdocStringLiteral.js @@ -0,0 +1,16 @@ +//// [in.js] +/** + * @param {'literal'} input + */ +function f(input) { + return input + '.'; +} + + +//// [out.js] +/** + * @param {'literal'} input + */ +function f(input) { + return input + '.'; +} diff --git a/tests/baselines/reference/jsdocStringLiteral.symbols b/tests/baselines/reference/jsdocStringLiteral.symbols new file mode 100644 index 00000000000..0414efc24d9 --- /dev/null +++ b/tests/baselines/reference/jsdocStringLiteral.symbols @@ -0,0 +1,12 @@ +=== tests/cases/compiler/in.js === +/** + * @param {'literal'} input + */ +function f(input) { +>f : Symbol(f, Decl(in.js, 0, 0)) +>input : Symbol(input, Decl(in.js, 3, 11)) + + return input + '.'; +>input : Symbol(input, Decl(in.js, 3, 11)) +} + diff --git a/tests/baselines/reference/jsdocStringLiteral.types b/tests/baselines/reference/jsdocStringLiteral.types new file mode 100644 index 00000000000..c7b87857d18 --- /dev/null +++ b/tests/baselines/reference/jsdocStringLiteral.types @@ -0,0 +1,14 @@ +=== tests/cases/compiler/in.js === +/** + * @param {'literal'} input + */ +function f(input) { +>f : (input: "literal") => string +>input : "literal" + + return input + '.'; +>input + '.' : string +>input : "literal" +>'.' : string +} + diff --git a/tests/cases/conformance/jsdoc/jsdocStringLiteral.ts b/tests/cases/conformance/jsdoc/jsdocStringLiteral.ts new file mode 100644 index 00000000000..d2a0d6c644d --- /dev/null +++ b/tests/cases/conformance/jsdoc/jsdocStringLiteral.ts @@ -0,0 +1,12 @@ +// @allowJs: true +// @filename: in.js +// @out: out.js +/** + * @param {'literal'} p1 + * @param {"literal"} p2 + * @param {'literal' | 'other'} p3 + * @param {'literal' | number} p4 + */ +function f(p1, p2, p3, p4) { + return p1 + p2 + p3 + p4 + '.'; +} From 6fbd79b70954682f8a131a4899f8a227237e91f5 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Wed, 27 Jul 2016 13:25:16 -0700 Subject: [PATCH 05/72] Update baselines to be current --- .../baselines/reference/jsdocStringLiteral.js | 18 ++++++++----- .../reference/jsdocStringLiteral.symbols | 19 ++++++++++---- .../reference/jsdocStringLiteral.types | 26 ++++++++++++++----- 3 files changed, 45 insertions(+), 18 deletions(-) diff --git a/tests/baselines/reference/jsdocStringLiteral.js b/tests/baselines/reference/jsdocStringLiteral.js index f74faf3519e..e1210690f91 100644 --- a/tests/baselines/reference/jsdocStringLiteral.js +++ b/tests/baselines/reference/jsdocStringLiteral.js @@ -1,16 +1,22 @@ //// [in.js] /** - * @param {'literal'} input + * @param {'literal'} p1 + * @param {"literal"} p2 + * @param {'literal' | 'other'} p3 + * @param {'literal' | number} p4 */ -function f(input) { - return input + '.'; +function f(p1, p2, p3, p4) { + return p1 + p2 + p3 + p4 + '.'; } //// [out.js] /** - * @param {'literal'} input + * @param {'literal'} p1 + * @param {"literal"} p2 + * @param {'literal' | 'other'} p3 + * @param {'literal' | number} p4 */ -function f(input) { - return input + '.'; +function f(p1, p2, p3, p4) { + return p1 + p2 + p3 + p4 + '.'; } diff --git a/tests/baselines/reference/jsdocStringLiteral.symbols b/tests/baselines/reference/jsdocStringLiteral.symbols index 0414efc24d9..83f0f4b1e65 100644 --- a/tests/baselines/reference/jsdocStringLiteral.symbols +++ b/tests/baselines/reference/jsdocStringLiteral.symbols @@ -1,12 +1,21 @@ === tests/cases/compiler/in.js === /** - * @param {'literal'} input + * @param {'literal'} p1 + * @param {"literal"} p2 + * @param {'literal' | 'other'} p3 + * @param {'literal' | number} p4 */ -function f(input) { +function f(p1, p2, p3, p4) { >f : Symbol(f, Decl(in.js, 0, 0)) ->input : Symbol(input, Decl(in.js, 3, 11)) +>p1 : Symbol(p1, Decl(in.js, 6, 11)) +>p2 : Symbol(p2, Decl(in.js, 6, 14)) +>p3 : Symbol(p3, Decl(in.js, 6, 18)) +>p4 : Symbol(p4, Decl(in.js, 6, 22)) - return input + '.'; ->input : Symbol(input, Decl(in.js, 3, 11)) + return p1 + p2 + p3 + p4 + '.'; +>p1 : Symbol(p1, Decl(in.js, 6, 11)) +>p2 : Symbol(p2, Decl(in.js, 6, 14)) +>p3 : Symbol(p3, Decl(in.js, 6, 18)) +>p4 : Symbol(p4, Decl(in.js, 6, 22)) } diff --git a/tests/baselines/reference/jsdocStringLiteral.types b/tests/baselines/reference/jsdocStringLiteral.types index c7b87857d18..834ec65b916 100644 --- a/tests/baselines/reference/jsdocStringLiteral.types +++ b/tests/baselines/reference/jsdocStringLiteral.types @@ -1,14 +1,26 @@ === tests/cases/compiler/in.js === /** - * @param {'literal'} input + * @param {'literal'} p1 + * @param {"literal"} p2 + * @param {'literal' | 'other'} p3 + * @param {'literal' | number} p4 */ -function f(input) { ->f : (input: "literal") => string ->input : "literal" +function f(p1, p2, p3, p4) { +>f : (p1: "literal", p2: "literal", p3: "literal" | "other", p4: "literal" | number) => string +>p1 : "literal" +>p2 : "literal" +>p3 : "literal" | "other" +>p4 : "literal" | number - return input + '.'; ->input + '.' : string ->input : "literal" + return p1 + p2 + p3 + p4 + '.'; +>p1 + p2 + p3 + p4 + '.' : string +>p1 + p2 + p3 + p4 : string +>p1 + p2 + p3 : string +>p1 + p2 : string +>p1 : "literal" +>p2 : "literal" +>p3 : "literal" | "other" +>p4 : "literal" | number >'.' : string } From 5c2ba01bebd81e4b0afb146a1fa4e5fe8d4b6aa9 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Wed, 27 Jul 2016 13:44:45 -0700 Subject: [PATCH 06/72] Update baseline source location --- tests/baselines/reference/jsdocStringLiteral.symbols | 2 +- tests/baselines/reference/jsdocStringLiteral.types | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/baselines/reference/jsdocStringLiteral.symbols b/tests/baselines/reference/jsdocStringLiteral.symbols index 83f0f4b1e65..982a1536069 100644 --- a/tests/baselines/reference/jsdocStringLiteral.symbols +++ b/tests/baselines/reference/jsdocStringLiteral.symbols @@ -1,4 +1,4 @@ -=== tests/cases/compiler/in.js === +=== tests/cases/conformance/jsdoc/in.js === /** * @param {'literal'} p1 * @param {"literal"} p2 diff --git a/tests/baselines/reference/jsdocStringLiteral.types b/tests/baselines/reference/jsdocStringLiteral.types index 834ec65b916..0400416a259 100644 --- a/tests/baselines/reference/jsdocStringLiteral.types +++ b/tests/baselines/reference/jsdocStringLiteral.types @@ -1,4 +1,4 @@ -=== tests/cases/compiler/in.js === +=== tests/cases/conformance/jsdoc/in.js === /** * @param {'literal'} p1 * @param {"literal"} p2 From f8103b596091d717753ee8200f9f45a3c8ca6ef7 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Wed, 27 Jul 2016 15:58:13 -0700 Subject: [PATCH 07/72] Update LastJSDoc[Tag]Node --- src/compiler/types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 17e8a25038d..d5d5546a0cd 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -377,9 +377,9 @@ namespace ts { LastBinaryOperator = CaretEqualsToken, FirstNode = QualifiedName, FirstJSDocNode = JSDocTypeExpression, - LastJSDocNode = JSDocTypeLiteral, + LastJSDocNode = JSDocStringLiteralType, FirstJSDocTagNode = JSDocComment, - LastJSDocTagNode = JSDocTypeLiteral + LastJSDocTagNode = JSDocStringLiteralType } export const enum NodeFlags { From 9e962f5356a41d194907c883f5bc5d2bad1b5e5a Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 28 Jul 2016 09:15:09 -0700 Subject: [PATCH 08/72] null/undefined are allowed as index expressions `null` and `undefined` are not allowed with `--strictNullChecks` turned on. Previously, they were disallowed whether or not it was on. --- src/compiler/checker.ts | 5 +- .../reference/indexWithUndefinedAndNull.js | 22 +++++++++ .../indexWithUndefinedAndNull.symbols | 39 +++++++++++++++ .../reference/indexWithUndefinedAndNull.types | 47 +++++++++++++++++++ ...ndefinedAndNullStrictNullChecks.errors.txt | 40 ++++++++++++++++ ...dexWithUndefinedAndNullStrictNullChecks.js | 22 +++++++++ .../compiler/indexWithUndefinedAndNull.ts | 13 +++++ ...dexWithUndefinedAndNullStrictNullChecks.ts | 13 +++++ 8 files changed, 199 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/indexWithUndefinedAndNull.js create mode 100644 tests/baselines/reference/indexWithUndefinedAndNull.symbols create mode 100644 tests/baselines/reference/indexWithUndefinedAndNull.types create mode 100644 tests/baselines/reference/indexWithUndefinedAndNullStrictNullChecks.errors.txt create mode 100644 tests/baselines/reference/indexWithUndefinedAndNullStrictNullChecks.js create mode 100644 tests/cases/compiler/indexWithUndefinedAndNull.ts create mode 100644 tests/cases/compiler/indexWithUndefinedAndNullStrictNullChecks.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6da456afcbc..38079c1bea0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10394,10 +10394,11 @@ namespace ts { } // Check for compatible indexer types. - if (isTypeAnyOrAllConstituentTypesHaveKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbol)) { + const allowedOptionalFlags = strictNullChecks ? 0 : TypeFlags.Null | TypeFlags.Undefined; + if (isTypeAnyOrAllConstituentTypesHaveKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbol | allowedOptionalFlags)) { // Try to use a number indexer. - if (isTypeAnyOrAllConstituentTypesHaveKind(indexType, TypeFlags.NumberLike) || isForInVariableForNumericPropertyNames(node.argumentExpression)) { + if (isTypeAnyOrAllConstituentTypesHaveKind(indexType, TypeFlags.NumberLike | allowedOptionalFlags) || isForInVariableForNumericPropertyNames(node.argumentExpression)) { const numberIndexInfo = getIndexInfoOfType(objectType, IndexKind.Number); if (numberIndexInfo) { getNodeLinks(node).resolvedIndexInfo = numberIndexInfo; diff --git a/tests/baselines/reference/indexWithUndefinedAndNull.js b/tests/baselines/reference/indexWithUndefinedAndNull.js new file mode 100644 index 00000000000..f885323efae --- /dev/null +++ b/tests/baselines/reference/indexWithUndefinedAndNull.js @@ -0,0 +1,22 @@ +//// [indexWithUndefinedAndNull.ts] +interface N { + [n: number]: string; +} +interface S { + [s: string]: number; +} +let n: N; +let s: S; +let str: string = n[undefined]; +str = n[null]; +let num: number = s[undefined]; +num = s[null]; + + +//// [indexWithUndefinedAndNull.js] +var n; +var s; +var str = n[undefined]; +str = n[null]; +var num = s[undefined]; +num = s[null]; diff --git a/tests/baselines/reference/indexWithUndefinedAndNull.symbols b/tests/baselines/reference/indexWithUndefinedAndNull.symbols new file mode 100644 index 00000000000..15e8bbdbf41 --- /dev/null +++ b/tests/baselines/reference/indexWithUndefinedAndNull.symbols @@ -0,0 +1,39 @@ +=== tests/cases/compiler/indexWithUndefinedAndNull.ts === +interface N { +>N : Symbol(N, Decl(indexWithUndefinedAndNull.ts, 0, 0)) + + [n: number]: string; +>n : Symbol(n, Decl(indexWithUndefinedAndNull.ts, 1, 5)) +} +interface S { +>S : Symbol(S, Decl(indexWithUndefinedAndNull.ts, 2, 1)) + + [s: string]: number; +>s : Symbol(s, Decl(indexWithUndefinedAndNull.ts, 4, 5)) +} +let n: N; +>n : Symbol(n, Decl(indexWithUndefinedAndNull.ts, 6, 3)) +>N : Symbol(N, Decl(indexWithUndefinedAndNull.ts, 0, 0)) + +let s: S; +>s : Symbol(s, Decl(indexWithUndefinedAndNull.ts, 7, 3)) +>S : Symbol(S, Decl(indexWithUndefinedAndNull.ts, 2, 1)) + +let str: string = n[undefined]; +>str : Symbol(str, Decl(indexWithUndefinedAndNull.ts, 8, 3)) +>n : Symbol(n, Decl(indexWithUndefinedAndNull.ts, 6, 3)) +>undefined : Symbol(undefined) + +str = n[null]; +>str : Symbol(str, Decl(indexWithUndefinedAndNull.ts, 8, 3)) +>n : Symbol(n, Decl(indexWithUndefinedAndNull.ts, 6, 3)) + +let num: number = s[undefined]; +>num : Symbol(num, Decl(indexWithUndefinedAndNull.ts, 10, 3)) +>s : Symbol(s, Decl(indexWithUndefinedAndNull.ts, 7, 3)) +>undefined : Symbol(undefined) + +num = s[null]; +>num : Symbol(num, Decl(indexWithUndefinedAndNull.ts, 10, 3)) +>s : Symbol(s, Decl(indexWithUndefinedAndNull.ts, 7, 3)) + diff --git a/tests/baselines/reference/indexWithUndefinedAndNull.types b/tests/baselines/reference/indexWithUndefinedAndNull.types new file mode 100644 index 00000000000..07b6050503a --- /dev/null +++ b/tests/baselines/reference/indexWithUndefinedAndNull.types @@ -0,0 +1,47 @@ +=== tests/cases/compiler/indexWithUndefinedAndNull.ts === +interface N { +>N : N + + [n: number]: string; +>n : number +} +interface S { +>S : S + + [s: string]: number; +>s : string +} +let n: N; +>n : N +>N : N + +let s: S; +>s : S +>S : S + +let str: string = n[undefined]; +>str : string +>n[undefined] : string +>n : N +>undefined : undefined + +str = n[null]; +>str = n[null] : string +>str : string +>n[null] : string +>n : N +>null : null + +let num: number = s[undefined]; +>num : number +>s[undefined] : number +>s : S +>undefined : undefined + +num = s[null]; +>num = s[null] : number +>num : number +>s[null] : number +>s : S +>null : null + diff --git a/tests/baselines/reference/indexWithUndefinedAndNullStrictNullChecks.errors.txt b/tests/baselines/reference/indexWithUndefinedAndNullStrictNullChecks.errors.txt new file mode 100644 index 00000000000..a1fce75c54e --- /dev/null +++ b/tests/baselines/reference/indexWithUndefinedAndNullStrictNullChecks.errors.txt @@ -0,0 +1,40 @@ +tests/cases/compiler/indexWithUndefinedAndNullStrictNullChecks.ts(9,19): error TS2454: Variable 'n' is used before being assigned. +tests/cases/compiler/indexWithUndefinedAndNullStrictNullChecks.ts(9,19): error TS2342: An index expression argument must be of type 'string', 'number', 'symbol', or 'any'. +tests/cases/compiler/indexWithUndefinedAndNullStrictNullChecks.ts(10,7): error TS2454: Variable 'n' is used before being assigned. +tests/cases/compiler/indexWithUndefinedAndNullStrictNullChecks.ts(10,7): error TS2342: An index expression argument must be of type 'string', 'number', 'symbol', or 'any'. +tests/cases/compiler/indexWithUndefinedAndNullStrictNullChecks.ts(11,19): error TS2454: Variable 's' is used before being assigned. +tests/cases/compiler/indexWithUndefinedAndNullStrictNullChecks.ts(11,19): error TS2342: An index expression argument must be of type 'string', 'number', 'symbol', or 'any'. +tests/cases/compiler/indexWithUndefinedAndNullStrictNullChecks.ts(12,7): error TS2454: Variable 's' is used before being assigned. +tests/cases/compiler/indexWithUndefinedAndNullStrictNullChecks.ts(12,7): error TS2342: An index expression argument must be of type 'string', 'number', 'symbol', or 'any'. + + +==== tests/cases/compiler/indexWithUndefinedAndNullStrictNullChecks.ts (8 errors) ==== + interface N { + [n: number]: string; + } + interface S { + [s: string]: number; + } + let n: N; + let s: S; + let str: string = n[undefined]; + ~ +!!! error TS2454: Variable 'n' is used before being assigned. + ~~~~~~~~~~~~ +!!! error TS2342: An index expression argument must be of type 'string', 'number', 'symbol', or 'any'. + str = n[null]; + ~ +!!! error TS2454: Variable 'n' is used before being assigned. + ~~~~~~~ +!!! error TS2342: An index expression argument must be of type 'string', 'number', 'symbol', or 'any'. + let num: number = s[undefined]; + ~ +!!! error TS2454: Variable 's' is used before being assigned. + ~~~~~~~~~~~~ +!!! error TS2342: An index expression argument must be of type 'string', 'number', 'symbol', or 'any'. + num = s[null]; + ~ +!!! error TS2454: Variable 's' is used before being assigned. + ~~~~~~~ +!!! error TS2342: An index expression argument must be of type 'string', 'number', 'symbol', or 'any'. + \ No newline at end of file diff --git a/tests/baselines/reference/indexWithUndefinedAndNullStrictNullChecks.js b/tests/baselines/reference/indexWithUndefinedAndNullStrictNullChecks.js new file mode 100644 index 00000000000..9300b7c0549 --- /dev/null +++ b/tests/baselines/reference/indexWithUndefinedAndNullStrictNullChecks.js @@ -0,0 +1,22 @@ +//// [indexWithUndefinedAndNullStrictNullChecks.ts] +interface N { + [n: number]: string; +} +interface S { + [s: string]: number; +} +let n: N; +let s: S; +let str: string = n[undefined]; +str = n[null]; +let num: number = s[undefined]; +num = s[null]; + + +//// [indexWithUndefinedAndNullStrictNullChecks.js] +var n; +var s; +var str = n[undefined]; +str = n[null]; +var num = s[undefined]; +num = s[null]; diff --git a/tests/cases/compiler/indexWithUndefinedAndNull.ts b/tests/cases/compiler/indexWithUndefinedAndNull.ts new file mode 100644 index 00000000000..2aeb2ee0b1d --- /dev/null +++ b/tests/cases/compiler/indexWithUndefinedAndNull.ts @@ -0,0 +1,13 @@ +// @strictNullChecks: false +interface N { + [n: number]: string; +} +interface S { + [s: string]: number; +} +let n: N; +let s: S; +let str: string = n[undefined]; +str = n[null]; +let num: number = s[undefined]; +num = s[null]; diff --git a/tests/cases/compiler/indexWithUndefinedAndNullStrictNullChecks.ts b/tests/cases/compiler/indexWithUndefinedAndNullStrictNullChecks.ts new file mode 100644 index 00000000000..f8fe0a323c6 --- /dev/null +++ b/tests/cases/compiler/indexWithUndefinedAndNullStrictNullChecks.ts @@ -0,0 +1,13 @@ +// @strictNullChecks: true +interface N { + [n: number]: string; +} +interface S { + [s: string]: number; +} +let n: N; +let s: S; +let str: string = n[undefined]; +str = n[null]; +let num: number = s[undefined]; +num = s[null]; From e2709d801dd3f48f8e4d718ecdcded2bb5dd66f6 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 28 Jul 2016 09:19:25 -0700 Subject: [PATCH 09/72] Use correct nullable terminology --- src/compiler/checker.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 38079c1bea0..c8b03876c46 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10394,11 +10394,11 @@ namespace ts { } // Check for compatible indexer types. - const allowedOptionalFlags = strictNullChecks ? 0 : TypeFlags.Null | TypeFlags.Undefined; - if (isTypeAnyOrAllConstituentTypesHaveKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbol | allowedOptionalFlags)) { + const allowedNullableFlags = strictNullChecks ? 0 : TypeFlags.Nullable; + if (isTypeAnyOrAllConstituentTypesHaveKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbol | allowedNullableFlags)) { // Try to use a number indexer. - if (isTypeAnyOrAllConstituentTypesHaveKind(indexType, TypeFlags.NumberLike | allowedOptionalFlags) || isForInVariableForNumericPropertyNames(node.argumentExpression)) { + if (isTypeAnyOrAllConstituentTypesHaveKind(indexType, TypeFlags.NumberLike | allowedNullableFlags) || isForInVariableForNumericPropertyNames(node.argumentExpression)) { const numberIndexInfo = getIndexInfoOfType(objectType, IndexKind.Number); if (numberIndexInfo) { getNodeLinks(node).resolvedIndexInfo = numberIndexInfo; From e8066158eba22aff78d6899d0bc0dc67b20404d4 Mon Sep 17 00:00:00 2001 From: Yuichi Nukiyama Date: Mon, 1 Aug 2016 15:15:11 +0900 Subject: [PATCH 10/72] change error message for unused parameter property fix --- src/compiler/checker.ts | 2 +- src/compiler/diagnosticMessages.json | 4 ++++ .../unusedParameterProperty1.errors.txt | 14 ++++++++++++++ .../reference/unusedParameterProperty1.js | 19 +++++++++++++++++++ .../unusedParameterProperty2.errors.txt | 14 ++++++++++++++ .../reference/unusedParameterProperty2.js | 19 +++++++++++++++++++ .../compiler/unusedParameterProperty1.ts | 9 +++++++++ .../compiler/unusedParameterProperty2.ts | 9 +++++++++ 8 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/unusedParameterProperty1.errors.txt create mode 100644 tests/baselines/reference/unusedParameterProperty1.js create mode 100644 tests/baselines/reference/unusedParameterProperty2.errors.txt create mode 100644 tests/baselines/reference/unusedParameterProperty2.js create mode 100644 tests/cases/compiler/unusedParameterProperty1.ts create mode 100644 tests/cases/compiler/unusedParameterProperty2.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7b85189b784..4b12e2e61f5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14967,7 +14967,7 @@ namespace ts { else if (member.kind === SyntaxKind.Constructor) { for (const parameter of (member).parameters) { if (!parameter.symbol.isReferenced && parameter.flags & NodeFlags.Private) { - error(parameter.name, Diagnostics._0_is_declared_but_never_used, parameter.symbol.name); + error(parameter.name, Diagnostics.Property_0_is_declared_but_never_used, parameter.symbol.name); } } } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 8126d5c605e..c1eead94f14 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2828,6 +2828,10 @@ "category": "Message", "code": 6137 }, + "Property '{0}' is declared but never used.": { + "category": "Error", + "code": 6138 + }, "Variable '{0}' implicitly has an '{1}' type.": { "category": "Error", "code": 7005 diff --git a/tests/baselines/reference/unusedParameterProperty1.errors.txt b/tests/baselines/reference/unusedParameterProperty1.errors.txt new file mode 100644 index 00000000000..0581199313f --- /dev/null +++ b/tests/baselines/reference/unusedParameterProperty1.errors.txt @@ -0,0 +1,14 @@ +tests/cases/compiler/unusedParameterProperty1.ts(3,25): error TS6138: Property 'used' is declared but never used. + + +==== tests/cases/compiler/unusedParameterProperty1.ts (1 errors) ==== + + class A { + constructor(private used: string) { + ~~~~ +!!! error TS6138: Property 'used' is declared but never used. + let foge = used; + foge += ""; + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/unusedParameterProperty1.js b/tests/baselines/reference/unusedParameterProperty1.js new file mode 100644 index 00000000000..d0b608a6d87 --- /dev/null +++ b/tests/baselines/reference/unusedParameterProperty1.js @@ -0,0 +1,19 @@ +//// [unusedParameterProperty1.ts] + +class A { + constructor(private used: string) { + let foge = used; + foge += ""; + } +} + + +//// [unusedParameterProperty1.js] +var A = (function () { + function A(used) { + this.used = used; + var foge = used; + foge += ""; + } + return A; +}()); diff --git a/tests/baselines/reference/unusedParameterProperty2.errors.txt b/tests/baselines/reference/unusedParameterProperty2.errors.txt new file mode 100644 index 00000000000..cb3b3e95556 --- /dev/null +++ b/tests/baselines/reference/unusedParameterProperty2.errors.txt @@ -0,0 +1,14 @@ +tests/cases/compiler/unusedParameterProperty2.ts(3,25): error TS6138: Property 'used' is declared but never used. + + +==== tests/cases/compiler/unusedParameterProperty2.ts (1 errors) ==== + + class A { + constructor(private used) { + ~~~~ +!!! error TS6138: Property 'used' is declared but never used. + let foge = used; + foge += ""; + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/unusedParameterProperty2.js b/tests/baselines/reference/unusedParameterProperty2.js new file mode 100644 index 00000000000..2bb04fde088 --- /dev/null +++ b/tests/baselines/reference/unusedParameterProperty2.js @@ -0,0 +1,19 @@ +//// [unusedParameterProperty2.ts] + +class A { + constructor(private used) { + let foge = used; + foge += ""; + } +} + + +//// [unusedParameterProperty2.js] +var A = (function () { + function A(used) { + this.used = used; + var foge = used; + foge += ""; + } + return A; +}()); diff --git a/tests/cases/compiler/unusedParameterProperty1.ts b/tests/cases/compiler/unusedParameterProperty1.ts new file mode 100644 index 00000000000..61c4374c60a --- /dev/null +++ b/tests/cases/compiler/unusedParameterProperty1.ts @@ -0,0 +1,9 @@ +//@noUnusedLocals:true +//@noUnusedParameters:true + +class A { + constructor(private used: string) { + let foge = used; + foge += ""; + } +} diff --git a/tests/cases/compiler/unusedParameterProperty2.ts b/tests/cases/compiler/unusedParameterProperty2.ts new file mode 100644 index 00000000000..b9e05fbc967 --- /dev/null +++ b/tests/cases/compiler/unusedParameterProperty2.ts @@ -0,0 +1,9 @@ +//@noUnusedLocals:true +//@noUnusedParameters:true + +class A { + constructor(private used) { + let foge = used; + foge += ""; + } +} From 8c01efba04a3ee3a1fa0aec6658afb5884b9e3a4 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Wed, 3 Aug 2016 10:33:10 -0700 Subject: [PATCH 11/72] Allow JS multiple declarations of ctor properties When a property is declared in the constructor and on the prototype of an ES6 class, the property's symbol is discarded in favour of the method's symbol. That because the usual use for this pattern is to bind an instance function: `this.m = this.m.bind(this)`. In this case the type you want really is the method's type. --- src/compiler/binder.ts | 60 +++++++++++-------- src/compiler/types.ts | 1 + .../reference/multipleDeclarations.js | 32 +++++++++- .../reference/multipleDeclarations.symbols | 47 ++++++++++++++- .../reference/multipleDeclarations.types | 57 +++++++++++++++++- .../conformance/salsa/multipleDeclarations.ts | 17 +++++- 6 files changed, 184 insertions(+), 30 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 8d8666a67ab..6f0def32702 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -298,8 +298,10 @@ namespace ts { const name = isDefaultExport && parent ? "default" : getDeclarationName(node); let symbol: Symbol; - if (name !== undefined) { - + if (name === undefined) { + symbol = createSymbol(SymbolFlags.None, "__missing"); + } + else { // Check and see if the symbol table already has a symbol with this name. If not, // create a new symbol with this name and add it to the table. Note that we don't // give the new symbol any flags *yet*. This ensures that it will not conflict @@ -326,34 +328,38 @@ namespace ts { classifiableNames[name] = name; } - if (symbol.flags & excludes) { - if (node.name) { - node.name.parent = node; + else if (symbol.flags & excludes) { + if (symbol.isDiscardable) { + // Javascript constructor-declared symbols can be discarded in favor of + // prototype symbols like methods. + symbol = symbolTable[name] = createSymbol(SymbolFlags.None, name); } - - // Report errors every position with duplicate declaration - // Report errors on previous encountered declarations - let message = symbol.flags & SymbolFlags.BlockScopedVariable - ? Diagnostics.Cannot_redeclare_block_scoped_variable_0 - : Diagnostics.Duplicate_identifier_0; - - forEach(symbol.declarations, declaration => { - if (declaration.flags & NodeFlags.Default) { - message = Diagnostics.A_module_cannot_have_multiple_default_exports; + else { + if (node.name) { + node.name.parent = node; } - }); - forEach(symbol.declarations, declaration => { - file.bindDiagnostics.push(createDiagnosticForNode(declaration.name || declaration, message, getDisplayName(declaration))); - }); - file.bindDiagnostics.push(createDiagnosticForNode(node.name || node, message, getDisplayName(node))); + // Report errors every position with duplicate declaration + // Report errors on previous encountered declarations + let message = symbol.flags & SymbolFlags.BlockScopedVariable + ? Diagnostics.Cannot_redeclare_block_scoped_variable_0 + : Diagnostics.Duplicate_identifier_0; - symbol = createSymbol(SymbolFlags.None, name); + forEach(symbol.declarations, declaration => { + if (declaration.flags & NodeFlags.Default) { + message = Diagnostics.A_module_cannot_have_multiple_default_exports; + } + }); + + forEach(symbol.declarations, declaration => { + file.bindDiagnostics.push(createDiagnosticForNode(declaration.name || declaration, message, getDisplayName(declaration))); + }); + file.bindDiagnostics.push(createDiagnosticForNode(node.name || node, message, getDisplayName(node))); + + symbol = createSymbol(SymbolFlags.None, name); + } } } - else { - symbol = createSymbol(SymbolFlags.None, "__missing"); - } addDeclarationToSymbol(symbol, node, includes); symbol.parent = parent; @@ -1967,7 +1973,7 @@ namespace ts { } function bindThisPropertyAssignment(node: BinaryExpression) { - // Declare a 'member' in case it turns out the container was an ES5 class or ES6 constructor + // Declare a 'member' if the container is an ES5 class or ES6 constructor let assignee: Node; if (container.kind === SyntaxKind.FunctionDeclaration || container.kind === SyntaxKind.FunctionExpression) { assignee = container; @@ -1980,7 +1986,9 @@ namespace ts { } assignee.symbol.members = assignee.symbol.members || {}; // It's acceptable for multiple 'this' assignments of the same identifier to occur - declareSymbol(assignee.symbol.members, assignee.symbol, node, SymbolFlags.Property, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property); + // AND it can be overwritten by subsequent method declarations + let symbol = declareSymbol(assignee.symbol.members, assignee.symbol, node, SymbolFlags.Property, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property); + symbol.isDiscardable = true; } function bindPrototypePropertyAssignment(node: BinaryExpression) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 072209aa496..4c08d7a487c 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2137,6 +2137,7 @@ namespace ts { /* @internal */ exportSymbol?: Symbol; // Exported symbol associated with this symbol /* @internal */ constEnumOnlyModule?: boolean; // True if module contains only const enums or other modules with only const enums /* @internal */ isReferenced?: boolean; // True if the symbol is referenced elsewhere + /* @internal */ isDiscardable?: boolean;// True if a Javascript class property can be overwritten by a method } /* @internal */ diff --git a/tests/baselines/reference/multipleDeclarations.js b/tests/baselines/reference/multipleDeclarations.js index 5f5ac29e909..e5aae416663 100644 --- a/tests/baselines/reference/multipleDeclarations.js +++ b/tests/baselines/reference/multipleDeclarations.js @@ -5,7 +5,22 @@ function C() { } C.prototype.m = function() { this.nothing(); -}; +} + +class X { + constructor() { + this.m = this.m.bind(this); + this.mistake = 'frankly, complete nonsense'; + } + m() { + } + mistake() { + } +} +let x = new X(); +X.prototype.mistake = false; +x.m(); +x.mistake; //// [output.js] @@ -15,3 +30,18 @@ function C() { C.prototype.m = function () { this.nothing(); }; +var X = (function () { + function X() { + this.m = this.m.bind(this); + this.mistake = 'frankly, complete nonsense'; + } + X.prototype.m = function () { + }; + X.prototype.mistake = function () { + }; + return X; +}()); +var x = new X(); +X.prototype.mistake = false; +x.m(); +x.mistake; diff --git a/tests/baselines/reference/multipleDeclarations.symbols b/tests/baselines/reference/multipleDeclarations.symbols index a256943dd50..7e4bcd53c8a 100644 --- a/tests/baselines/reference/multipleDeclarations.symbols +++ b/tests/baselines/reference/multipleDeclarations.symbols @@ -14,6 +14,51 @@ C.prototype.m = function() { this.nothing(); >this : Symbol(C, Decl(input.js, 0, 0)) +} -}; +class X { +>X : Symbol(X, Decl(input.js, 6, 1)) + + constructor() { + this.m = this.m.bind(this); +>this.m : Symbol(X.m, Decl(input.js, 12, 5)) +>this : Symbol(X, Decl(input.js, 6, 1)) +>m : Symbol(X.m, Decl(input.js, 9, 19)) +>this.m.bind : Symbol(Function.bind, Decl(lib.d.ts, --, --)) +>this.m : Symbol(X.m, Decl(input.js, 12, 5)) +>this : Symbol(X, Decl(input.js, 6, 1)) +>m : Symbol(X.m, Decl(input.js, 12, 5)) +>bind : Symbol(Function.bind, Decl(lib.d.ts, --, --)) +>this : Symbol(X, Decl(input.js, 6, 1)) + + this.mistake = 'frankly, complete nonsense'; +>this.mistake : Symbol(X.mistake, Decl(input.js, 14, 5)) +>this : Symbol(X, Decl(input.js, 6, 1)) +>mistake : Symbol(X.mistake, Decl(input.js, 10, 35)) + } + m() { +>m : Symbol(X.m, Decl(input.js, 12, 5)) + } + mistake() { +>mistake : Symbol(X.mistake, Decl(input.js, 14, 5)) + } +} +let x = new X(); +>x : Symbol(x, Decl(input.js, 18, 3)) +>X : Symbol(X, Decl(input.js, 6, 1)) + +X.prototype.mistake = false; +>X.prototype.mistake : Symbol(X.mistake, Decl(input.js, 14, 5)) +>X : Symbol(X, Decl(input.js, 6, 1)) +>prototype : Symbol(X.prototype) + +x.m(); +>x.m : Symbol(X.m, Decl(input.js, 12, 5)) +>x : Symbol(x, Decl(input.js, 18, 3)) +>m : Symbol(X.m, Decl(input.js, 12, 5)) + +x.mistake; +>x.mistake : Symbol(X.mistake, Decl(input.js, 14, 5)) +>x : Symbol(x, Decl(input.js, 18, 3)) +>mistake : Symbol(X.mistake, Decl(input.js, 14, 5)) diff --git a/tests/baselines/reference/multipleDeclarations.types b/tests/baselines/reference/multipleDeclarations.types index 7c0a3de70c9..2a7cac18205 100644 --- a/tests/baselines/reference/multipleDeclarations.types +++ b/tests/baselines/reference/multipleDeclarations.types @@ -24,6 +24,61 @@ C.prototype.m = function() { >this.nothing : any >this : { m: () => void; } >nothing : any +} -}; +class X { +>X : X + + constructor() { + this.m = this.m.bind(this); +>this.m = this.m.bind(this) : any +>this.m : () => void +>this : this +>m : () => void +>this.m.bind(this) : any +>this.m.bind : (this: Function, thisArg: any, ...argArray: any[]) => any +>this.m : () => void +>this : this +>m : () => void +>bind : (this: Function, thisArg: any, ...argArray: any[]) => any +>this : this + + this.mistake = 'frankly, complete nonsense'; +>this.mistake = 'frankly, complete nonsense' : string +>this.mistake : () => void +>this : this +>mistake : () => void +>'frankly, complete nonsense' : string + } + m() { +>m : () => void + } + mistake() { +>mistake : () => void + } +} +let x = new X(); +>x : X +>new X() : X +>X : typeof X + +X.prototype.mistake = false; +>X.prototype.mistake = false : boolean +>X.prototype.mistake : () => void +>X.prototype : X +>X : typeof X +>prototype : X +>mistake : () => void +>false : boolean + +x.m(); +>x.m() : void +>x.m : () => void +>x : X +>m : () => void + +x.mistake; +>x.mistake : () => void +>x : X +>mistake : () => void diff --git a/tests/cases/conformance/salsa/multipleDeclarations.ts b/tests/cases/conformance/salsa/multipleDeclarations.ts index 6899be2ec89..9ead1eeb0a5 100644 --- a/tests/cases/conformance/salsa/multipleDeclarations.ts +++ b/tests/cases/conformance/salsa/multipleDeclarations.ts @@ -7,4 +7,19 @@ function C() { } C.prototype.m = function() { this.nothing(); -}; +} + +class X { + constructor() { + this.m = this.m.bind(this); + this.mistake = 'frankly, complete nonsense'; + } + m() { + } + mistake() { + } +} +let x = new X(); +X.prototype.mistake = false; +x.m(); +x.mistake; From 72057500b5220b28e637a6a31acb86f4f4273393 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Wed, 3 Aug 2016 16:10:14 -0700 Subject: [PATCH 12/72] Test that declares conflicting method first --- .../conformance/salsa/multipleDeclarations.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/cases/conformance/salsa/multipleDeclarations.ts b/tests/cases/conformance/salsa/multipleDeclarations.ts index 9ead1eeb0a5..ba2e14e11e9 100644 --- a/tests/cases/conformance/salsa/multipleDeclarations.ts +++ b/tests/cases/conformance/salsa/multipleDeclarations.ts @@ -1,14 +1,12 @@ // @filename: input.js // @out: output.js // @allowJs: true - function C() { this.m = null; } C.prototype.m = function() { this.nothing(); } - class X { constructor() { this.m = this.m.bind(this); @@ -23,3 +21,17 @@ let x = new X(); X.prototype.mistake = false; x.m(); x.mistake; +class Y { + mistake() { + } + m() { + } + constructor() { + this.m = this.m.bind(this); + this.mistake = 'even more nonsense'; + } +} +Y.prototype.mistake = true; +let y = new Y(); +y.m(); +y.mistake(); From e5973b8daa325344cf55b4c2840dd235e5e4e4f4 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 4 Aug 2016 09:46:35 -0700 Subject: [PATCH 13/72] Add string-literal completion test for jsdoc --- .../fourslash/completionForStringLiteral4.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 tests/cases/fourslash/completionForStringLiteral4.ts diff --git a/tests/cases/fourslash/completionForStringLiteral4.ts b/tests/cases/fourslash/completionForStringLiteral4.ts new file mode 100644 index 00000000000..c240c125dbe --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteral4.ts @@ -0,0 +1,22 @@ +/// +// @allowJs: true +// @Filename: in.js +/////** I am documentation +//// * @param {'literal'} p1 +//// * @param {"literal"} p2 +//// * @param {'other1' | 'other2'} p3 +//// * @param {'literal' | number} p4 +//// */ +////function f(p1, p2, p3, p4) { +//// return p1 + p2 + p3 + p4 + '.'; +////} +////f/*1*/('literal', 'literal', "o/*2*/ther1", 12); + +goTo.marker('1'); +verify.quickInfoExists(); +verify.quickInfoIs('function f(p1: "literal", p2: "literal", p3: "other1" | "other2", p4: "literal" | number): string', 'I am documentation'); + +goTo.marker('2'); +verify.completionListContains("other1"); +verify.completionListContains("other2"); +verify.memberListCount(2); From 3c32478b8fd433b5bdf708b27aef04309a107a3d Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 4 Aug 2016 13:01:17 -0700 Subject: [PATCH 14/72] Support other (new) literal types in jsdoc --- src/compiler/checker.ts | 4 ++-- src/compiler/parser.ts | 13 ++++++---- src/compiler/types.ts | 10 ++++---- ...{jsdocStringLiteral.js => jsdocLiteral.js} | 10 ++++---- .../baselines/reference/jsdocLiteral.symbols | 24 +++++++++++++++++++ ...StringLiteral.types => jsdocLiteral.types} | 16 ++++++++----- .../reference/jsdocStringLiteral.symbols | 21 ---------------- ...{jsdocStringLiteral.ts => jsdocLiteral.ts} | 5 ++-- .../fourslash/completionForStringLiteral4.ts | 7 +++--- 9 files changed, 63 insertions(+), 47 deletions(-) rename tests/baselines/reference/{jsdocStringLiteral.js => jsdocLiteral.js} (55%) create mode 100644 tests/baselines/reference/jsdocLiteral.symbols rename tests/baselines/reference/{jsdocStringLiteral.types => jsdocLiteral.types} (54%) delete mode 100644 tests/baselines/reference/jsdocStringLiteral.symbols rename tests/cases/conformance/jsdoc/{jsdocStringLiteral.ts => jsdocLiteral.ts} (63%) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 01aeb7ca744..ddc188ace92 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5529,8 +5529,8 @@ namespace ts { return getTypeFromThisTypeNode(node); case SyntaxKind.LiteralType: return getTypeFromLiteralTypeNode(node); - case SyntaxKind.JSDocStringLiteralType: - return getTypeFromStringLiteralTypeNode((node).stringLiteral); + case SyntaxKind.JSDocLiteralType: + return getTypeFromLiteralTypeNode((node).literal); case SyntaxKind.TypeReference: case SyntaxKind.JSDocTypeReference: return getTypeFromTypeReference(node); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 9136105457e..07f013e4f31 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -417,6 +417,8 @@ namespace ts { case SyntaxKind.JSDocPropertyTag: return visitNode(cbNode, (node).typeExpression) || visitNode(cbNode, (node).name); + case SyntaxKind.JSDocLiteralType: + return visitNode(cbNode, (node).literal); } } @@ -5890,7 +5892,10 @@ namespace ts { case SyntaxKind.VoidKeyword: return parseTokenNode(); case SyntaxKind.StringLiteral: - return parseJSDocStringLiteralType(); + case SyntaxKind.NumericLiteral: + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + return parseJSDocLiteralType(); } return parseJSDocTypeReference(); @@ -6071,9 +6076,9 @@ namespace ts { return finishNode(result); } - function parseJSDocStringLiteralType(): JSDocStringLiteralType { - const result = createNode(SyntaxKind.JSDocStringLiteralType); - result.stringLiteral = parseStringLiteralTypeNode(); + function parseJSDocLiteralType(): JSDocLiteralType { + const result = createNode(SyntaxKind.JSDocLiteralType); + result.literal = parseLiteralTypeNode(); return finishNode(result); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 4f6806d11a5..45187b784b0 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -346,7 +346,7 @@ namespace ts { JSDocTypedefTag, JSDocPropertyTag, JSDocTypeLiteral, - JSDocStringLiteralType, + JSDocLiteralType, // Synthesized list SyntaxList, @@ -377,9 +377,9 @@ namespace ts { LastBinaryOperator = CaretEqualsToken, FirstNode = QualifiedName, FirstJSDocNode = JSDocTypeExpression, - LastJSDocNode = JSDocStringLiteralType, + LastJSDocNode = JSDocLiteralType, FirstJSDocTagNode = JSDocComment, - LastJSDocTagNode = JSDocStringLiteralType + LastJSDocTagNode = JSDocLiteralType } export const enum NodeFlags { @@ -1493,8 +1493,8 @@ namespace ts { type: JSDocType; } - export interface JSDocStringLiteralType extends JSDocType { - stringLiteral: StringLiteralTypeNode; + export interface JSDocLiteralType extends JSDocType { + literal: LiteralTypeNode; } export type JSDocTypeReferencingNode = JSDocThisType | JSDocConstructorType | JSDocVariadicType | JSDocOptionalType | JSDocNullableType | JSDocNonNullableType; diff --git a/tests/baselines/reference/jsdocStringLiteral.js b/tests/baselines/reference/jsdocLiteral.js similarity index 55% rename from tests/baselines/reference/jsdocStringLiteral.js rename to tests/baselines/reference/jsdocLiteral.js index e1210690f91..1a017160302 100644 --- a/tests/baselines/reference/jsdocStringLiteral.js +++ b/tests/baselines/reference/jsdocLiteral.js @@ -4,9 +4,10 @@ * @param {"literal"} p2 * @param {'literal' | 'other'} p3 * @param {'literal' | number} p4 + * @param {12 | true | 'str'} p5 */ -function f(p1, p2, p3, p4) { - return p1 + p2 + p3 + p4 + '.'; +function f(p1, p2, p3, p4, p5) { + return p1 + p2 + p3 + p4 + p5 + '.'; } @@ -16,7 +17,8 @@ function f(p1, p2, p3, p4) { * @param {"literal"} p2 * @param {'literal' | 'other'} p3 * @param {'literal' | number} p4 + * @param {12 | true | 'str'} p5 */ -function f(p1, p2, p3, p4) { - return p1 + p2 + p3 + p4 + '.'; +function f(p1, p2, p3, p4, p5) { + return p1 + p2 + p3 + p4 + p5 + '.'; } diff --git a/tests/baselines/reference/jsdocLiteral.symbols b/tests/baselines/reference/jsdocLiteral.symbols new file mode 100644 index 00000000000..9f01fe782aa --- /dev/null +++ b/tests/baselines/reference/jsdocLiteral.symbols @@ -0,0 +1,24 @@ +=== tests/cases/conformance/jsdoc/in.js === +/** + * @param {'literal'} p1 + * @param {"literal"} p2 + * @param {'literal' | 'other'} p3 + * @param {'literal' | number} p4 + * @param {12 | true | 'str'} p5 + */ +function f(p1, p2, p3, p4, p5) { +>f : Symbol(f, Decl(in.js, 0, 0)) +>p1 : Symbol(p1, Decl(in.js, 7, 11)) +>p2 : Symbol(p2, Decl(in.js, 7, 14)) +>p3 : Symbol(p3, Decl(in.js, 7, 18)) +>p4 : Symbol(p4, Decl(in.js, 7, 22)) +>p5 : Symbol(p5, Decl(in.js, 7, 26)) + + return p1 + p2 + p3 + p4 + p5 + '.'; +>p1 : Symbol(p1, Decl(in.js, 7, 11)) +>p2 : Symbol(p2, Decl(in.js, 7, 14)) +>p3 : Symbol(p3, Decl(in.js, 7, 18)) +>p4 : Symbol(p4, Decl(in.js, 7, 22)) +>p5 : Symbol(p5, Decl(in.js, 7, 26)) +} + diff --git a/tests/baselines/reference/jsdocStringLiteral.types b/tests/baselines/reference/jsdocLiteral.types similarity index 54% rename from tests/baselines/reference/jsdocStringLiteral.types rename to tests/baselines/reference/jsdocLiteral.types index 0400416a259..b6c9e522ac3 100644 --- a/tests/baselines/reference/jsdocStringLiteral.types +++ b/tests/baselines/reference/jsdocLiteral.types @@ -4,23 +4,27 @@ * @param {"literal"} p2 * @param {'literal' | 'other'} p3 * @param {'literal' | number} p4 + * @param {12 | true | 'str'} p5 */ -function f(p1, p2, p3, p4) { ->f : (p1: "literal", p2: "literal", p3: "literal" | "other", p4: "literal" | number) => string +function f(p1, p2, p3, p4, p5) { +>f : (p1: "literal", p2: "literal", p3: "literal" | "other", p4: number | "literal", p5: true | 12 | "str") => string >p1 : "literal" >p2 : "literal" >p3 : "literal" | "other" ->p4 : "literal" | number +>p4 : number | "literal" +>p5 : true | 12 | "str" - return p1 + p2 + p3 + p4 + '.'; ->p1 + p2 + p3 + p4 + '.' : string + return p1 + p2 + p3 + p4 + p5 + '.'; +>p1 + p2 + p3 + p4 + p5 + '.' : string +>p1 + p2 + p3 + p4 + p5 : string >p1 + p2 + p3 + p4 : string >p1 + p2 + p3 : string >p1 + p2 : string >p1 : "literal" >p2 : "literal" >p3 : "literal" | "other" ->p4 : "literal" | number +>p4 : number | "literal" +>p5 : true | 12 | "str" >'.' : string } diff --git a/tests/baselines/reference/jsdocStringLiteral.symbols b/tests/baselines/reference/jsdocStringLiteral.symbols deleted file mode 100644 index 982a1536069..00000000000 --- a/tests/baselines/reference/jsdocStringLiteral.symbols +++ /dev/null @@ -1,21 +0,0 @@ -=== tests/cases/conformance/jsdoc/in.js === -/** - * @param {'literal'} p1 - * @param {"literal"} p2 - * @param {'literal' | 'other'} p3 - * @param {'literal' | number} p4 - */ -function f(p1, p2, p3, p4) { ->f : Symbol(f, Decl(in.js, 0, 0)) ->p1 : Symbol(p1, Decl(in.js, 6, 11)) ->p2 : Symbol(p2, Decl(in.js, 6, 14)) ->p3 : Symbol(p3, Decl(in.js, 6, 18)) ->p4 : Symbol(p4, Decl(in.js, 6, 22)) - - return p1 + p2 + p3 + p4 + '.'; ->p1 : Symbol(p1, Decl(in.js, 6, 11)) ->p2 : Symbol(p2, Decl(in.js, 6, 14)) ->p3 : Symbol(p3, Decl(in.js, 6, 18)) ->p4 : Symbol(p4, Decl(in.js, 6, 22)) -} - diff --git a/tests/cases/conformance/jsdoc/jsdocStringLiteral.ts b/tests/cases/conformance/jsdoc/jsdocLiteral.ts similarity index 63% rename from tests/cases/conformance/jsdoc/jsdocStringLiteral.ts rename to tests/cases/conformance/jsdoc/jsdocLiteral.ts index d2a0d6c644d..bd0d2d562f9 100644 --- a/tests/cases/conformance/jsdoc/jsdocStringLiteral.ts +++ b/tests/cases/conformance/jsdoc/jsdocLiteral.ts @@ -6,7 +6,8 @@ * @param {"literal"} p2 * @param {'literal' | 'other'} p3 * @param {'literal' | number} p4 + * @param {12 | true | 'str'} p5 */ -function f(p1, p2, p3, p4) { - return p1 + p2 + p3 + p4 + '.'; +function f(p1, p2, p3, p4, p5) { + return p1 + p2 + p3 + p4 + p5 + '.'; } diff --git a/tests/cases/fourslash/completionForStringLiteral4.ts b/tests/cases/fourslash/completionForStringLiteral4.ts index c240c125dbe..11ae699eab8 100644 --- a/tests/cases/fourslash/completionForStringLiteral4.ts +++ b/tests/cases/fourslash/completionForStringLiteral4.ts @@ -6,15 +6,16 @@ //// * @param {"literal"} p2 //// * @param {'other1' | 'other2'} p3 //// * @param {'literal' | number} p4 +//// * @param {12 | true} p5 //// */ -////function f(p1, p2, p3, p4) { -//// return p1 + p2 + p3 + p4 + '.'; +////function f(p1, p2, p3, p4, p5) { +//// return p1 + p2 + p3 + p4 + p5 + '.'; ////} ////f/*1*/('literal', 'literal', "o/*2*/ther1", 12); goTo.marker('1'); verify.quickInfoExists(); -verify.quickInfoIs('function f(p1: "literal", p2: "literal", p3: "other1" | "other2", p4: "literal" | number): string', 'I am documentation'); +verify.quickInfoIs('function f(p1: "literal", p2: "literal", p3: "other1" | "other2", p4: number | "literal", p5: true | 12): string', 'I am documentation'); goTo.marker('2'); verify.completionListContains("other1"); From 798be6f4f977dfbc0e46353e56b28b59c123d1d6 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 4 Aug 2016 15:17:08 -0700 Subject: [PATCH 15/72] Add new test baseline and delete else in binder The extra `else` caused a ton of test failures! --- src/compiler/binder.ts | 2 +- .../reference/multipleDeclarations.js | 31 +++++- .../reference/multipleDeclarations.symbols | 98 +++++++++++++------ .../reference/multipleDeclarations.types | 59 ++++++++++- 4 files changed, 157 insertions(+), 33 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 6f0def32702..5e2cd3f8680 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -328,7 +328,7 @@ namespace ts { classifiableNames[name] = name; } - else if (symbol.flags & excludes) { + if (symbol.flags & excludes) { if (symbol.isDiscardable) { // Javascript constructor-declared symbols can be discarded in favor of // prototype symbols like methods. diff --git a/tests/baselines/reference/multipleDeclarations.js b/tests/baselines/reference/multipleDeclarations.js index e5aae416663..7fb7e0bca02 100644 --- a/tests/baselines/reference/multipleDeclarations.js +++ b/tests/baselines/reference/multipleDeclarations.js @@ -1,12 +1,10 @@ //// [input.js] - function C() { this.m = null; } C.prototype.m = function() { this.nothing(); } - class X { constructor() { this.m = this.m.bind(this); @@ -21,6 +19,20 @@ let x = new X(); X.prototype.mistake = false; x.m(); x.mistake; +class Y { + mistake() { + } + m() { + } + constructor() { + this.m = this.m.bind(this); + this.mistake = 'even more nonsense'; + } +} +Y.prototype.mistake = true; +let y = new Y(); +y.m(); +y.mistake(); //// [output.js] @@ -45,3 +57,18 @@ var x = new X(); X.prototype.mistake = false; x.m(); x.mistake; +var Y = (function () { + function Y() { + this.m = this.m.bind(this); + this.mistake = 'even more nonsense'; + } + Y.prototype.mistake = function () { + }; + Y.prototype.m = function () { + }; + return Y; +}()); +Y.prototype.mistake = true; +var y = new Y(); +y.m(); +y.mistake(); diff --git a/tests/baselines/reference/multipleDeclarations.symbols b/tests/baselines/reference/multipleDeclarations.symbols index 7e4bcd53c8a..9e49c2fc00c 100644 --- a/tests/baselines/reference/multipleDeclarations.symbols +++ b/tests/baselines/reference/multipleDeclarations.symbols @@ -1,64 +1,106 @@ === tests/cases/conformance/salsa/input.js === - function C() { >C : Symbol(C, Decl(input.js, 0, 0)) this.m = null; ->m : Symbol(C.m, Decl(input.js, 1, 14), Decl(input.js, 3, 1)) +>m : Symbol(C.m, Decl(input.js, 0, 14), Decl(input.js, 2, 1)) } C.prototype.m = function() { ->C.prototype : Symbol(C.m, Decl(input.js, 1, 14), Decl(input.js, 3, 1)) +>C.prototype : Symbol(C.m, Decl(input.js, 0, 14), Decl(input.js, 2, 1)) >C : Symbol(C, Decl(input.js, 0, 0)) >prototype : Symbol(Function.prototype, Decl(lib.d.ts, --, --)) ->m : Symbol(C.m, Decl(input.js, 1, 14), Decl(input.js, 3, 1)) +>m : Symbol(C.m, Decl(input.js, 0, 14), Decl(input.js, 2, 1)) this.nothing(); >this : Symbol(C, Decl(input.js, 0, 0)) } - class X { ->X : Symbol(X, Decl(input.js, 6, 1)) +>X : Symbol(X, Decl(input.js, 5, 1)) constructor() { this.m = this.m.bind(this); ->this.m : Symbol(X.m, Decl(input.js, 12, 5)) ->this : Symbol(X, Decl(input.js, 6, 1)) ->m : Symbol(X.m, Decl(input.js, 9, 19)) +>this.m : Symbol(X.m, Decl(input.js, 10, 5)) +>this : Symbol(X, Decl(input.js, 5, 1)) +>m : Symbol(X.m, Decl(input.js, 7, 19)) >this.m.bind : Symbol(Function.bind, Decl(lib.d.ts, --, --)) ->this.m : Symbol(X.m, Decl(input.js, 12, 5)) ->this : Symbol(X, Decl(input.js, 6, 1)) ->m : Symbol(X.m, Decl(input.js, 12, 5)) +>this.m : Symbol(X.m, Decl(input.js, 10, 5)) +>this : Symbol(X, Decl(input.js, 5, 1)) +>m : Symbol(X.m, Decl(input.js, 10, 5)) >bind : Symbol(Function.bind, Decl(lib.d.ts, --, --)) ->this : Symbol(X, Decl(input.js, 6, 1)) +>this : Symbol(X, Decl(input.js, 5, 1)) this.mistake = 'frankly, complete nonsense'; ->this.mistake : Symbol(X.mistake, Decl(input.js, 14, 5)) ->this : Symbol(X, Decl(input.js, 6, 1)) ->mistake : Symbol(X.mistake, Decl(input.js, 10, 35)) +>this.mistake : Symbol(X.mistake, Decl(input.js, 12, 5)) +>this : Symbol(X, Decl(input.js, 5, 1)) +>mistake : Symbol(X.mistake, Decl(input.js, 8, 35)) } m() { ->m : Symbol(X.m, Decl(input.js, 12, 5)) +>m : Symbol(X.m, Decl(input.js, 10, 5)) } mistake() { ->mistake : Symbol(X.mistake, Decl(input.js, 14, 5)) +>mistake : Symbol(X.mistake, Decl(input.js, 12, 5)) } } let x = new X(); ->x : Symbol(x, Decl(input.js, 18, 3)) ->X : Symbol(X, Decl(input.js, 6, 1)) +>x : Symbol(x, Decl(input.js, 16, 3)) +>X : Symbol(X, Decl(input.js, 5, 1)) X.prototype.mistake = false; ->X.prototype.mistake : Symbol(X.mistake, Decl(input.js, 14, 5)) ->X : Symbol(X, Decl(input.js, 6, 1)) +>X.prototype.mistake : Symbol(X.mistake, Decl(input.js, 12, 5)) +>X : Symbol(X, Decl(input.js, 5, 1)) >prototype : Symbol(X.prototype) x.m(); ->x.m : Symbol(X.m, Decl(input.js, 12, 5)) ->x : Symbol(x, Decl(input.js, 18, 3)) ->m : Symbol(X.m, Decl(input.js, 12, 5)) +>x.m : Symbol(X.m, Decl(input.js, 10, 5)) +>x : Symbol(x, Decl(input.js, 16, 3)) +>m : Symbol(X.m, Decl(input.js, 10, 5)) x.mistake; ->x.mistake : Symbol(X.mistake, Decl(input.js, 14, 5)) ->x : Symbol(x, Decl(input.js, 18, 3)) ->mistake : Symbol(X.mistake, Decl(input.js, 14, 5)) +>x.mistake : Symbol(X.mistake, Decl(input.js, 12, 5)) +>x : Symbol(x, Decl(input.js, 16, 3)) +>mistake : Symbol(X.mistake, Decl(input.js, 12, 5)) + +class Y { +>Y : Symbol(Y, Decl(input.js, 19, 10)) + + mistake() { +>mistake : Symbol(Y.mistake, Decl(input.js, 20, 9), Decl(input.js, 26, 35)) + } + m() { +>m : Symbol(Y.m, Decl(input.js, 22, 5), Decl(input.js, 25, 19)) + } + constructor() { + this.m = this.m.bind(this); +>this.m : Symbol(Y.m, Decl(input.js, 22, 5), Decl(input.js, 25, 19)) +>this : Symbol(Y, Decl(input.js, 19, 10)) +>m : Symbol(Y.m, Decl(input.js, 22, 5), Decl(input.js, 25, 19)) +>this.m : Symbol(Y.m, Decl(input.js, 22, 5), Decl(input.js, 25, 19)) +>this : Symbol(Y, Decl(input.js, 19, 10)) +>m : Symbol(Y.m, Decl(input.js, 22, 5), Decl(input.js, 25, 19)) +>this : Symbol(Y, Decl(input.js, 19, 10)) + + this.mistake = 'even more nonsense'; +>this.mistake : Symbol(Y.mistake, Decl(input.js, 20, 9), Decl(input.js, 26, 35)) +>this : Symbol(Y, Decl(input.js, 19, 10)) +>mistake : Symbol(Y.mistake, Decl(input.js, 20, 9), Decl(input.js, 26, 35)) + } +} +Y.prototype.mistake = true; +>Y.prototype.mistake : Symbol(Y.mistake, Decl(input.js, 20, 9), Decl(input.js, 26, 35)) +>Y : Symbol(Y, Decl(input.js, 19, 10)) +>prototype : Symbol(Y.prototype) + +let y = new Y(); +>y : Symbol(y, Decl(input.js, 31, 3)) +>Y : Symbol(Y, Decl(input.js, 19, 10)) + +y.m(); +>y.m : Symbol(Y.m, Decl(input.js, 22, 5), Decl(input.js, 25, 19)) +>y : Symbol(y, Decl(input.js, 31, 3)) +>m : Symbol(Y.m, Decl(input.js, 22, 5), Decl(input.js, 25, 19)) + +y.mistake(); +>y.mistake : Symbol(Y.mistake, Decl(input.js, 20, 9), Decl(input.js, 26, 35)) +>y : Symbol(y, Decl(input.js, 31, 3)) +>mistake : Symbol(Y.mistake, Decl(input.js, 20, 9), Decl(input.js, 26, 35)) diff --git a/tests/baselines/reference/multipleDeclarations.types b/tests/baselines/reference/multipleDeclarations.types index 2a7cac18205..900d03195d4 100644 --- a/tests/baselines/reference/multipleDeclarations.types +++ b/tests/baselines/reference/multipleDeclarations.types @@ -1,5 +1,4 @@ === tests/cases/conformance/salsa/input.js === - function C() { >C : () => void @@ -25,7 +24,6 @@ C.prototype.m = function() { >this : { m: () => void; } >nothing : any } - class X { >X : X @@ -82,3 +80,60 @@ x.mistake; >x : X >mistake : () => void +class Y { +>Y : Y + + mistake() { +>mistake : any + } + m() { +>m : any + } + constructor() { + this.m = this.m.bind(this); +>this.m = this.m.bind(this) : any +>this.m : any +>this : this +>m : any +>this.m.bind(this) : any +>this.m.bind : any +>this.m : any +>this : this +>m : any +>bind : any +>this : this + + this.mistake = 'even more nonsense'; +>this.mistake = 'even more nonsense' : string +>this.mistake : any +>this : this +>mistake : any +>'even more nonsense' : string + } +} +Y.prototype.mistake = true; +>Y.prototype.mistake = true : boolean +>Y.prototype.mistake : any +>Y.prototype : Y +>Y : typeof Y +>prototype : Y +>mistake : any +>true : boolean + +let y = new Y(); +>y : Y +>new Y() : Y +>Y : typeof Y + +y.m(); +>y.m() : any +>y.m : any +>y : Y +>m : any + +y.mistake(); +>y.mistake() : any +>y.mistake : any +>y : Y +>mistake : any + From 8f638f7ecdc154c00c2be11a55971a7d13ee5757 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Fri, 5 Aug 2016 09:58:30 -0700 Subject: [PATCH 16/72] Fix lint --- src/compiler/binder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 5e2cd3f8680..b771a3b67be 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1987,7 +1987,7 @@ namespace ts { assignee.symbol.members = assignee.symbol.members || {}; // It's acceptable for multiple 'this' assignments of the same identifier to occur // AND it can be overwritten by subsequent method declarations - let symbol = declareSymbol(assignee.symbol.members, assignee.symbol, node, SymbolFlags.Property, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property); + const symbol = declareSymbol(assignee.symbol.members, assignee.symbol, node, SymbolFlags.Property, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property); symbol.isDiscardable = true; } From cabd276ddcfe849f59b1ccfebc24b0d798dee062 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Fri, 5 Aug 2016 10:28:03 -0700 Subject: [PATCH 17/72] Fix more lint --- src/compiler/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 4c08d7a487c..f5cee2f8f8f 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2137,7 +2137,7 @@ namespace ts { /* @internal */ exportSymbol?: Symbol; // Exported symbol associated with this symbol /* @internal */ constEnumOnlyModule?: boolean; // True if module contains only const enums or other modules with only const enums /* @internal */ isReferenced?: boolean; // True if the symbol is referenced elsewhere - /* @internal */ isDiscardable?: boolean;// True if a Javascript class property can be overwritten by a method + /* @internal */ isDiscardable?: boolean; // True if a Javascript class property can be overwritten by a method } /* @internal */ From cc2dc3acb0a3c654ca6ca26cad906af5585fcadf Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Sat, 6 Aug 2016 10:36:17 -0700 Subject: [PATCH 18/72] Emit more efficient/concise "empty" ES6 ctor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When there are property assignments in a the class body of an inheriting class, tsc current emit the following compilation: ```ts class Foo extends Bar { public foo = 1; } ``` ```js class Foo extends Bar { constructor(…args) { super(…args); this.foo = 1; } } ``` This introduces an unneeded local variable and might force a reification of the `arguments` object (or otherwise reify the arguments into an array). This is particularly bad when that output is fed into another transpiler like Babel. In Babel, you get something like this today: ```js var Foo = (function (_Bar) { _inherits(Foo, _Bar); function Foo() { _classCallCheck(this, Foo); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } _Bar.call.apply(_Bar, [this].concat(args)); this.foo = 1; } return Foo; })(Bar); ``` This causes a lot of needless work/allocations and some very strange code (`.call.apply` o_0). Admittedly, this is not strictly tsc’s problem; it could have done a deeper analysis of the code and optimized out the extra dance. However, tsc could also have emitted this simpler, more concise and semantically equivalent code in the first place: ```js class Foo extends Bar { constructor() { super(…arguments); this.foo = 1; } } ``` Which compiles into the following in Babel: ```js var Foo = (function (_Bar) { _inherits(Foo, _Bar); function Foo() { _classCallCheck(this, Foo); _Bar.apply(this, arguments); this.foo = 1; } return Foo; })(Bar); ``` Which is well-optimized (today) in most engines and much less confusing to read. As far as I can tell, the proposed compilation has exactly the same semantics as before. Fixes #10175 --- src/compiler/emitter.ts | 15 ++------------- tests/baselines/reference/classExpressionES63.js | 8 ++++---- ...ClassDeclarationWithPropertyAssignmentInES6.js | 4 ++-- 3 files changed, 8 insertions(+), 19 deletions(-) diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index c8bd8072b24..6666b7df235 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -5311,18 +5311,7 @@ const _super = (function (geti, seti) { emitSignatureParameters(ctor); } else { - // Based on EcmaScript6 section 14.5.14: Runtime Semantics: ClassDefinitionEvaluation. - // If constructor is empty, then, - // If ClassHeritageopt is present, then - // Let constructor be the result of parsing the String "constructor(... args){ super (...args);}" using the syntactic grammar with the goal symbol MethodDefinition. - // Else, - // Let constructor be the result of parsing the String "constructor( ){ }" using the syntactic grammar with the goal symbol MethodDefinition - if (baseTypeElement) { - write("(...args)"); - } - else { - write("()"); - } + write("()"); } } @@ -5360,7 +5349,7 @@ const _super = (function (geti, seti) { write("_super.apply(this, arguments);"); } else { - write("super(...args);"); + write("super(...arguments);"); } emitEnd(baseTypeElement); } diff --git a/tests/baselines/reference/classExpressionES63.js b/tests/baselines/reference/classExpressionES63.js index 6b3a06cf7c3..5a72e173475 100644 --- a/tests/baselines/reference/classExpressionES63.js +++ b/tests/baselines/reference/classExpressionES63.js @@ -13,14 +13,14 @@ let C = class extends class extends class { } } { - constructor(...args) { - super(...args); + constructor() { + super(...arguments); this.b = 2; } } { - constructor(...args) { - super(...args); + constructor() { + super(...arguments); this.c = 3; } } diff --git a/tests/baselines/reference/emitClassDeclarationWithPropertyAssignmentInES6.js b/tests/baselines/reference/emitClassDeclarationWithPropertyAssignmentInES6.js index 03fb45b806e..91e6c506c2b 100644 --- a/tests/baselines/reference/emitClassDeclarationWithPropertyAssignmentInES6.js +++ b/tests/baselines/reference/emitClassDeclarationWithPropertyAssignmentInES6.js @@ -37,8 +37,8 @@ class D { } } class E extends D { - constructor(...args) { - super(...args); + constructor() { + super(...arguments); this.z = true; } } From 2845d2f8b8c2c476085eda9abe083000047d70c4 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Mon, 8 Aug 2016 09:04:46 -0700 Subject: [PATCH 19/72] Improve naming and documentation from PR --- src/compiler/binder.ts | 9 +++++++-- src/compiler/types.ts | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index b771a3b67be..849f503ef85 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -313,6 +313,11 @@ namespace ts { // declaration we have for this symbol, and then create a new symbol for this // declaration. // + // Note that when properties declared in Javascript constructors + // (marked by isReplaceableByMethod) conflict with another symbol, the property loses. + // Always. This allows the common Javascript pattern of overwriting a prototype method + // with an bound instance method of the same type: `this.method = this.method.bind(this)` + // // If we created a new symbol, either because we didn't have a symbol with this name // in the symbol table, or we conflicted with an existing symbol, then just add this // node as the sole declaration of the new symbol. @@ -329,7 +334,7 @@ namespace ts { } if (symbol.flags & excludes) { - if (symbol.isDiscardable) { + if (symbol.isReplaceableByMethod) { // Javascript constructor-declared symbols can be discarded in favor of // prototype symbols like methods. symbol = symbolTable[name] = createSymbol(SymbolFlags.None, name); @@ -1988,7 +1993,7 @@ namespace ts { // It's acceptable for multiple 'this' assignments of the same identifier to occur // AND it can be overwritten by subsequent method declarations const symbol = declareSymbol(assignee.symbol.members, assignee.symbol, node, SymbolFlags.Property, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property); - symbol.isDiscardable = true; + symbol.isReplaceableByMethod = true; } function bindPrototypePropertyAssignment(node: BinaryExpression) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index f5cee2f8f8f..70bd3c3c070 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2137,7 +2137,7 @@ namespace ts { /* @internal */ exportSymbol?: Symbol; // Exported symbol associated with this symbol /* @internal */ constEnumOnlyModule?: boolean; // True if module contains only const enums or other modules with only const enums /* @internal */ isReferenced?: boolean; // True if the symbol is referenced elsewhere - /* @internal */ isDiscardable?: boolean; // True if a Javascript class property can be overwritten by a method + /* @internal */ isReplaceableByMethod?: boolean; // Can this Javascript class property be replaced by a method symbol? } /* @internal */ From ce7f554090c68ad9c897ea4928596a8c64d6e934 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Mon, 8 Aug 2016 13:15:25 -0700 Subject: [PATCH 20/72] Speed up fourslash tests --- src/harness/fourslash.ts | 107 +++++++++++++-------------------------- 1 file changed, 35 insertions(+), 72 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index a42abbbc609..366891c0d30 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -249,6 +249,7 @@ namespace FourSlash { if (compilationOptions.typeRoots) { compilationOptions.typeRoots = compilationOptions.typeRoots.map(p => ts.getNormalizedAbsolutePath(p, this.basePath)); } + compilationOptions.skipDefaultLibCheck = true; const languageServiceAdapter = this.getLanguageServiceAdapter(testType, this.cancellationToken, compilationOptions); this.languageServiceAdapterHost = languageServiceAdapter.getHost(); @@ -376,7 +377,7 @@ namespace FourSlash { public verifyErrorExistsBetweenMarkers(startMarkerName: string, endMarkerName: string, negative: boolean) { const startMarker = this.getMarkerByName(startMarkerName); const endMarker = this.getMarkerByName(endMarkerName); - const predicate = function (errorMinChar: number, errorLimChar: number, startPos: number, endPos: number) { + const predicate = function(errorMinChar: number, errorLimChar: number, startPos: number, endPos: number) { return ((errorMinChar === startPos) && (errorLimChar === endPos)) ? true : false; }; @@ -428,12 +429,12 @@ namespace FourSlash { let predicate: (errorMinChar: number, errorLimChar: number, startPos: number, endPos: number) => boolean; if (after) { - predicate = function (errorMinChar: number, errorLimChar: number, startPos: number, endPos: number) { + predicate = function(errorMinChar: number, errorLimChar: number, startPos: number, endPos: number) { return ((errorMinChar >= startPos) && (errorLimChar >= startPos)) ? true : false; }; } else { - predicate = function (errorMinChar: number, errorLimChar: number, startPos: number, endPos: number) { + predicate = function(errorMinChar: number, errorLimChar: number, startPos: number, endPos: number) { return ((errorMinChar <= startPos) && (errorLimChar <= startPos)) ? true : false; }; } @@ -458,7 +459,7 @@ namespace FourSlash { endPos = endMarker.position; } - errors.forEach(function (error: ts.Diagnostic) { + errors.forEach(function(error: ts.Diagnostic) { if (predicate(error.start, error.start + error.length, startPos, endPos)) { exists = true; } @@ -475,7 +476,7 @@ namespace FourSlash { Harness.IO.log("Unexpected error(s) found. Error list is:"); } - errors.forEach(function (error: ts.Diagnostic) { + errors.forEach(function(error: ts.Diagnostic) { Harness.IO.log(" minChar: " + error.start + ", limChar: " + (error.start + error.length) + ", message: " + ts.flattenDiagnosticMessageText(error.messageText, Harness.IO.newLine()) + "\n"); @@ -1348,14 +1349,7 @@ namespace FourSlash { } // Enters lines of text at the current caret position - public type(text: string) { - return this.typeHighFidelity(text); - } - - // Enters lines of text at the current caret position, invoking - // language service APIs to mimic Visual Studio's behavior - // as much as possible - private typeHighFidelity(text: string) { + public type(text: string, highFidelity = false) { let offset = this.currentCaretPosition; const prevChar = " "; const checkCadence = (text.length >> 2) + 1; @@ -1364,24 +1358,26 @@ namespace FourSlash { // Make the edit const ch = text.charAt(i); this.languageServiceAdapterHost.editScript(this.activeFile.fileName, offset, offset, ch); - this.languageService.getBraceMatchingAtPosition(this.activeFile.fileName, offset); + if (highFidelity) { + this.languageService.getBraceMatchingAtPosition(this.activeFile.fileName, offset); + } this.updateMarkersForEdit(this.activeFile.fileName, offset, offset, ch); offset++; - if (ch === "(" || ch === ",") { - /* Signature help*/ - this.languageService.getSignatureHelpItems(this.activeFile.fileName, offset); - } - else if (prevChar === " " && /A-Za-z_/.test(ch)) { - /* Completions */ - this.languageService.getCompletionsAtPosition(this.activeFile.fileName, offset); - } + if (highFidelity) { + if (ch === "(" || ch === ",") { + /* Signature help*/ + this.languageService.getSignatureHelpItems(this.activeFile.fileName, offset); + } + else if (prevChar === " " && /A-Za-z_/.test(ch)) { + /* Completions */ + this.languageService.getCompletionsAtPosition(this.activeFile.fileName, offset); + } - if (i % checkCadence === 0) { - this.checkPostEditInvariants(); - // this.languageService.getSyntacticDiagnostics(this.activeFile.fileName); - // this.languageService.getSemanticDiagnostics(this.activeFile.fileName); + if (i % checkCadence === 0) { + this.checkPostEditInvariants(); + } } // Handle post-keystroke formatting @@ -1389,14 +1385,12 @@ namespace FourSlash { const edits = this.languageService.getFormattingEditsAfterKeystroke(this.activeFile.fileName, offset, ch, this.formatCodeOptions); if (edits.length) { offset += this.applyEdits(this.activeFile.fileName, edits, /*isFormattingEdit*/ true); - // this.checkPostEditInvariants(); } } } // Move the caret to wherever we ended up this.currentCaretPosition = offset; - this.fixCaretPosition(); this.checkPostEditInvariants(); } @@ -1415,7 +1409,6 @@ namespace FourSlash { const edits = this.languageService.getFormattingEditsForRange(this.activeFile.fileName, start, offset, this.formatCodeOptions); if (edits.length) { offset += this.applyEdits(this.activeFile.fileName, edits, /*isFormattingEdit*/ true); - this.checkPostEditInvariants(); } } @@ -1433,20 +1426,22 @@ namespace FourSlash { return; } - const incrementalSourceFile = this.languageService.getNonBoundSourceFile(this.activeFile.fileName); - Utils.assertInvariants(incrementalSourceFile, /*parent:*/ undefined); + if (1 + 1 === 2) { + const incrementalSourceFile = this.languageService.getNonBoundSourceFile(this.activeFile.fileName); + Utils.assertInvariants(incrementalSourceFile, /*parent:*/ undefined); - const incrementalSyntaxDiagnostics = incrementalSourceFile.parseDiagnostics; + const incrementalSyntaxDiagnostics = incrementalSourceFile.parseDiagnostics; - // Check syntactic structure - const content = this.getFileContent(this.activeFile.fileName); + // Check syntactic structure + const content = this.getFileContent(this.activeFile.fileName); - const referenceSourceFile = ts.createLanguageServiceSourceFile( - this.activeFile.fileName, createScriptSnapShot(content), ts.ScriptTarget.Latest, /*version:*/ "0", /*setNodeParents:*/ false); - const referenceSyntaxDiagnostics = referenceSourceFile.parseDiagnostics; + const referenceSourceFile = ts.createLanguageServiceSourceFile( + this.activeFile.fileName, createScriptSnapShot(content), ts.ScriptTarget.Latest, /*version:*/ "0", /*setNodeParents:*/ false); + const referenceSyntaxDiagnostics = referenceSourceFile.parseDiagnostics; - Utils.assertDiagnosticsEquals(incrementalSyntaxDiagnostics, referenceSyntaxDiagnostics); - Utils.assertStructuralEquals(incrementalSourceFile, referenceSourceFile); + Utils.assertDiagnosticsEquals(incrementalSyntaxDiagnostics, referenceSyntaxDiagnostics); + Utils.assertStructuralEquals(incrementalSourceFile, referenceSourceFile); + } } private fixCaretPosition() { @@ -2269,40 +2264,8 @@ namespace FourSlash { export function runFourSlashTestContent(basePath: string, testType: FourSlashTestType, content: string, fileName: string): void { // Parse out the files and their metadata const testData = parseTestData(basePath, content, fileName); - const state = new TestState(basePath, testType, testData); - - let result = ""; - const fourslashFile: Harness.Compiler.TestFile = { - unitName: Harness.Compiler.fourslashFileName, - content: undefined, - }; - const testFile: Harness.Compiler.TestFile = { - unitName: fileName, - content: content - }; - - const host = Harness.Compiler.createCompilerHost( - [fourslashFile, testFile], - (fn, contents) => result = contents, - ts.ScriptTarget.Latest, - Harness.IO.useCaseSensitiveFileNames(), - Harness.IO.getCurrentDirectory()); - - const program = ts.createProgram([Harness.Compiler.fourslashFileName, fileName], { outFile: "fourslashTestOutput.js", noResolve: true, target: ts.ScriptTarget.ES3 }, host); - - const sourceFile = host.getSourceFile(fileName, ts.ScriptTarget.ES3); - - const diagnostics = ts.getPreEmitDiagnostics(program, sourceFile); - if (diagnostics.length > 0) { - throw new Error(`Error compiling ${fileName}: ` + - diagnostics.map(e => ts.flattenDiagnosticMessageText(e.messageText, Harness.IO.newLine())).join("\r\n")); - } - - program.emit(sourceFile); - - ts.Debug.assert(!!result); - runCode(result, state); + runCode(ts.transpile(content), state); } function runCode(code: string, state: TestState): void { From e724e883e6dd21226c8399893e5b49a9266f5797 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Mon, 8 Aug 2016 13:31:45 -0700 Subject: [PATCH 21/72] Duh --- src/harness/fourslash.ts | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 366891c0d30..751db7b971a 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -1426,22 +1426,20 @@ namespace FourSlash { return; } - if (1 + 1 === 2) { - const incrementalSourceFile = this.languageService.getNonBoundSourceFile(this.activeFile.fileName); - Utils.assertInvariants(incrementalSourceFile, /*parent:*/ undefined); + const incrementalSourceFile = this.languageService.getNonBoundSourceFile(this.activeFile.fileName); + Utils.assertInvariants(incrementalSourceFile, /*parent:*/ undefined); - const incrementalSyntaxDiagnostics = incrementalSourceFile.parseDiagnostics; + const incrementalSyntaxDiagnostics = incrementalSourceFile.parseDiagnostics; - // Check syntactic structure - const content = this.getFileContent(this.activeFile.fileName); + // Check syntactic structure + const content = this.getFileContent(this.activeFile.fileName); - const referenceSourceFile = ts.createLanguageServiceSourceFile( - this.activeFile.fileName, createScriptSnapShot(content), ts.ScriptTarget.Latest, /*version:*/ "0", /*setNodeParents:*/ false); - const referenceSyntaxDiagnostics = referenceSourceFile.parseDiagnostics; + const referenceSourceFile = ts.createLanguageServiceSourceFile( + this.activeFile.fileName, createScriptSnapShot(content), ts.ScriptTarget.Latest, /*version:*/ "0", /*setNodeParents:*/ false); + const referenceSyntaxDiagnostics = referenceSourceFile.parseDiagnostics; - Utils.assertDiagnosticsEquals(incrementalSyntaxDiagnostics, referenceSyntaxDiagnostics); - Utils.assertStructuralEquals(incrementalSourceFile, referenceSourceFile); - } + Utils.assertDiagnosticsEquals(incrementalSyntaxDiagnostics, referenceSyntaxDiagnostics); + Utils.assertStructuralEquals(incrementalSourceFile, referenceSourceFile); } private fixCaretPosition() { From 53d54c2b282d7bc4808941292fd91c1d41f5e4cc Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Mon, 8 Aug 2016 14:30:27 -0700 Subject: [PATCH 22/72] Make baselines faster by not writing out unneeded files --- Gulpfile.ts | 111 ++++++++++++++++++++----------------- Jakefile.js | 17 +++--- package.json | 1 + scripts/types/ambient.d.ts | 2 +- src/harness/harness.ts | 19 +++---- 5 files changed, 79 insertions(+), 71 deletions(-) diff --git a/Gulpfile.ts b/Gulpfile.ts index b9b5d2068b2..e2d37277bcf 100644 --- a/Gulpfile.ts +++ b/Gulpfile.ts @@ -17,7 +17,7 @@ declare module "gulp-typescript" { stripInternal?: boolean; types?: string[]; } - interface CompileStream extends NodeJS.ReadWriteStream {} // Either gulp or gulp-typescript has some odd typings which don't reflect reality, making this required + interface CompileStream extends NodeJS.ReadWriteStream { } // Either gulp or gulp-typescript has some odd typings which don't reflect reality, making this required } import * as insert from "gulp-insert"; import * as sourcemaps from "gulp-sourcemaps"; @@ -65,7 +65,7 @@ const cmdLineOptions = minimist(process.argv.slice(2), { } }); -function exec(cmd: string, args: string[], complete: () => void = (() => {}), error: (e: any, status: number) => void = (() => {})) { +function exec(cmd: string, args: string[], complete: () => void = (() => { }), error: (e: any, status: number) => void = (() => { })) { console.log(`${cmd} ${args.join(" ")}`); // TODO (weswig): Update child_process types to add windowsVerbatimArguments to the type definition const subshellFlag = isWin ? "/c" : "-c"; @@ -116,12 +116,12 @@ const es2015LibrarySources = [ ]; const es2015LibrarySourceMap = es2015LibrarySources.map(function(source) { - return { target: "lib." + source, sources: ["header.d.ts", source] }; + return { target: "lib." + source, sources: ["header.d.ts", source] }; }); -const es2016LibrarySource = [ "es2016.array.include.d.ts" ]; +const es2016LibrarySource = ["es2016.array.include.d.ts"]; -const es2016LibrarySourceMap = es2016LibrarySource.map(function (source) { +const es2016LibrarySourceMap = es2016LibrarySource.map(function(source) { return { target: "lib." + source, sources: ["header.d.ts", source] }; }); @@ -130,38 +130,38 @@ const es2017LibrarySource = [ "es2017.sharedmemory.d.ts" ]; -const es2017LibrarySourceMap = es2017LibrarySource.map(function (source) { +const es2017LibrarySourceMap = es2017LibrarySource.map(function(source) { return { target: "lib." + source, sources: ["header.d.ts", source] }; }); const hostsLibrarySources = ["dom.generated.d.ts", "webworker.importscripts.d.ts", "scripthost.d.ts"]; const librarySourceMap = [ - // Host library - { target: "lib.dom.d.ts", sources: ["header.d.ts", "dom.generated.d.ts"] }, - { target: "lib.dom.iterable.d.ts", sources: ["header.d.ts", "dom.iterable.d.ts"] }, - { target: "lib.webworker.d.ts", sources: ["header.d.ts", "webworker.generated.d.ts"] }, - { target: "lib.scripthost.d.ts", sources: ["header.d.ts", "scripthost.d.ts"] }, + // Host library + { target: "lib.dom.d.ts", sources: ["header.d.ts", "dom.generated.d.ts"] }, + { target: "lib.dom.iterable.d.ts", sources: ["header.d.ts", "dom.iterable.d.ts"] }, + { target: "lib.webworker.d.ts", sources: ["header.d.ts", "webworker.generated.d.ts"] }, + { target: "lib.scripthost.d.ts", sources: ["header.d.ts", "scripthost.d.ts"] }, - // JavaScript library - { target: "lib.es5.d.ts", sources: ["header.d.ts", "es5.d.ts"] }, - { target: "lib.es2015.d.ts", sources: ["header.d.ts", "es2015.d.ts"] }, - { target: "lib.es2016.d.ts", sources: ["header.d.ts", "es2016.d.ts"] }, - { target: "lib.es2017.d.ts", sources: ["header.d.ts", "es2017.d.ts"] }, + // JavaScript library + { target: "lib.es5.d.ts", sources: ["header.d.ts", "es5.d.ts"] }, + { target: "lib.es2015.d.ts", sources: ["header.d.ts", "es2015.d.ts"] }, + { target: "lib.es2016.d.ts", sources: ["header.d.ts", "es2016.d.ts"] }, + { target: "lib.es2017.d.ts", sources: ["header.d.ts", "es2017.d.ts"] }, - // JavaScript + all host library - { target: "lib.d.ts", sources: ["header.d.ts", "es5.d.ts"].concat(hostsLibrarySources) }, - { target: "lib.es6.d.ts", sources: ["header.d.ts", "es5.d.ts"].concat(es2015LibrarySources, hostsLibrarySources, "dom.iterable.d.ts") } + // JavaScript + all host library + { target: "lib.d.ts", sources: ["header.d.ts", "es5.d.ts"].concat(hostsLibrarySources) }, + { target: "lib.es6.d.ts", sources: ["header.d.ts", "es5.d.ts"].concat(es2015LibrarySources, hostsLibrarySources, "dom.iterable.d.ts") } ].concat(es2015LibrarySourceMap, es2016LibrarySourceMap, es2017LibrarySourceMap); -const libraryTargets = librarySourceMap.map(function (f) { +const libraryTargets = librarySourceMap.map(function(f) { return path.join(builtLocalDirectory, f.target); }); for (const i in libraryTargets) { const entry = librarySourceMap[i]; const target = libraryTargets[i]; - const sources = [copyright].concat(entry.sources.map(function (s) { + const sources = [copyright].concat(entry.sources.map(function(s) { return path.join(libraryDirectory, s); })); gulp.task(target, false, [], function() { @@ -391,7 +391,7 @@ gulp.task(servicesFile, false, ["lib", "generate-diagnostics"], () => { .pipe(sourcemaps.init()) .pipe(tsc(servicesProject)); const completedJs = js.pipe(prependCopyright()) - .pipe(sourcemaps.write(".")); + .pipe(sourcemaps.write(".")); const completedDts = dts.pipe(prependCopyright(/*outputCopyright*/true)) .pipe(insert.transform((contents, file) => { file.path = standaloneDefinitionsFile; @@ -434,17 +434,17 @@ const tsserverLibraryDefinitionFile = path.join(builtLocalDirectory, "tsserverli gulp.task(tsserverLibraryFile, false, [servicesFile], (done) => { const serverLibraryProject = tsc.createProject("src/server/tsconfig.library.json", getCompilerSettings({}, /*useBuiltCompiler*/ true)); - const {js, dts}: {js: NodeJS.ReadableStream, dts: NodeJS.ReadableStream} = serverLibraryProject.src() + const {js, dts}: { js: NodeJS.ReadableStream, dts: NodeJS.ReadableStream } = serverLibraryProject.src() .pipe(sourcemaps.init()) .pipe(newer(tsserverLibraryFile)) .pipe(tsc(serverLibraryProject)); return merge2([ js.pipe(prependCopyright()) - .pipe(sourcemaps.write(".")) - .pipe(gulp.dest(builtLocalDirectory)), + .pipe(sourcemaps.write(".")) + .pipe(gulp.dest(builtLocalDirectory)), dts.pipe(prependCopyright()) - .pipe(gulp.dest(builtLocalDirectory)) + .pipe(gulp.dest(builtLocalDirectory)) ]); }); @@ -476,7 +476,7 @@ gulp.task(specMd, false, [word2mdJs], (done) => { const specMDFullPath = path.resolve(specMd); const cmd = "cscript //nologo " + word2mdJs + " \"" + specWordFullPath + "\" " + "\"" + specMDFullPath + "\""; console.log(cmd); - cp.exec(cmd, function () { + cp.exec(cmd, function() { done(); }); }); @@ -492,12 +492,12 @@ gulp.task("dontUseDebugMode", false, [], (done) => { useDebugMode = false; done( gulp.task("VerifyLKG", false, [], () => { const expectedFiles = [builtLocalCompiler, servicesFile, serverFile, nodePackageFile, nodeDefinitionsFile, standaloneDefinitionsFile, tsserverLibraryFile, tsserverLibraryDefinitionFile].concat(libraryTargets); - const missingFiles = expectedFiles.filter(function (f) { + const missingFiles = expectedFiles.filter(function(f) { return !fs.existsSync(f); }); if (missingFiles.length > 0) { throw new Error("Cannot replace the LKG unless all built targets are present in directory " + builtLocalDirectory + - ". The following files are missing:\n" + missingFiles.join("\n")); + ". The following files are missing:\n" + missingFiles.join("\n")); } // Copy all the targets into the LKG directory return gulp.src(expectedFiles).pipe(gulp.dest(LKGDirectory)); @@ -627,7 +627,7 @@ function runConsoleTests(defaultReporter: string, runInParallel: boolean, done: } args.push(run); setNodeEnvToDevelopment(); - runTestsInParallel(taskConfigsFolder, run, { testTimeout: testTimeout, noColors: colors === " --no-colors " }, function (err) { + runTestsInParallel(taskConfigsFolder, run, { testTimeout: testTimeout, noColors: colors === " --no-colors " }, function(err) { // last worker clean everything and runs linter in case if there were no errors del(taskConfigsFolder).then(() => { if (!err) { @@ -679,7 +679,7 @@ gulp.task("runtests", ["build-rules", "tests"], (done) => { runConsoleTests("mocha-fivemat-progress-reporter", /*runInParallel*/ false, done); -}); + }); const nodeServerOutFile = "tests/webTestServer.js"; const nodeServerInFile = "tests/webTestServer.ts"; @@ -812,31 +812,38 @@ gulp.task("diff-rwc", "Diffs the RWC baselines using the diff tool specified by }); -gulp.task("baseline-accept", "Makes the most recent test results the new baseline, overwriting the old baseline", (done) => { - const softAccept = cmdLineOptions["soft"]; - if (!softAccept) { - del(refBaseline).then(() => { - fs.renameSync(localBaseline, refBaseline); - done(); - }, done); - } - else { - gulp.src(localBaseline) - .pipe(gulp.dest(refBaseline)) - .on("end", () => { - del(path.join(refBaseline, "local")).then(() => done(), done); - }); - } +const deleteEnding = '.delete'; +gulp.task("baseline-accept", "Makes the most recent test results the new baseline, overwriting the old baseline", () => { + return baselineAccept(""); }); + +function baselineAccept(subfolder = "") { + return merge2(baselineCopy(subfolder), baselineDelete(subfolder)); +} + +function baselineCopy(subfolder = "") { + return gulp.src([`tests/baselines/local/${subfolder}/**`, `!tests/baselines/local/${subfolder}/**/*.delete`]) + .pipe(gulp.dest(refBaseline)); +} + +function baselineDelete(subfolder = "") { + return gulp.src(['tests/baselines/local/**/*.delete']) + .pipe(insert.transform((content, fileObj) => { + const target = path.join(refBaseline, fileObj.relative.substr(0, fileObj.relative.length - '.delete'.length)); + console.log(target); + del.sync(target); + del.sync(fileObj.path); + return ''; + })); +} + gulp.task("baseline-accept-rwc", "Makes the most recent rwc test results the new baseline, overwriting the old baseline", () => { - return del(refRwcBaseline).then(() => { - fs.renameSync(localRwcBaseline, refRwcBaseline); - }); + return baselineAccept("rwc"); }); + + gulp.task("baseline-accept-test262", "Makes the most recent test262 test results the new baseline, overwriting the old baseline", () => { - return del(refTest262Baseline).then(() => { - fs.renameSync(localTest262Baseline, refTest262Baseline); - }); + return baselineAccept("test262"); }); diff --git a/Jakefile.js b/Jakefile.js index a5650a56b16..8062d8a2928 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -1,3 +1,4 @@ +"use strict"; // This file contains the build logic for the public repo var fs = require("fs"); @@ -898,16 +899,16 @@ task("tests-debug", ["setDebugMode", "tests"]); // Makes the test results the new baseline desc("Makes the most recent test results the new baseline, overwriting the old baseline"); task("baseline-accept", function(hardOrSoft) { - if (!hardOrSoft || hardOrSoft === "hard") { - jake.rmRf(refBaseline); - fs.renameSync(localBaseline, refBaseline); - } - else if (hardOrSoft === "soft") { - var files = jake.readdirR(localBaseline); - for (var i in files) { + var files = jake.readdirR(localBaseline); + var deleteEnding = '.delete'; + for (var i in files) { + if (files[i].substr(files[i].length - deleteEnding.length) === deleteEnding) { + var filename = path.basename(files[i]); + filename = filename.substr(0, filename.length - deleteEnding.length); + fs.unlink(path.join(refBaseline, filename)); + } else { jake.cpR(files[i], refBaseline); } - jake.rmRf(path.join(refBaseline, "local")); } }); diff --git a/package.json b/package.json index 9f3122c3374..1f1c52b42ed 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "mkdirp": "latest", "mocha": "latest", "mocha-fivemat-progress-reporter": "latest", + "q": "latest", "run-sequence": "latest", "sorcery": "latest", "through2": "latest", diff --git a/scripts/types/ambient.d.ts b/scripts/types/ambient.d.ts index e77e3fe8c5a..16ec9385cb1 100644 --- a/scripts/types/ambient.d.ts +++ b/scripts/types/ambient.d.ts @@ -10,7 +10,7 @@ declare module "gulp-insert" { export function append(text: string | Buffer): NodeJS.ReadWriteStream; export function prepend(text: string | Buffer): NodeJS.ReadWriteStream; export function wrap(text: string | Buffer, tail: string | Buffer): NodeJS.ReadWriteStream; - export function transform(cb: (contents: string, file: {path: string}) => string): NodeJS.ReadWriteStream; // file is a vinyl file + export function transform(cb: (contents: string, file: {path: string, relative: string}) => string): NodeJS.ReadWriteStream; // file is a vinyl file } declare module "into-stream" { diff --git a/src/harness/harness.ts b/src/harness/harness.ts index f27e7e1c174..18a440c789b 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -1569,6 +1569,7 @@ namespace Harness { /** Support class for baseline files */ export namespace Baseline { + const NoContent = ""; export interface BaselineOptions { Subfolder?: string; @@ -1635,14 +1636,6 @@ namespace Harness { throw new Error("The generated content was \"undefined\". Return \"null\" if no baselining is required.\""); } - // Store the content in the 'local' folder so we - // can accept it later (manually) - /* tslint:disable:no-null-keyword */ - if (actual !== null) { - /* tslint:enable:no-null-keyword */ - IO.writeFile(actualFileName, actual); - } - return actual; } @@ -1659,7 +1652,7 @@ namespace Harness { /* tslint:disable:no-null-keyword */ if (actual === null) { /* tslint:enable:no-null-keyword */ - actual = ""; + actual = NoContent; } let expected = ""; @@ -1672,7 +1665,13 @@ namespace Harness { function writeComparison(expected: string, actual: string, relativeFileName: string, actualFileName: string, descriptionForDescribe: string) { const encoded_actual = Utils.encodeString(actual); - if (expected != encoded_actual) { + if (expected !== encoded_actual) { + if (actual === NoContent) { + IO.writeFile(relativeFileName + '.delete', ''); + } + else { + IO.writeFile(relativeFileName, actual); + } // Overwrite & issue error const errMsg = "The baseline file " + relativeFileName + " has changed."; throw new Error(errMsg); From eac7a48c5f03248bf6c597aacf7a1216e20f4ba9 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Mon, 8 Aug 2016 14:39:31 -0700 Subject: [PATCH 23/72] Fix non-strict-compliant test --- tests/cases/fourslash/localGetReferences.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/cases/fourslash/localGetReferences.ts b/tests/cases/fourslash/localGetReferences.ts index ada4947acde..b05346a366a 100644 --- a/tests/cases/fourslash/localGetReferences.ts +++ b/tests/cases/fourslash/localGetReferences.ts @@ -205,12 +205,13 @@ const rangesByText = test.rangesByText(); for (const text in rangesByText) { const ranges = rangesByText[text]; if (text === "globalVar") { - function isShadow(r) { - return r.marker && r.marker.data && r.marker.data.shadow; - } verify.rangesReferenceEachOther(ranges.filter(isShadow)); verify.rangesReferenceEachOther(ranges.filter(r => !isShadow(r))); } else { verify.rangesReferenceEachOther(ranges); } } + +function isShadow(r) { + return r.marker && r.marker.data && r.marker.data.shadow; +} From 40f0c6018dce1e1aeadec37020827ef6a7b318a3 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Mon, 8 Aug 2016 15:36:38 -0700 Subject: [PATCH 24/72] use transpileModule --- src/harness/fourslash.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 751db7b971a..d0643db4967 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2263,7 +2263,11 @@ namespace FourSlash { // Parse out the files and their metadata const testData = parseTestData(basePath, content, fileName); const state = new TestState(basePath, testType, testData); - runCode(ts.transpile(content), state); + const output = ts.transpileModule(content, { reportDiagnostics: true }); + if (output.diagnostics.length > 0) { + throw new Error(`Syntax error in ${basePath}: ${output.diagnostics[0].messageText}`); + } + runCode(output.outputText, state); } function runCode(code: string, state: TestState): void { From 5adceb92a73c93dbc4dd642dae7398e6b1a4ad7b Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Mon, 8 Aug 2016 15:40:25 -0700 Subject: [PATCH 25/72] Remove use strict --- Jakefile.js | 1 - 1 file changed, 1 deletion(-) diff --git a/Jakefile.js b/Jakefile.js index 8062d8a2928..0c08c0faf4c 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -1,4 +1,3 @@ -"use strict"; // This file contains the build logic for the public repo var fs = require("fs"); From 9d4547c5d08430a87304caac6a8fe164baed4c19 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 9 Aug 2016 16:33:45 -0700 Subject: [PATCH 26/72] Optimize format of type list id strings used in maps --- src/compiler/checker.ts | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1f2eaea833d..ac4ac3d506d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4941,24 +4941,27 @@ namespace ts { } function getTypeListId(types: Type[]) { + let result = ""; if (types) { - switch (types.length) { - case 1: - return "" + types[0].id; - case 2: - return types[0].id + "," + types[1].id; - default: - let result = ""; - for (let i = 0; i < types.length; i++) { - if (i > 0) { - result += ","; - } - result += types[i].id; - } - return result; + const length = types.length; + let i = 0; + while (i < length) { + const startId = types[i].id; + let count = 1; + while (i + count < length && types[i + count].id === startId + count) { + count++; + } + if (result.length) { + result += ","; + } + result += startId; + if (count > 1) { + result += ":" + count; + } + i += count; } } - return ""; + return result; } // This function is used to propagate certain flags when creating new object type references and union types. From 65e1293b2e542165f668191a26dae2d446c54b74 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 10 Aug 2016 16:47:06 -0700 Subject: [PATCH 27/72] Optimize performance of maps --- src/compiler/binder.ts | 27 +- src/compiler/checker.ts | 280 ++++++++---------- src/compiler/commandLineParser.ts | 10 +- src/compiler/core.ts | 48 +-- src/compiler/declarationEmitter.ts | 2 +- src/compiler/emitter.ts | 24 +- src/compiler/parser.ts | 4 +- src/compiler/performance.ts | 4 +- src/compiler/program.ts | 16 +- src/compiler/scanner.ts | 4 +- src/compiler/sys.ts | 29 +- src/compiler/tsc.ts | 8 +- src/compiler/types.ts | 24 +- src/compiler/utilities.ts | 35 +-- src/harness/compilerRunner.ts | 6 +- src/harness/fourslash.ts | 14 +- src/harness/harness.ts | 4 +- src/harness/harnessLanguageService.ts | 6 +- src/harness/projectsRunner.ts | 2 +- .../unittests/cachingInServerLSHost.ts | 6 +- src/harness/unittests/moduleResolution.ts | 30 +- .../unittests/reuseProgramStructure.ts | 10 +- src/harness/unittests/session.ts | 4 +- .../unittests/tsserverProjectSystem.ts | 8 +- src/server/client.ts | 2 +- src/server/editorServices.ts | 16 +- src/server/session.ts | 2 +- src/services/jsTyping.ts | 4 +- src/services/navigationBar.ts | 2 +- src/services/patternMatcher.ts | 2 +- src/services/services.ts | 22 +- 31 files changed, 321 insertions(+), 334 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 8d8666a67ab..c7450ac88fb 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -135,7 +135,7 @@ namespace ts { options = opts; languageVersion = getEmitScriptTarget(options); inStrictMode = !!file.externalModuleIndicator; - classifiableNames = {}; + classifiableNames = createMap(); symbolCount = 0; Symbol = objectAllocator.getSymbolConstructor(); @@ -183,11 +183,11 @@ namespace ts { symbol.declarations.push(node); if (symbolFlags & SymbolFlags.HasExports && !symbol.exports) { - symbol.exports = {}; + symbol.exports = createMap(); } if (symbolFlags & SymbolFlags.HasMembers && !symbol.members) { - symbol.members = {}; + symbol.members = createMap(); } if (symbolFlags & SymbolFlags.Value) { @@ -318,9 +318,7 @@ namespace ts { // Otherwise, we'll be merging into a compatible existing symbol (for example when // you have multiple 'vars' with the same name in the same container). In this case // just add this node into the declarations list of the symbol. - symbol = hasProperty(symbolTable, name) - ? symbolTable[name] - : (symbolTable[name] = createSymbol(SymbolFlags.None, name)); + symbol = symbolTable[name] || (symbolTable[name] = createSymbol(SymbolFlags.None, name)); if (name && (includes & SymbolFlags.Classifiable)) { classifiableNames[name] = name; @@ -434,7 +432,7 @@ namespace ts { if (containerFlags & ContainerFlags.IsContainer) { container = blockScopeContainer = node; if (containerFlags & ContainerFlags.HasLocals) { - container.locals = {}; + container.locals = createMap(); } addToContainerChain(container); } @@ -1399,7 +1397,8 @@ namespace ts { const typeLiteralSymbol = createSymbol(SymbolFlags.TypeLiteral, "__type"); addDeclarationToSymbol(typeLiteralSymbol, node, SymbolFlags.TypeLiteral); - typeLiteralSymbol.members = { [symbol.name]: symbol }; + typeLiteralSymbol.members = createMap(); + typeLiteralSymbol.members[symbol.name] = symbol; } function bindObjectLiteralExpression(node: ObjectLiteralExpression) { @@ -1409,7 +1408,7 @@ namespace ts { } if (inStrictMode) { - const seen: Map = {}; + const seen = createMap(); for (const prop of node.properties) { if (prop.name.kind !== SyntaxKind.Identifier) { @@ -1465,7 +1464,7 @@ namespace ts { // fall through. default: if (!blockScopeContainer.locals) { - blockScopeContainer.locals = {}; + blockScopeContainer.locals = createMap(); addToContainerChain(blockScopeContainer); } declareSymbol(blockScopeContainer.locals, undefined, node, symbolFlags, symbolExcludes); @@ -1925,7 +1924,7 @@ namespace ts { } } - file.symbol.globalExports = file.symbol.globalExports || {}; + file.symbol.globalExports = file.symbol.globalExports || createMap(); declareSymbol(file.symbol.globalExports, file.symbol, node, SymbolFlags.Alias, SymbolFlags.AliasExcludes); } @@ -1978,7 +1977,7 @@ namespace ts { else { return; } - assignee.symbol.members = assignee.symbol.members || {}; + assignee.symbol.members = assignee.symbol.members || createMap(); // It's acceptable for multiple 'this' assignments of the same identifier to occur declareSymbol(assignee.symbol.members, assignee.symbol, node, SymbolFlags.Property, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property); } @@ -2004,7 +2003,7 @@ namespace ts { // Set up the members collection if it doesn't exist already if (!funcSymbol.members) { - funcSymbol.members = {}; + funcSymbol.members = createMap(); } // Declare the method/property @@ -2053,7 +2052,7 @@ namespace ts { // module might have an exported variable called 'prototype'. We can't allow that as // that would clash with the built-in 'prototype' for the class. const prototypeSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Prototype, "prototype"); - if (hasProperty(symbol.exports, prototypeSymbol.name)) { + if (symbol.exports[prototypeSymbol.name]) { if (node.name) { node.name.parent = node; } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c88ab16acd7..6b3fb1076a1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -44,7 +44,7 @@ namespace ts { let symbolCount = 0; const emptyArray: any[] = []; - const emptySymbols: SymbolTable = {}; + const emptySymbols = createMap(); const compilerOptions = host.getCompilerOptions(); const languageVersion = compilerOptions.target || ScriptTarget.ES3; @@ -106,11 +106,11 @@ namespace ts { isOptionalParameter }; - const tupleTypes: Map = {}; - const unionTypes: Map = {}; - const intersectionTypes: Map = {}; - const stringLiteralTypes: Map = {}; - const numericLiteralTypes: Map = {}; + const tupleTypes = createMap(); + const unionTypes = createMap(); + const intersectionTypes = createMap(); + const stringLiteralTypes = createMap(); + const numericLiteralTypes = createMap(); const unknownSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "unknown"); const resolvingSymbol = createSymbol(SymbolFlags.Transient, "__resolving__"); @@ -132,7 +132,7 @@ namespace ts { const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); const emptyGenericType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); - emptyGenericType.instantiations = {}; + emptyGenericType.instantiations = createMap(); const anyFunctionType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); // The anyFunctionType contains the anyFunctionType by definition. The flag is further propagated @@ -146,7 +146,7 @@ namespace ts { const enumNumberIndexInfo = createIndexInfo(stringType, /*isReadonly*/ true); - const globals: SymbolTable = {}; + const globals = createMap(); /** * List of every ambient module with a "*" wildcard. * Unlike other ambient modules, these can't be stored in `globals` because symbol tables only deal with exact matches. @@ -283,7 +283,7 @@ namespace ts { NullFacts = TypeofEQObject | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | TypeofNEHostObject | EQNull | EQUndefinedOrNull | NEUndefined | Falsy, } - const typeofEQFacts: Map = { + const typeofEQFacts: MapLike = { "string": TypeFacts.TypeofEQString, "number": TypeFacts.TypeofEQNumber, "boolean": TypeFacts.TypeofEQBoolean, @@ -293,7 +293,7 @@ namespace ts { "function": TypeFacts.TypeofEQFunction }; - const typeofNEFacts: Map = { + const typeofNEFacts: MapLike = { "string": TypeFacts.TypeofNEString, "number": TypeFacts.TypeofNENumber, "boolean": TypeFacts.TypeofNEBoolean, @@ -303,7 +303,7 @@ namespace ts { "function": TypeFacts.TypeofNEFunction }; - const typeofTypesByName: Map = { + const typeofTypesByName: MapLike = { "string": stringType, "number": numberType, "boolean": booleanType, @@ -313,7 +313,7 @@ namespace ts { let jsxElementType: ObjectType; /** Things we lazy load from the JSX namespace */ - const jsxTypes: Map = {}; + const jsxTypes = createMap(); const JsxNames = { JSX: "JSX", IntrinsicElements: "IntrinsicElements", @@ -324,10 +324,10 @@ namespace ts { IntrinsicClassAttributes: "IntrinsicClassAttributes" }; - const subtypeRelation: Map = {}; - const assignableRelation: Map = {}; - const comparableRelation: Map = {}; - const identityRelation: Map = {}; + const subtypeRelation = createMap(); + const assignableRelation = createMap(); + const comparableRelation = createMap(); + const identityRelation = createMap(); // This is for caching the result of getSymbolDisplayBuilder. Do not access directly. let _displayBuilder: SymbolDisplayBuilder; @@ -341,9 +341,8 @@ namespace ts { ResolvedReturnType } - const builtinGlobals: SymbolTable = { - [undefinedSymbol.name]: undefinedSymbol - }; + const builtinGlobals = createMap(); + builtinGlobals[undefinedSymbol.name] = undefinedSymbol; initializeTypeChecker(); @@ -426,11 +425,11 @@ namespace ts { target.declarations.push(node); }); if (source.members) { - if (!target.members) target.members = {}; + if (!target.members) target.members = createMap(); mergeSymbolTable(target.members, source.members); } if (source.exports) { - if (!target.exports) target.exports = {}; + if (!target.exports) target.exports = createMap(); mergeSymbolTable(target.exports, source.exports); } recordMergedSymbol(target, source); @@ -448,28 +447,24 @@ namespace ts { } function cloneSymbolTable(symbolTable: SymbolTable): SymbolTable { - const result: SymbolTable = {}; + const result = createMap(); for (const id in symbolTable) { - if (hasProperty(symbolTable, id)) { - result[id] = symbolTable[id]; - } + result[id] = symbolTable[id]; } return result; } function mergeSymbolTable(target: SymbolTable, source: SymbolTable) { for (const id in source) { - if (hasProperty(source, id)) { - if (!hasProperty(target, id)) { - target[id] = source[id]; - } - else { - let symbol = target[id]; - if (!(symbol.flags & SymbolFlags.Merged)) { - target[id] = symbol = cloneSymbol(symbol); - } - mergeSymbol(symbol, source[id]); + let targetSymbol = target[id]; + if (!targetSymbol) { + target[id] = source[id]; + } + else { + if (!(targetSymbol.flags & SymbolFlags.Merged)) { + target[id] = targetSymbol = cloneSymbol(targetSymbol); } + mergeSymbol(targetSymbol, source[id]); } } } @@ -513,14 +508,12 @@ namespace ts { function addToSymbolTable(target: SymbolTable, source: SymbolTable, message: DiagnosticMessage) { for (const id in source) { - if (hasProperty(source, id)) { - if (hasProperty(target, id)) { - // Error on redeclarations - forEach(target[id].declarations, addDeclarationDiagnostic(id, message)); - } - else { - target[id] = source[id]; - } + if (target[id]) { + // Error on redeclarations + forEach(target[id].declarations, addDeclarationDiagnostic(id, message)); + } + else { + target[id] = source[id]; } } @@ -545,18 +538,20 @@ namespace ts { } function getSymbol(symbols: SymbolTable, name: string, meaning: SymbolFlags): Symbol { - if (meaning && hasProperty(symbols, name)) { + if (meaning) { const symbol = symbols[name]; - Debug.assert((symbol.flags & SymbolFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); - if (symbol.flags & meaning) { - return symbol; - } - if (symbol.flags & SymbolFlags.Alias) { - const target = resolveAlias(symbol); - // Unknown symbol means an error occurred in alias resolution, treat it as positive answer to avoid cascading errors - if (target === unknownSymbol || target.flags & meaning) { + if (symbol) { + Debug.assert((symbol.flags & SymbolFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); + if (symbol.flags & meaning) { return symbol; } + if (symbol.flags & SymbolFlags.Alias) { + const target = resolveAlias(symbol); + // Unknown symbol means an error occurred in alias resolution, treat it as positive answer to avoid cascading errors + if (target === unknownSymbol || target.flags & meaning) { + return symbol; + } + } } } // return undefined if we can't find a symbol. @@ -744,7 +739,7 @@ namespace ts { // 2. We check === SymbolFlags.Alias in order to check that the symbol is *purely* // an alias. If we used &, we'd be throwing out symbols that have non alias aspects, // which is not the desired behavior. - if (hasProperty(moduleExports, name) && + if (moduleExports[name] && moduleExports[name].flags === SymbolFlags.Alias && getDeclarationOfKind(moduleExports[name], SyntaxKind.ExportSpecifier)) { break; @@ -1101,9 +1096,9 @@ namespace ts { function getExportOfModule(symbol: Symbol, name: string): Symbol { if (symbol.flags & SymbolFlags.Module) { - const exports = getExportsOfSymbol(symbol); - if (hasProperty(exports, name)) { - return resolveSymbol(exports[name]); + const exportedSymbol = getExportsOfSymbol(symbol)[name]; + if (exportedSymbol) { + return resolveSymbol(exportedSymbol); } } } @@ -1421,7 +1416,7 @@ namespace ts { */ function extendExportSymbols(target: SymbolTable, source: SymbolTable, lookupTable?: Map, exportNode?: ExportDeclaration) { for (const id in source) { - if (id !== "default" && !hasProperty(target, id)) { + if (id !== "default" && !target[id]) { target[id] = source[id]; if (lookupTable && exportNode) { lookupTable[id] = { @@ -1429,7 +1424,7 @@ namespace ts { } as ExportCollisionTracker; } } - else if (lookupTable && exportNode && id !== "default" && hasProperty(target, id) && resolveSymbol(target[id]) !== resolveSymbol(source[id])) { + else if (lookupTable && exportNode && id !== "default" && target[id] && resolveSymbol(target[id]) !== resolveSymbol(source[id])) { if (!lookupTable[id].exportsWithDuplicate) { lookupTable[id].exportsWithDuplicate = [exportNode]; } @@ -1455,8 +1450,8 @@ namespace ts { // All export * declarations are collected in an __export symbol by the binder const exportStars = symbol.exports["__export"]; if (exportStars) { - const nestedSymbols: SymbolTable = {}; - const lookupTable: Map = {}; + const nestedSymbols = createMap(); + const lookupTable = createMap(); for (const node of exportStars.declarations) { const resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier); const exportedSymbols = visit(resolvedModule); @@ -1470,7 +1465,7 @@ namespace ts { for (const id in lookupTable) { const { exportsWithDuplicate } = lookupTable[id]; // It's not an error if the file with multiple `export *`s with duplicate names exports a member with that name itself - if (id === "export=" || !(exportsWithDuplicate && exportsWithDuplicate.length) || hasProperty(symbols, id)) { + if (id === "export=" || !(exportsWithDuplicate && exportsWithDuplicate.length) || symbols[id]) { continue; } for (const node of exportsWithDuplicate) { @@ -1576,13 +1571,11 @@ namespace ts { function getNamedMembers(members: SymbolTable): Symbol[] { let result: Symbol[]; for (const id in members) { - if (hasProperty(members, id)) { - if (!isReservedMemberName(id)) { - if (!result) result = []; - const symbol = members[id]; - if (symbolIsValue(symbol)) { - result.push(symbol); - } + if (!isReservedMemberName(id)) { + if (!result) result = []; + const symbol = members[id]; + if (symbolIsValue(symbol)) { + result.push(symbol); } } } @@ -1698,12 +1691,12 @@ namespace ts { let qualify = false; forEachSymbolTableInScope(enclosingDeclaration, symbolTable => { // If symbol of this name is not available in the symbol table we are ok - if (!hasProperty(symbolTable, symbol.name)) { + let symbolFromSymbolTable = symbolTable[symbol.name]; + if (!symbolFromSymbolTable) { // Continue to the next symbol table return false; } // If the symbol with this name is present it should refer to the symbol - let symbolFromSymbolTable = symbolTable[symbol.name]; if (symbolFromSymbolTable === symbol) { // No need to qualify return true; @@ -3121,7 +3114,7 @@ namespace ts { // Return the type implied by an object binding pattern function getTypeFromObjectBindingPattern(pattern: BindingPattern, includePatternInType: boolean): Type { - const members: SymbolTable = {}; + const members = createMap(); let hasComputedProperties = false; forEach(pattern.elements, e => { const name = e.propertyName || e.name; @@ -3709,7 +3702,7 @@ namespace ts { type.typeParameters = concatenate(outerTypeParameters, localTypeParameters); type.outerTypeParameters = outerTypeParameters; type.localTypeParameters = localTypeParameters; - (type).instantiations = {}; + (type).instantiations = createMap(); (type).instantiations[getTypeListId(type.typeParameters)] = type; (type).target = type; (type).typeArguments = type.typeParameters; @@ -3751,7 +3744,7 @@ namespace ts { if (typeParameters) { // Initialize the instantiation cache for generic type aliases. The declared type corresponds to // an instantiation of the type alias with the type parameters supplied as type arguments. - links.instantiations = {}; + links.instantiations = createMap(); links.instantiations[getTypeListId(links.typeParameters)] = type; } } @@ -3772,7 +3765,7 @@ namespace ts { return expr.kind === SyntaxKind.NumericLiteral || expr.kind === SyntaxKind.PrefixUnaryExpression && (expr).operator === SyntaxKind.MinusToken && (expr).operand.kind === SyntaxKind.NumericLiteral || - expr.kind === SyntaxKind.Identifier && hasProperty(symbol.exports, (expr).text); + expr.kind === SyntaxKind.Identifier && !!symbol.exports[(expr).text]; } function enumHasLiteralMembers(symbol: Symbol) { @@ -3795,7 +3788,7 @@ namespace ts { enumType.symbol = symbol; if (enumHasLiteralMembers(symbol)) { const memberTypeList: Type[] = []; - const memberTypes: Map = {}; + const memberTypes = createMap(); for (const declaration of enumType.symbol.declarations) { if (declaration.kind === SyntaxKind.EnumDeclaration) { computeEnumMemberValues(declaration); @@ -3958,7 +3951,7 @@ namespace ts { } function createSymbolTable(symbols: Symbol[]): SymbolTable { - const result: SymbolTable = {}; + const result = createMap(); for (const symbol of symbols) { result[symbol.name] = symbol; } @@ -3968,7 +3961,7 @@ namespace ts { // The mappingThisOnly flag indicates that the only type parameter being mapped is "this". When the flag is true, // we check symbols to see if we can quickly conclude they are free of "this" references, thus needing no instantiation. function createInstantiatedSymbolTable(symbols: Symbol[], mapper: TypeMapper, mappingThisOnly: boolean): SymbolTable { - const result: SymbolTable = {}; + const result = createMap(); for (const symbol of symbols) { result[symbol.name] = mappingThisOnly && isIndependentMember(symbol) ? symbol : instantiateSymbol(symbol, mapper); } @@ -3977,7 +3970,7 @@ namespace ts { function addInheritedMembers(symbols: SymbolTable, baseSymbols: Symbol[]) { for (const s of baseSymbols) { - if (!hasProperty(symbols, s.name)) { + if (!symbols[s.name]) { symbols[s.name] = s; } } @@ -4091,7 +4084,7 @@ namespace ts { } function createTupleTypeMemberSymbols(memberTypes: Type[]): SymbolTable { - const members: SymbolTable = {}; + const members = createMap(); for (let i = 0; i < memberTypes.length; i++) { const symbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "" + i); symbol.type = memberTypes[i]; @@ -4313,11 +4306,9 @@ namespace ts { function getPropertyOfObjectType(type: Type, name: string): Symbol { if (type.flags & TypeFlags.ObjectType) { const resolved = resolveStructuredTypeMembers(type); - if (hasProperty(resolved.members, name)) { - const symbol = resolved.members[name]; - if (symbolIsValue(symbol)) { - return symbol; - } + const symbol = resolved.members[name]; + if (symbol && symbolIsValue(symbol)) { + return symbol; } } } @@ -4446,13 +4437,13 @@ namespace ts { } function getPropertyOfUnionOrIntersectionType(type: UnionOrIntersectionType, name: string): Symbol { - const properties = type.resolvedProperties || (type.resolvedProperties = {}); - if (hasProperty(properties, name)) { - return properties[name]; - } - const property = createUnionOrIntersectionProperty(type, name); - if (property) { - properties[name] = property; + const properties = type.resolvedProperties || (type.resolvedProperties = createMap()); + let property = properties[name]; + if (!property) { + property = createUnionOrIntersectionProperty(type, name); + if (property) { + properties[name] = property; + } } return property; } @@ -4469,11 +4460,9 @@ namespace ts { type = getApparentType(type); if (type.flags & TypeFlags.ObjectType) { const resolved = resolveStructuredTypeMembers(type); - if (hasProperty(resolved.members, name)) { - const symbol = resolved.members[name]; - if (symbolIsValue(symbol)) { - return symbol; - } + const symbol = resolved.members[name]; + if (symbol && symbolIsValue(symbol)) { + return symbol; } if (resolved === anyFunctionType || resolved.callSignatures.length || resolved.constructSignatures.length) { const symbol = getPropertyOfObjectType(globalFunctionType, name); @@ -5467,7 +5456,7 @@ namespace ts { function getLiteralTypeForText(flags: TypeFlags, text: string) { const map = flags & TypeFlags.StringLiteral ? stringLiteralTypes : numericLiteralTypes; - return hasProperty(map, text) ? map[text] : map[text] = createLiteralType(flags, text); + return map[text] || (map[text] = createLiteralType(flags, text)); } function getTypeFromLiteralTypeNode(node: LiteralTypeNode): Type { @@ -6582,7 +6571,7 @@ namespace ts { } sourceStack[depth] = source; targetStack[depth] = target; - maybeStack[depth] = {}; + maybeStack[depth] = createMap(); maybeStack[depth][id] = RelationComparisonResult.Succeeded; depth++; const saveExpandingFlags = expandingFlags; @@ -7223,7 +7212,7 @@ namespace ts { } function transformTypeOfMembers(type: Type, f: (propertyType: Type) => Type) { - const members: SymbolTable = {}; + const members = createMap(); for (const property of getPropertiesOfObjectType(type)) { const original = getTypeOfSymbol(property); const updated = f(original); @@ -7447,7 +7436,7 @@ namespace ts { let targetStack: Type[]; let depth = 0; let inferiority = 0; - const visited: Map = {}; + const visited = createMap(); inferFromTypes(source, target); function isInProcess(source: Type, target: Type) { @@ -7581,7 +7570,7 @@ namespace ts { return; } const key = source.id + "," + target.id; - if (hasProperty(visited, key)) { + if (visited[key]) { return; } visited[key] = true; @@ -8338,7 +8327,7 @@ namespace ts { // If we have previously computed the control flow type for the reference at // this flow loop junction, return the cached type. const id = getFlowNodeId(flow); - const cache = flowLoopCaches[id] || (flowLoopCaches[id] = {}); + const cache = flowLoopCaches[id] || (flowLoopCaches[id] = createMap()); if (!key) { key = getFlowCacheKey(reference); } @@ -9933,7 +9922,7 @@ namespace ts { // Grammar checking checkGrammarObjectLiteralExpression(node, inDestructuringPattern); - const propertiesTable: SymbolTable = {}; + const propertiesTable = createMap(); const propertiesArray: Symbol[] = []; const contextualType = getApparentTypeOfContextualType(node); const contextualTypeHasPattern = contextualType && contextualType.pattern && @@ -10024,7 +10013,7 @@ namespace ts { // type with those properties for which the binding pattern specifies a default value. if (contextualTypeHasPattern) { for (const prop of getPropertiesOfType(contextualType)) { - if (!hasProperty(propertiesTable, prop.name)) { + if (!propertiesTable[prop.name]) { if (!(prop.flags & SymbolFlags.Optional)) { error(prop.valueDeclaration || (prop).bindingElement, Diagnostics.Initializer_provides_no_value_for_this_binding_element_and_the_binding_element_has_no_default_value); @@ -10152,7 +10141,7 @@ namespace ts { for (const prop of props) { // Is there a corresponding property in the element attributes type? Skip checking of properties // that have already been assigned to, as these are not actually pushed into the resulting type - if (!hasProperty(nameTable, prop.name)) { + if (!nameTable[prop.name]) { const targetPropSym = getPropertyOfType(elementAttributesType, prop.name); if (targetPropSym) { const msg = chainDiagnosticMessages(undefined, Diagnostics.Property_0_of_JSX_spread_attribute_is_not_assignable_to_target_property, prop.name); @@ -10474,7 +10463,7 @@ namespace ts { const targetAttributesType = getJsxElementAttributesType(node); - const nameTable: Map = {}; + const nameTable = createMap(); // Process this array in right-to-left order so we know which // attributes (mostly from spreads) are being overwritten and // thus should have their types ignored @@ -10498,7 +10487,7 @@ namespace ts { const targetProperties = getPropertiesOfType(targetAttributesType); for (let i = 0; i < targetProperties.length; i++) { if (!(targetProperties[i].flags & SymbolFlags.Optional) && - !hasProperty(nameTable, targetProperties[i].name)) { + !nameTable[targetProperties[i].name]) { error(node, Diagnostics.Property_0_is_missing_in_type_1, targetProperties[i].name, typeToString(targetAttributesType)); } @@ -13782,8 +13771,8 @@ namespace ts { function checkClassForDuplicateDeclarations(node: ClassLikeDeclaration) { const getter = 1, setter = 2, property = getter | setter; - const instanceNames: Map = {}; - const staticNames: Map = {}; + const instanceNames = createMap(); + const staticNames = createMap(); for (const member of node.members) { if (member.kind === SyntaxKind.Constructor) { for (const param of (member as ConstructorDeclaration).parameters) { @@ -13816,8 +13805,8 @@ namespace ts { } function addName(names: Map, location: Node, name: string, meaning: number) { - if (hasProperty(names, name)) { - const prev = names[name]; + const prev = names[name]; + if (prev) { if (prev & meaning) { error(location, Diagnostics.Duplicate_identifier_0, getTextOfNode(location)); } @@ -13832,7 +13821,7 @@ namespace ts { } function checkObjectTypeForDuplicateDeclarations(node: TypeLiteralNode | InterfaceDeclaration) { - const names: Map = {}; + const names = createMap(); for (const member of node.members) { if (member.kind == SyntaxKind.PropertySignature) { let memberName: string; @@ -13846,7 +13835,7 @@ namespace ts { continue; } - if (hasProperty(names, memberName)) { + if (names[memberName]) { error(member.symbol.valueDeclaration.name, Diagnostics.Duplicate_identifier_0, memberName); error(member.name, Diagnostics.Duplicate_identifier_0, memberName); } @@ -15058,22 +15047,20 @@ namespace ts { function checkUnusedLocalsAndParameters(node: Node): void { if (node.parent.kind !== SyntaxKind.InterfaceDeclaration && noUnusedIdentifiers && !isInAmbientContext(node)) { for (const key in node.locals) { - if (hasProperty(node.locals, key)) { - const local = node.locals[key]; - if (!local.isReferenced) { - if (local.valueDeclaration && local.valueDeclaration.kind === SyntaxKind.Parameter) { - const parameter = local.valueDeclaration; - if (compilerOptions.noUnusedParameters && - !isParameterPropertyDeclaration(parameter) && - !parameterIsThisKeyword(parameter) && - !parameterNameStartsWithUnderscore(parameter)) { - error(local.valueDeclaration.name, Diagnostics._0_is_declared_but_never_used, local.name); - } - } - else if (compilerOptions.noUnusedLocals) { - forEach(local.declarations, d => error(d.name || d, Diagnostics._0_is_declared_but_never_used, local.name)); + const local = node.locals[key]; + if (!local.isReferenced) { + if (local.valueDeclaration && local.valueDeclaration.kind === SyntaxKind.Parameter) { + const parameter = local.valueDeclaration; + if (compilerOptions.noUnusedParameters && + !isParameterPropertyDeclaration(parameter) && + !parameterIsThisKeyword(parameter) && + !parameterNameStartsWithUnderscore(parameter)) { + error(local.valueDeclaration.name, Diagnostics._0_is_declared_but_never_used, local.name); } } + else if (compilerOptions.noUnusedLocals) { + forEach(local.declarations, d => error(d.name || d, Diagnostics._0_is_declared_but_never_used, local.name)); + } } } } @@ -15130,13 +15117,11 @@ namespace ts { function checkUnusedModuleMembers(node: ModuleDeclaration | SourceFile): void { if (compilerOptions.noUnusedLocals && !isInAmbientContext(node)) { for (const key in node.locals) { - if (hasProperty(node.locals, key)) { - const local = node.locals[key]; - if (!local.isReferenced && !local.exportSymbol) { - for (const declaration of local.declarations) { - if (!isAmbientModule(declaration)) { - error(declaration.name, Diagnostics._0_is_declared_but_never_used, local.name); - } + const local = node.locals[key]; + if (!local.isReferenced && !local.exportSymbol) { + for (const declaration of local.declarations) { + if (!isAmbientModule(declaration)) { + error(declaration.name, Diagnostics._0_is_declared_but_never_used, local.name); } } } @@ -16145,7 +16130,7 @@ namespace ts { else { const identifierName = (catchClause.variableDeclaration.name).text; const locals = catchClause.block.locals; - if (locals && hasProperty(locals, identifierName)) { + if (locals) { const localSymbol = locals[identifierName]; if (localSymbol && (localSymbol.flags & SymbolFlags.BlockScopedVariable) !== 0) { grammarErrorOnNode(localSymbol.valueDeclaration, Diagnostics.Cannot_redeclare_identifier_0_in_catch_clause, identifierName); @@ -16566,18 +16551,18 @@ namespace ts { return true; } - const seen: Map<{ prop: Symbol; containingType: Type }> = {}; + const seen = createMap<{ prop: Symbol; containingType: Type }>(); forEach(resolveDeclaredMembers(type).declaredProperties, p => { seen[p.name] = { prop: p, containingType: type }; }); let ok = true; for (const base of baseTypes) { const properties = getPropertiesOfObjectType(getTypeWithThisArgument(base, type.thisType)); for (const prop of properties) { - if (!hasProperty(seen, prop.name)) { + const existing = seen[prop.name]; + if (!existing) { seen[prop.name] = { prop: prop, containingType: base }; } else { - const existing = seen[prop.name]; const isInheritedProperty = existing.containingType !== type; if (isInheritedProperty && !isPropertyIdenticalTo(existing.prop, prop)) { ok = false; @@ -17635,7 +17620,7 @@ namespace ts { } function getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[] { - const symbols: SymbolTable = {}; + const symbols = createMap(); let memberFlags: NodeFlags = 0; if (isInsideWithStatementBody(location)) { @@ -17713,7 +17698,7 @@ namespace ts { // We will copy all symbol regardless of its reserved name because // symbolsToArray will check whether the key is a reserved name and // it will not copy symbol with reserved name to the array - if (!hasProperty(symbols, id)) { + if (!symbols[id]) { symbols[id] = symbol; } } @@ -18134,7 +18119,7 @@ namespace ts { const propsByName = createSymbolTable(getPropertiesOfType(type)); if (getSignaturesOfType(type, SignatureKind.Call).length || getSignaturesOfType(type, SignatureKind.Construct).length) { forEach(getPropertiesOfType(globalFunctionType), p => { - if (!hasProperty(propsByName, p.name)) { + if (!propsByName[p.name]) { propsByName[p.name] = p; } }); @@ -18482,7 +18467,7 @@ namespace ts { } function hasGlobalName(name: string): boolean { - return hasProperty(globals, name); + return !!globals[name]; } function getReferencedValueSymbol(reference: Identifier): Symbol { @@ -18506,9 +18491,6 @@ namespace ts { // populate reverse mapping: file path -> type reference directive that was resolved to this file fileToDirective = createFileMap(); for (const key in resolvedTypeReferenceDirectives) { - if (!hasProperty(resolvedTypeReferenceDirectives, key)) { - continue; - } const resolvedDirective = resolvedTypeReferenceDirectives[key]; if (!resolvedDirective) { continue; @@ -19297,7 +19279,7 @@ namespace ts { } function checkGrammarObjectLiteralExpression(node: ObjectLiteralExpression, inDestructuring: boolean) { - const seen: Map = {}; + const seen = createMap(); const Property = 1; const GetAccessor = 2; const SetAccessor = 4; @@ -19359,7 +19341,7 @@ namespace ts { continue; } - if (!hasProperty(seen, effectiveName)) { + if (!seen[effectiveName]) { seen[effectiveName] = currentKind; } else { @@ -19383,7 +19365,7 @@ namespace ts { } function checkGrammarJsxElement(node: JsxOpeningLikeElement) { - const seen: Map = {}; + const seen = createMap(); for (const attr of node.attributes) { if (attr.kind === SyntaxKind.JsxSpreadAttribute) { continue; @@ -19391,7 +19373,7 @@ namespace ts { const jsxAttr = (attr); const name = jsxAttr.name; - if (!hasProperty(seen, name.text)) { + if (!seen[name.text]) { seen[name.text] = true; } else { diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 7e2c6eb8d33..eaf17f00dd0 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -470,8 +470,8 @@ namespace ts { return optionNameMapCache; } - const optionNameMap: Map = {}; - const shortOptionNames: Map = {}; + const optionNameMap = createMap(); + const shortOptionNames = createMap(); forEach(optionDeclarations, option => { optionNameMap[option.name.toLowerCase()] = option; if (option.shortName) { @@ -958,12 +958,12 @@ namespace ts { // Literal file names (provided via the "files" array in tsconfig.json) are stored in a // file map with a possibly case insensitive key. We use this map later when when including // wildcard paths. - const literalFileMap: Map = {}; + const literalFileMap = createMap(); // Wildcard paths (provided via the "includes" array in tsconfig.json) are stored in a // file map with a possibly case insensitive key. We use this map to store paths matched // via wildcard, and to handle extension priority. - const wildcardFileMap: Map = {}; + const wildcardFileMap = createMap(); if (include) { include = validateSpecs(include, errors, /*allowTrailingRecursion*/ false); @@ -1063,7 +1063,7 @@ namespace ts { // /a/b/a?z - Watch /a/b directly to catch any new file matching a?z const rawExcludeRegex = getRegularExpressionForWildcard(exclude, path, "exclude"); const excludeRegex = rawExcludeRegex && new RegExp(rawExcludeRegex, useCaseSensitiveFileNames ? "" : "i"); - const wildcardDirectories: Map = {}; + const wildcardDirectories = createMap(); if (include !== undefined) { const recursiveKeys: string[] = []; for (const file of include) { diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 6c87ad82955..db88305d2c5 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -19,8 +19,24 @@ namespace ts { True = -1 } + const createObject = Object.create; + + export function createMap(): Map { + /* tslint:disable:no-null-keyword */ + const map: Map = createObject(null); + /* tslint:enable:no-null-keyword */ + + // Using 'delete' on an object causes V8 to put the object in dictionary mode. + // This disables creation of hidden classes, which are expensive when an object is + // constantly changing shape. + map["__"] = undefined; + delete map["__"]; + + return map; + } + export function createFileMap(keyMapper?: (key: string) => string): FileMap { - let files: Map = {}; + let files = createMap(); return { get, set, @@ -55,7 +71,7 @@ namespace ts { } function clear() { - files = {}; + files = createMap(); } function toKey(path: Path): string { @@ -311,11 +327,11 @@ namespace ts { const hasOwnProperty = Object.prototype.hasOwnProperty; - export function hasProperty(map: Map, key: string): boolean { + export function hasProperty(map: MapLike, key: string): boolean { return hasOwnProperty.call(map, key); } - export function getKeys(map: Map): string[] { + export function getKeys(map: MapLike): string[] { const keys: string[] = []; for (const key in map) { keys.push(key); @@ -323,15 +339,15 @@ namespace ts { return keys; } - export function getProperty(map: Map, key: string): T | undefined { + export function getProperty(map: MapLike, key: string): T | undefined { return hasProperty(map, key) ? map[key] : undefined; } - export function getOrUpdateProperty(map: Map, key: string, makeValue: () => T): T { + export function getOrUpdateProperty(map: MapLike, key: string, makeValue: () => T): T { return hasProperty(map, key) ? map[key] : map[key] = makeValue(); } - export function isEmpty(map: Map) { + export function isEmpty(map: MapLike) { for (const id in map) { if (hasProperty(map, id)) { return false; @@ -348,7 +364,7 @@ namespace ts { return result; } - export function extend, T2 extends Map<{}>>(first: T1 , second: T2): T1 & T2 { + export function extend, T2 extends MapLike<{}>>(first: T1 , second: T2): T1 & T2 { const result: T1 & T2 = {}; for (const id in first) { (result as any)[id] = first[id]; @@ -361,7 +377,7 @@ namespace ts { return result; } - export function forEachValue(map: Map, callback: (value: T) => U): U { + export function forEachValue(map: MapLike, callback: (value: T) => U): U { let result: U; for (const id in map) { if (result = callback(map[id])) break; @@ -369,7 +385,7 @@ namespace ts { return result; } - export function forEachKey(map: Map, callback: (key: string) => U): U { + export function forEachKey(map: MapLike, callback: (key: string) => U): U { let result: U; for (const id in map) { if (result = callback(id)) break; @@ -377,11 +393,11 @@ namespace ts { return result; } - export function lookUp(map: Map, key: string): T { + export function lookUp(map: MapLike, key: string): T { return hasProperty(map, key) ? map[key] : undefined; } - export function copyMap(source: Map, target: Map): void { + export function copyMap(source: MapLike, target: MapLike): void { for (const p in source) { target[p] = source[p]; } @@ -398,7 +414,7 @@ namespace ts { * index in the array will be the one associated with the produced key. */ export function arrayToMap(array: T[], makeKey: (value: T) => string): Map { - const result: Map = {}; + const result = createMap(); forEach(array, value => { result[makeKey(value)] = value; @@ -414,7 +430,7 @@ namespace ts { * @param callback An aggregation function that is called for each entry in the map * @param initial The initial value for the reduction. */ - export function reduceProperties(map: Map, callback: (aggregate: U, value: T, key: string) => U, initial: U): U { + export function reduceProperties(map: MapLike, callback: (aggregate: U, value: T, key: string) => U, initial: U): U { let result = initial; if (map) { for (const key in map) { @@ -454,9 +470,7 @@ namespace ts { export let localizedDiagnosticMessages: Map = undefined; export function getLocaleSpecificMessage(message: DiagnosticMessage) { - return localizedDiagnosticMessages && localizedDiagnosticMessages[message.key] - ? localizedDiagnosticMessages[message.key] - : message.message; + return localizedDiagnosticMessages && localizedDiagnosticMessages[message.key] || message.message; } export function createFileDiagnostic(file: SourceFile, start: number, length: number, message: DiagnosticMessage, ...args: any[]): Diagnostic; diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index d93a8a0aed0..113b79f33d4 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -269,7 +269,7 @@ namespace ts { } if (!usedTypeDirectiveReferences) { - usedTypeDirectiveReferences = {}; + usedTypeDirectiveReferences = createMap(); } for (const directive of typeReferenceDirectives) { if (!hasProperty(usedTypeDirectiveReferences, directive)) { diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index c8bd8072b24..1d62a4a4f46 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -24,7 +24,7 @@ namespace ts { Return = 1 << 3 } - const entities: Map = { + const entities: MapLike = { "quot": 0x0022, "amp": 0x0026, "apos": 0x0027, @@ -489,13 +489,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge function setLabeledJump(state: ConvertedLoopState, isBreak: boolean, labelText: string, labelMarker: string): void { if (isBreak) { if (!state.labeledNonLocalBreaks) { - state.labeledNonLocalBreaks = {}; + state.labeledNonLocalBreaks = createMap(); } state.labeledNonLocalBreaks[labelText] = labelMarker; } else { if (!state.labeledNonLocalContinues) { - state.labeledNonLocalContinues = {}; + state.labeledNonLocalContinues = createMap(); } state.labeledNonLocalContinues[labelText] = labelMarker; } @@ -531,7 +531,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge let currentText: string; let currentLineMap: number[]; let currentFileIdentifiers: Map; - let renamedDependencies: Map; + let renamedDependencies: MapLike; let isEs6Module: boolean; let isCurrentFileExternalModule: boolean; @@ -577,7 +577,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge const setSourceMapWriterEmit = compilerOptions.sourceMap || compilerOptions.inlineSourceMap ? changeSourceMapEmit : function (writer: SourceMapWriter) { }; - const moduleEmitDelegates: Map<(node: SourceFile, emitRelativePathAsModuleName?: boolean) => void> = { + const moduleEmitDelegates: MapLike<(node: SourceFile, emitRelativePathAsModuleName?: boolean) => void> = { [ModuleKind.ES6]: emitES6Module, [ModuleKind.AMD]: emitAMDModule, [ModuleKind.System]: emitSystemModule, @@ -585,7 +585,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge [ModuleKind.CommonJS]: emitCommonJSModule, }; - const bundleEmitDelegates: Map<(node: SourceFile, emitRelativePathAsModuleName?: boolean) => void> = { + const bundleEmitDelegates: MapLike<(node: SourceFile, emitRelativePathAsModuleName?: boolean) => void> = { [ModuleKind.ES6]() {}, [ModuleKind.AMD]: emitAMDModule, [ModuleKind.System]: emitSystemModule, @@ -597,7 +597,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge function doEmit(jsFilePath: string, sourceMapFilePath: string, sourceFiles: SourceFile[], isBundledEmit: boolean) { sourceMap.initialize(jsFilePath, sourceMapFilePath, sourceFiles, isBundledEmit); - generatedNameSet = {}; + generatedNameSet = createMap(); nodeToGeneratedName = []; decoratedClassAliases = []; isOwnFileEmit = !isBundledEmit; @@ -3257,7 +3257,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge // Don't initialize seen unless we have at least one element. // Emit a comma to separate for all but the first element. if (!seen) { - seen = {}; + seen = createMap(); } else { write(", "); @@ -3856,7 +3856,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge if (convertedLoopState) { if (!convertedLoopState.labels) { - convertedLoopState.labels = {}; + convertedLoopState.labels = createMap(); } convertedLoopState.labels[node.label.text] = node.label.text; } @@ -6803,7 +6803,7 @@ const _super = (function (geti, seti) { function collectExternalModuleInfo(sourceFile: SourceFile) { externalImports = []; - exportSpecifiers = {}; + exportSpecifiers = createMap(); exportEquals = undefined; hasExportStarsToExportValues = false; for (const node of sourceFile.statements) { @@ -7081,7 +7081,7 @@ const _super = (function (geti, seti) { if (hoistedVars) { writeLine(); write("var "); - const seen: Map = {}; + const seen = createMap(); for (let i = 0; i < hoistedVars.length; i++) { const local = hoistedVars[i]; const name = local.kind === SyntaxKind.Identifier @@ -7447,7 +7447,7 @@ const _super = (function (geti, seti) { writeModuleName(node, emitRelativePathAsModuleName); write("["); - const groupIndices: Map = {}; + const groupIndices = createMap(); const dependencyGroups: DependencyGroup[] = []; for (let i = 0; i < externalImports.length; i++) { diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 014d0926213..6f38783d5ed 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -595,7 +595,7 @@ namespace ts { parseDiagnostics = []; parsingContext = 0; - identifiers = {}; + identifiers = createMap(); identifierCount = 0; nodeCount = 0; @@ -1084,7 +1084,7 @@ namespace ts { function internIdentifier(text: string): string { text = escapeIdentifier(text); - return hasProperty(identifiers, text) ? identifiers[text] : (identifiers[text] = text); + return identifiers[text] || (identifiers[text] = text); } // An identifier that starts with two underscores has an extra underscore character prepended to it to avoid issues diff --git a/src/compiler/performance.ts b/src/compiler/performance.ts index 89db876ae5e..e496353fde8 100644 --- a/src/compiler/performance.ts +++ b/src/compiler/performance.ts @@ -10,8 +10,8 @@ namespace ts.performance { /** Performance measurements for the compiler. */ declare const onProfilerEvent: { (markName: string): void; profiler: boolean; }; let profilerEvent: (markName: string) => void; - let counters: Map; - let measures: Map; + let counters: MapLike; + let measures: MapLike; /** * Emit a performance event if ts-profiler is connected. This is primarily used diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 7d40b2f3219..c26e748da9a 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -846,7 +846,7 @@ namespace ts { } export function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost { - const existingDirectories: Map = {}; + const existingDirectories = createMap(); function getCanonicalFileName(fileName: string): string { // if underlying system can distinguish between two files whose names differs only in cases then file name already in canonical form. @@ -899,7 +899,7 @@ namespace ts { function writeFileIfUpdated(fileName: string, data: string, writeByteOrderMark: boolean): void { if (!outputFingerprints) { - outputFingerprints = {}; + outputFingerprints = createMap(); } const hash = sys.createHash(data); @@ -1040,7 +1040,7 @@ namespace ts { return []; } const resolutions: T[] = []; - const cache: Map = {}; + const cache = createMap(); for (const name of names) { let result: T; if (hasProperty(cache, name)) { @@ -1095,7 +1095,7 @@ namespace ts { let noDiagnosticsTypeChecker: TypeChecker; let classifiableNames: Map; - let resolvedTypeReferenceDirectives: Map = {}; + let resolvedTypeReferenceDirectives = createMap(); let fileProcessingDiagnostics = createDiagnosticCollection(); // The below settings are to track if a .js file should be add to the program if loaded via searching under node_modules. @@ -1110,10 +1110,10 @@ namespace ts { // If a module has some of its imports skipped due to being at the depth limit under node_modules, then track // this, as it may be imported at a shallower depth later, and then it will need its skipped imports processed. - const modulesWithElidedImports: Map = {}; + const modulesWithElidedImports = createMap(); // Track source files that are source files found by searching under node_modules, as these shouldn't be compiled. - const sourceFilesFoundSearchingNodeModules: Map = {}; + const sourceFilesFoundSearchingNodeModules = createMap(); const start = performance.mark(); @@ -1241,7 +1241,7 @@ namespace ts { if (!classifiableNames) { // Initialize a checker so that all our files are bound. getTypeChecker(); - classifiableNames = {}; + classifiableNames = createMap(); for (const sourceFile of files) { copyMap(sourceFile.classifiableNames, classifiableNames); @@ -2082,7 +2082,7 @@ namespace ts { function processImportedModules(file: SourceFile, basePath: string) { collectExternalModuleReferences(file); if (file.imports.length || file.moduleAugmentations.length) { - file.resolvedModules = {}; + file.resolvedModules = createMap(); const moduleNames = map(concatenate(file.imports, file.moduleAugmentations), getTextOfLiteral); const resolutions = resolveModuleNamesWorker(moduleNames, getNormalizedAbsolutePath(file.fileName, currentDirectory)); for (let i = 0; i < moduleNames.length; i++) { diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 6dd84298008..134dc489b2a 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -55,7 +55,7 @@ namespace ts { tryScan(callback: () => T): T; } - const textToToken: Map = { + const textToToken: MapLike = { "abstract": SyntaxKind.AbstractKeyword, "any": SyntaxKind.AnyKeyword, "as": SyntaxKind.AsKeyword, @@ -271,7 +271,7 @@ namespace ts { lookupInUnicodeMap(code, unicodeES3IdentifierPart); } - function makeReverseMap(source: Map): string[] { + function makeReverseMap(source: MapLike): string[] { const result: string[] = []; for (const name in source) { if (source.hasOwnProperty(name)) { diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 29ae2c60af1..350d75429b7 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -233,15 +233,15 @@ namespace ts { const useNonPollingWatchers = process.env["TSC_NONPOLLING_WATCHER"]; function createWatchedFileSet() { - const dirWatchers: Map = {}; + const dirWatchers = createMap(); // One file can have multiple watchers - const fileWatcherCallbacks: Map = {}; + const fileWatcherCallbacks = createMap(); return { addFile, removeFile }; function reduceDirWatcherRefCountForFile(fileName: string) { const dirName = getDirectoryPath(fileName); - if (hasProperty(dirWatchers, dirName)) { - const watcher = dirWatchers[dirName]; + const watcher = dirWatchers[dirName]; + if (watcher) { watcher.referenceCount -= 1; if (watcher.referenceCount <= 0) { watcher.close(); @@ -251,13 +251,12 @@ namespace ts { } function addDirWatcher(dirPath: string): void { - if (hasProperty(dirWatchers, dirPath)) { - const watcher = dirWatchers[dirPath]; + let watcher = dirWatchers[dirPath]; + if (watcher) { watcher.referenceCount += 1; return; } - - const watcher: DirectoryWatcher = _fs.watch( + watcher = _fs.watch( dirPath, { persistent: true }, (eventName: string, relativeFileName: string) => fileEventHandler(eventName, relativeFileName, dirPath) @@ -268,12 +267,7 @@ namespace ts { } function addFileWatcherCallback(filePath: string, callback: FileWatcherCallback): void { - if (hasProperty(fileWatcherCallbacks, filePath)) { - fileWatcherCallbacks[filePath].push(callback); - } - else { - fileWatcherCallbacks[filePath] = [callback]; - } + (fileWatcherCallbacks[filePath] || (fileWatcherCallbacks[filePath] = [])).push(callback); } function addFile(fileName: string, callback: FileWatcherCallback): WatchedFile { @@ -289,8 +283,9 @@ namespace ts { } function removeFileWatcherCallback(filePath: string, callback: FileWatcherCallback) { - if (hasProperty(fileWatcherCallbacks, filePath)) { - const newCallbacks = copyListRemovingItem(callback, fileWatcherCallbacks[filePath]); + const callbacks = fileWatcherCallbacks[filePath]; + if (callbacks) { + const newCallbacks = copyListRemovingItem(callback, callbacks); if (newCallbacks.length === 0) { delete fileWatcherCallbacks[filePath]; } @@ -306,7 +301,7 @@ namespace ts { ? undefined : ts.getNormalizedAbsolutePath(relativeFileName, baseDirPath); // Some applications save a working file via rename operations - if ((eventName === "change" || eventName === "rename") && hasProperty(fileWatcherCallbacks, fileName)) { + if ((eventName === "change" || eventName === "rename") && fileWatcherCallbacks[fileName]) { for (const fileCallback of fileWatcherCallbacks[fileName]) { fileCallback(fileName); } diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 10538d0c009..3b588edd101 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -122,7 +122,7 @@ namespace ts { const gutterSeparator = " "; const resetEscapeSequence = "\u001b[0m"; const ellipsis = "..."; - const categoryFormatMap: Map = { + const categoryFormatMap: MapLike = { [DiagnosticCategory.Warning]: yellowForegroundEscapeSequence, [DiagnosticCategory.Error]: redForegroundEscapeSequence, [DiagnosticCategory.Message]: blueForegroundEscapeSequence, @@ -432,7 +432,7 @@ namespace ts { } // reset the cache of existing files - cachedExistingFiles = {}; + cachedExistingFiles = createMap(); const compileResult = compile(rootFileNames, compilerOptions, compilerHost); @@ -676,7 +676,7 @@ namespace ts { const usageColumn: string[] = []; // Things like "-d, --declaration" go in here. const descriptionColumn: string[] = []; - const optionsDescriptionMap: Map = {}; // Map between option.description and list of option.type if it is a kind + const optionsDescriptionMap = createMap(); // Map between option.description and list of option.type if it is a kind for (let i = 0; i < optsList.length; i++) { const option = optsList[i]; @@ -786,7 +786,7 @@ namespace ts { return; function serializeCompilerOptions(options: CompilerOptions): Map { - const result: Map = {}; + const result = createMap(); const optionsNameMap = getOptionNameMap().optionNameMap; for (const name in options) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index a6e860450c6..dad60775baf 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1,9 +1,13 @@ - namespace ts { - export interface Map { + + export interface MapLike { [index: string]: T; } + export interface Map extends MapLike { + __mapBrand: any; + } + // branded string type used to store absolute, normalized and canonicalized paths // arbitrary file name can be converted to Path via toPath function export type Path = string & { __pathBrand: any }; @@ -1640,7 +1644,7 @@ namespace ts { // this map is used by transpiler to supply alternative names for dependencies (i.e. in case of bundling) /* @internal */ - renamedDependencies?: Map; + renamedDependencies?: MapLike; /** * lib.d.ts should have a reference comment like @@ -2172,9 +2176,7 @@ namespace ts { /* @internal */ export interface TransientSymbol extends Symbol, SymbolLinks { } - export interface SymbolTable { - [index: string]: Symbol; - } + export type SymbolTable = Map; /** Represents a "prefix*suffix" pattern. */ /* @internal */ @@ -2557,7 +2559,7 @@ namespace ts { } export type RootPaths = string[]; - export type PathSubstitutions = Map; + export type PathSubstitutions = MapLike; export type TsConfigOnlyOptions = RootPaths | PathSubstitutions; export type CompilerOptionsValue = string | number | boolean | (string | number)[] | TsConfigOnlyOptions; @@ -2717,7 +2719,7 @@ namespace ts { fileNames: string[]; raw?: any; errors: Diagnostic[]; - wildcardDirectories?: Map; + wildcardDirectories?: MapLike; } export const enum WatchDirectoryFlags { @@ -2727,13 +2729,13 @@ namespace ts { export interface ExpandResult { fileNames: string[]; - wildcardDirectories: Map; + wildcardDirectories: MapLike; } /* @internal */ export interface CommandLineOptionBase { name: string; - type: "string" | "number" | "boolean" | "object" | "list" | Map; // a value of a primitive type, or an object literal mapping named values to actual values + type: "string" | "number" | "boolean" | "object" | "list" | MapLike; // a value of a primitive type, or an object literal mapping named values to actual values isFilePath?: boolean; // True if option value is a path or fileName shortName?: string; // A short mnemonic for convenience - for instance, 'h' can be used in place of 'help' description?: DiagnosticMessage; // The message describing what the command line switch does @@ -2749,7 +2751,7 @@ namespace ts { /* @internal */ export interface CommandLineOptionOfCustomType extends CommandLineOptionBase { - type: Map; // an object literal mapping named values to actual values + type: MapLike; // an object literal mapping named values to actual values } /* @internal */ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 2cfaf1f5cfb..e0b3700c6f1 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -87,14 +87,14 @@ namespace ts { return node.end - node.pos; } - export function mapIsEqualTo(map1: Map, map2: Map): boolean { + export function mapIsEqualTo(map1: MapLike, map2: MapLike): boolean { if (!map1 || !map2) { return map1 === map2; } return containsAll(map1, map2) && containsAll(map2, map1); } - function containsAll(map: Map, other: Map): boolean { + function containsAll(map: MapLike, other: MapLike): boolean { for (const key in map) { if (!hasProperty(map, key)) { continue; @@ -126,7 +126,7 @@ namespace ts { } export function hasResolvedModule(sourceFile: SourceFile, moduleNameText: string): boolean { - return sourceFile.resolvedModules && hasProperty(sourceFile.resolvedModules, moduleNameText); + return !!(sourceFile.resolvedModules && sourceFile.resolvedModules[moduleNameText]); } export function getResolvedModule(sourceFile: SourceFile, moduleNameText: string): ResolvedModule { @@ -135,7 +135,7 @@ namespace ts { export function setResolvedModule(sourceFile: SourceFile, moduleNameText: string, resolvedModule: ResolvedModule): void { if (!sourceFile.resolvedModules) { - sourceFile.resolvedModules = {}; + sourceFile.resolvedModules = createMap(); } sourceFile.resolvedModules[moduleNameText] = resolvedModule; @@ -143,7 +143,7 @@ namespace ts { export function setResolvedTypeReferenceDirective(sourceFile: SourceFile, typeReferenceDirectiveName: string, resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective): void { if (!sourceFile.resolvedTypeReferenceDirectiveNames) { - sourceFile.resolvedTypeReferenceDirectiveNames = {}; + sourceFile.resolvedTypeReferenceDirectiveNames = createMap(); } sourceFile.resolvedTypeReferenceDirectiveNames[typeReferenceDirectiveName] = resolvedTypeReferenceDirective; @@ -166,7 +166,7 @@ namespace ts { } for (let i = 0; i < names.length; i++) { const newResolution = newResolutions[i]; - const oldResolution = oldResolutions && hasProperty(oldResolutions, names[i]) ? oldResolutions[names[i]] : undefined; + const oldResolution = oldResolutions && oldResolutions[names[i]]; const changed = oldResolution ? !newResolution || !comparer(oldResolution, newResolution) @@ -1966,7 +1966,7 @@ namespace ts { export function createDiagnosticCollection(): DiagnosticCollection { let nonFileDiagnostics: Diagnostic[] = []; - const fileDiagnostics: Map = {}; + const fileDiagnostics = createMap(); let diagnosticsModified = false; let modificationCount = 0; @@ -1984,12 +1984,11 @@ namespace ts { } function reattachFileDiagnostics(newFile: SourceFile): void { - if (!hasProperty(fileDiagnostics, newFile.fileName)) { - return; - } - - for (const diagnostic of fileDiagnostics[newFile.fileName]) { - diagnostic.file = newFile; + const diagnostics = fileDiagnostics[newFile.fileName]; + if (diagnostics) { + for (const diagnostic of diagnostics) { + diagnostic.file = newFile; + } } } @@ -2030,9 +2029,7 @@ namespace ts { forEach(nonFileDiagnostics, pushDiagnostic); for (const key in fileDiagnostics) { - if (hasProperty(fileDiagnostics, key)) { - forEach(fileDiagnostics[key], pushDiagnostic); - } + forEach(fileDiagnostics[key], pushDiagnostic); } return sortAndDeduplicateDiagnostics(allDiagnostics); @@ -2047,9 +2044,7 @@ namespace ts { nonFileDiagnostics = sortAndDeduplicateDiagnostics(nonFileDiagnostics); for (const key in fileDiagnostics) { - if (hasProperty(fileDiagnostics, key)) { - fileDiagnostics[key] = sortAndDeduplicateDiagnostics(fileDiagnostics[key]); - } + fileDiagnostics[key] = sortAndDeduplicateDiagnostics(fileDiagnostics[key]); } } } @@ -2060,7 +2055,7 @@ namespace ts { // the map below must be updated. Note that this regexp *does not* include the 'delete' character. // There is no reason for this other than that JSON.stringify does not handle it either. const escapedCharsRegExp = /[\\\"\u0000-\u001f\t\v\f\b\r\n\u2028\u2029\u0085]/g; - const escapedCharsMap: Map = { + const escapedCharsMap: MapLike = { "\0": "\\0", "\t": "\\t", "\v": "\\v", diff --git a/src/harness/compilerRunner.ts b/src/harness/compilerRunner.ts index ecdc18394b0..5d4bdf7db32 100644 --- a/src/harness/compilerRunner.ts +++ b/src/harness/compilerRunner.ts @@ -291,8 +291,8 @@ class CompilerBaselineRunner extends RunnerBase { const fullWalker = new TypeWriterWalker(program, /*fullTypeCheck*/ true); - const fullResults: ts.Map = {}; - const pullResults: ts.Map = {}; + const fullResults: ts.MapLike = {}; + const pullResults: ts.MapLike = {}; for (const sourceFile of allFiles) { fullResults[sourceFile.unitName] = fullWalker.getTypeAndSymbols(sourceFile.unitName); @@ -338,7 +338,7 @@ class CompilerBaselineRunner extends RunnerBase { } } - function generateBaseLine(typeWriterResults: ts.Map, isSymbolBaseline: boolean): string { + function generateBaseLine(typeWriterResults: ts.MapLike, isSymbolBaseline: boolean): string { const typeLines: string[] = []; const typeMap: { [fileName: string]: { [lineNum: number]: string[]; } } = {}; diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index a42abbbc609..fed7751eb4a 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -95,7 +95,7 @@ namespace FourSlash { export import IndentStyle = ts.IndentStyle; - const entityMap: ts.Map = { + const entityMap: ts.MapLike = { "&": "&", "\"": """, "'": "'", @@ -204,7 +204,7 @@ namespace FourSlash { public formatCodeOptions: ts.FormatCodeOptions; - private inputFiles: ts.Map = {}; // Map between inputFile's fileName and its content for easily looking up when resolving references + private inputFiles: ts.MapLike = {}; // Map between inputFile's fileName and its content for easily looking up when resolving references // Add input file which has matched file name with the given reference-file path. // This is necessary when resolveReference flag is specified @@ -593,7 +593,7 @@ namespace FourSlash { public noItemsWithSameNameButDifferentKind(): void { const completions = this.getCompletionListAtCaret(); - const uniqueItems: ts.Map = {}; + const uniqueItems: ts.MapLike = {}; for (const item of completions.entries) { if (!ts.hasProperty(uniqueItems, item.name)) { uniqueItems[item.name] = item.kind; @@ -1638,8 +1638,8 @@ namespace FourSlash { return this.testData.ranges; } - public rangesByText(): ts.Map { - const result: ts.Map = {}; + public rangesByText(): ts.MapLike { + const result: ts.MapLike = {}; for (const range of this.getRanges()) { const text = this.rangeText(range); (ts.getProperty(result, text) || (result[text] = [])).push(range); @@ -1897,7 +1897,7 @@ namespace FourSlash { public verifyBraceCompletionAtPosition(negative: boolean, openingBrace: string) { - const openBraceMap: ts.Map = { + const openBraceMap: ts.MapLike = { "(": ts.CharacterCodes.openParen, "{": ts.CharacterCodes.openBrace, "[": ts.CharacterCodes.openBracket, @@ -2772,7 +2772,7 @@ namespace FourSlashInterface { return this.state.getRanges(); } - public rangesByText(): ts.Map { + public rangesByText(): ts.MapLike { return this.state.rangesByText(); } diff --git a/src/harness/harness.ts b/src/harness/harness.ts index e8e0a11f615..5ae33303d55 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -848,7 +848,7 @@ namespace Harness { export const defaultLibFileName = "lib.d.ts"; export const es2015DefaultLibFileName = "lib.es2015.d.ts"; - const libFileNameSourceFileMap: ts.Map = { + const libFileNameSourceFileMap: ts.MapLike = { [defaultLibFileName]: createSourceFileAndAssertInvariants(defaultLibFileName, IO.readFile(libFolder + "lib.es5.d.ts"), /*languageVersion*/ ts.ScriptTarget.Latest) }; @@ -1002,7 +1002,7 @@ namespace Harness { { name: "symlink", type: "string" } ]; - let optionsIndex: ts.Map; + let optionsIndex: ts.MapLike; function getCommandLineOption(name: string): ts.CommandLineOption { if (!optionsIndex) { optionsIndex = {}; diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index d7ed04b627f..4f95f19e144 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -123,7 +123,7 @@ namespace Harness.LanguageService { } export class LanguageServiceAdapterHost { - protected fileNameToScript: ts.Map = {}; + protected fileNameToScript: ts.MapLike = {}; constructor(protected cancellationToken = DefaultHostCancellationToken.Instance, protected settings = ts.getDefaultCompilerOptions()) { @@ -235,7 +235,7 @@ namespace Harness.LanguageService { this.getModuleResolutionsForFile = (fileName) => { const scriptInfo = this.getScriptInfo(fileName); const preprocessInfo = ts.preProcessFile(scriptInfo.content, /*readImportFiles*/ true); - const imports: ts.Map = {}; + const imports: ts.MapLike = {}; for (const module of preprocessInfo.importedFiles) { const resolutionInfo = ts.resolveModuleName(module.fileName, fileName, compilerOptions, moduleResolutionHost); if (resolutionInfo.resolvedModule) { @@ -248,7 +248,7 @@ namespace Harness.LanguageService { const scriptInfo = this.getScriptInfo(fileName); if (scriptInfo) { const preprocessInfo = ts.preProcessFile(scriptInfo.content, /*readImportFiles*/ false); - const resolutions: ts.Map = {}; + const resolutions: ts.MapLike = {}; const settings = this.nativeHost.getCompilationSettings(); for (const typeReferenceDirective of preprocessInfo.typeReferenceDirectives) { const resolutionInfo = ts.resolveTypeReferenceDirective(typeReferenceDirective.fileName, fileName, settings, moduleResolutionHost); diff --git a/src/harness/projectsRunner.ts b/src/harness/projectsRunner.ts index 038b2dbe359..be3030a6dd6 100644 --- a/src/harness/projectsRunner.ts +++ b/src/harness/projectsRunner.ts @@ -253,7 +253,7 @@ class ProjectRunner extends RunnerBase { moduleResolution: ts.ModuleResolutionKind.Classic, // currently all tests use classic module resolution kind, this will change in the future }; // Set the values specified using json - const optionNameMap: ts.Map = {}; + const optionNameMap: ts.MapLike = {}; ts.forEach(ts.optionDeclarations, option => { optionNameMap[option.name] = option; }); diff --git a/src/harness/unittests/cachingInServerLSHost.ts b/src/harness/unittests/cachingInServerLSHost.ts index e0ce09391fb..a1e010b97df 100644 --- a/src/harness/unittests/cachingInServerLSHost.ts +++ b/src/harness/unittests/cachingInServerLSHost.ts @@ -6,8 +6,8 @@ namespace ts { content: string; } - function createDefaultServerHost(fileMap: Map): server.ServerHost { - const existingDirectories: Map = {}; + function createDefaultServerHost(fileMap: MapLike): server.ServerHost { + const existingDirectories: MapLike = {}; forEachValue(fileMap, v => { let dir = getDirectoryPath(v.name); let previous: string; @@ -193,7 +193,7 @@ namespace ts { content: `export var y = 1` }; - const fileMap: Map = { [root.name]: root }; + const fileMap: MapLike = { [root.name]: root }; const serverHost = createDefaultServerHost(fileMap); const originalFileExists = serverHost.fileExists; diff --git a/src/harness/unittests/moduleResolution.ts b/src/harness/unittests/moduleResolution.ts index 5f63889b084..4b5ef9f24f1 100644 --- a/src/harness/unittests/moduleResolution.ts +++ b/src/harness/unittests/moduleResolution.ts @@ -10,7 +10,7 @@ namespace ts { const map = arrayToMap(files, f => f.name); if (hasDirectoryExists) { - const directories: Map = {}; + const directories: MapLike = {}; for (const f of files) { let name = getDirectoryPath(f.name); while (true) { @@ -282,7 +282,7 @@ namespace ts { }); describe("Module resolution - relative imports", () => { - function test(files: Map, currentDirectory: string, rootFiles: string[], expectedFilesCount: number, relativeNamesToCheck: string[]) { + function test(files: MapLike, currentDirectory: string, rootFiles: string[], expectedFilesCount: number, relativeNamesToCheck: string[]) { const options: CompilerOptions = { module: ModuleKind.CommonJS }; const host: CompilerHost = { getSourceFile: (fileName: string, languageVersion: ScriptTarget) => { @@ -318,7 +318,7 @@ namespace ts { } it("should find all modules", () => { - const files: Map = { + const files: MapLike = { "/a/b/c/first/shared.ts": ` class A {} export = A`, @@ -337,7 +337,7 @@ export = C; }); it("should find modules in node_modules", () => { - const files: Map = { + const files: MapLike = { "/parent/node_modules/mod/index.d.ts": "export var x", "/parent/app/myapp.ts": `import {x} from "mod"` }; @@ -345,7 +345,7 @@ export = C; }); it("should find file referenced via absolute and relative names", () => { - const files: Map = { + const files: MapLike = { "/a/b/c.ts": `/// `, "/a/b/b.ts": "var x" }; @@ -355,10 +355,10 @@ export = C; describe("Files with different casing", () => { const library = createSourceFile("lib.d.ts", "", ScriptTarget.ES5); - function test(files: Map, options: CompilerOptions, currentDirectory: string, useCaseSensitiveFileNames: boolean, rootFiles: string[], diagnosticCodes: number[]): void { + function test(files: MapLike, options: CompilerOptions, currentDirectory: string, useCaseSensitiveFileNames: boolean, rootFiles: string[], diagnosticCodes: number[]): void { const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); if (!useCaseSensitiveFileNames) { - const f: Map = {}; + const f: MapLike = {}; for (const fileName in files) { f[getCanonicalFileName(fileName)] = files[fileName]; } @@ -395,7 +395,7 @@ export = C; } it("should succeed when the same file is referenced using absolute and relative names", () => { - const files: Map = { + const files: MapLike = { "/a/b/c.ts": `/// `, "/a/b/d.ts": "var x" }; @@ -403,7 +403,7 @@ export = C; }); it("should fail when two files used in program differ only in casing (tripleslash references)", () => { - const files: Map = { + const files: MapLike = { "/a/b/c.ts": `/// `, "/a/b/d.ts": "var x" }; @@ -411,7 +411,7 @@ export = C; }); it("should fail when two files used in program differ only in casing (imports)", () => { - const files: Map = { + const files: MapLike = { "/a/b/c.ts": `import {x} from "D"`, "/a/b/d.ts": "export var x" }; @@ -419,7 +419,7 @@ export = C; }); it("should fail when two files used in program differ only in casing (imports, relative module names)", () => { - const files: Map = { + const files: MapLike = { "moduleA.ts": `import {x} from "./ModuleB"`, "moduleB.ts": "export var x" }; @@ -427,7 +427,7 @@ export = C; }); it("should fail when two files exist on disk that differs only in casing", () => { - const files: Map = { + const files: MapLike = { "/a/b/c.ts": `import {x} from "D"`, "/a/b/D.ts": "export var x", "/a/b/d.ts": "export var y" @@ -436,7 +436,7 @@ export = C; }); it("should fail when module name in 'require' calls has inconsistent casing", () => { - const files: Map = { + const files: MapLike = { "moduleA.ts": `import a = require("./ModuleC")`, "moduleB.ts": `import a = require("./moduleC")`, "moduleC.ts": "export var x" @@ -445,7 +445,7 @@ export = C; }); it("should fail when module names in 'require' calls has inconsistent casing and current directory has uppercase chars", () => { - const files: Map = { + const files: MapLike = { "/a/B/c/moduleA.ts": `import a = require("./ModuleC")`, "/a/B/c/moduleB.ts": `import a = require("./moduleC")`, "/a/B/c/moduleC.ts": "export var x", @@ -457,7 +457,7 @@ import b = require("./moduleB.ts"); test(files, { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, "/a/B/c", /*useCaseSensitiveFileNames*/ false, ["moduleD.ts"], [1149]); }); it("should not fail when module names in 'require' calls has consistent casing and current directory has uppercase chars", () => { - const files: Map = { + const files: MapLike = { "/a/B/c/moduleA.ts": `import a = require("./moduleC")`, "/a/B/c/moduleB.ts": `import a = require("./moduleC")`, "/a/B/c/moduleC.ts": "export var x", diff --git a/src/harness/unittests/reuseProgramStructure.ts b/src/harness/unittests/reuseProgramStructure.ts index 8b2b15c15b3..76015667232 100644 --- a/src/harness/unittests/reuseProgramStructure.ts +++ b/src/harness/unittests/reuseProgramStructure.ts @@ -96,7 +96,7 @@ namespace ts { } function createTestCompilerHost(texts: NamedSourceText[], target: ScriptTarget): CompilerHost { - const files: Map = {}; + const files: MapLike = {}; for (const t of texts) { const file = createSourceFile(t.name, t.text.getFullText(), target); file.sourceText = t.text; @@ -152,7 +152,7 @@ namespace ts { return program; } - function getSizeOfMap(map: Map): number { + function getSizeOfMap(map: MapLike): number { let size = 0; for (const id in map) { if (hasProperty(map, id)) { @@ -174,7 +174,7 @@ namespace ts { assert.isTrue(expected.primary === actual.primary, `'primary': expected '${expected.primary}' to be equal to '${actual.primary}'`); } - function checkCache(caption: string, program: Program, fileName: string, expectedContent: Map, getCache: (f: SourceFile) => Map, entryChecker: (expected: T, original: T) => void): void { + function checkCache(caption: string, program: Program, fileName: string, expectedContent: MapLike, getCache: (f: SourceFile) => MapLike, entryChecker: (expected: T, original: T) => void): void { const file = program.getSourceFile(fileName); assert.isTrue(file !== undefined, `cannot find file ${fileName}`); const cache = getCache(file); @@ -203,11 +203,11 @@ namespace ts { } } - function checkResolvedModulesCache(program: Program, fileName: string, expectedContent: Map): void { + function checkResolvedModulesCache(program: Program, fileName: string, expectedContent: MapLike): void { checkCache("resolved modules", program, fileName, expectedContent, f => f.resolvedModules, checkResolvedModule); } - function checkResolvedTypeDirectivesCache(program: Program, fileName: string, expectedContent: Map): void { + function checkResolvedTypeDirectivesCache(program: Program, fileName: string, expectedContent: MapLike): void { checkCache("resolved type directives", program, fileName, expectedContent, f => f.resolvedTypeReferenceDirectiveNames, checkResolvedTypeDirective); } diff --git a/src/harness/unittests/session.ts b/src/harness/unittests/session.ts index c5285544329..8e7faa94a7a 100644 --- a/src/harness/unittests/session.ts +++ b/src/harness/unittests/session.ts @@ -362,8 +362,8 @@ namespace ts.server { class InProcClient { private server: InProcSession; private seq = 0; - private callbacks: ts.Map<(resp: protocol.Response) => void> = {}; - private eventHandlers: ts.Map<(args: any) => void> = {}; + private callbacks: ts.MapLike<(resp: protocol.Response) => void> = {}; + private eventHandlers: ts.MapLike<(args: any) => void> = {}; handle(msg: protocol.Message): void { if (msg.type === "response") { diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 7a375c50b8a..74b9e303cf7 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -68,7 +68,7 @@ namespace ts { return entry; } - function sizeOfMap(map: Map): number { + function sizeOfMap(map: MapLike): number { let n = 0; for (const name in map) { if (hasProperty(map, name)) { @@ -78,7 +78,7 @@ namespace ts { return n; } - function checkMapKeys(caption: string, map: Map, expectedKeys: string[]) { + function checkMapKeys(caption: string, map: MapLike, expectedKeys: string[]) { assert.equal(sizeOfMap(map), expectedKeys.length, `${caption}: incorrect size of map`); for (const name of expectedKeys) { assert.isTrue(hasProperty(map, name), `${caption} is expected to contain ${name}, actual keys: ${getKeys(map)}`); @@ -126,8 +126,8 @@ namespace ts { private getCanonicalFileName: (s: string) => string; private toPath: (f: string) => Path; private callbackQueue: TimeOutCallback[] = []; - readonly watchedDirectories: Map<{ cb: DirectoryWatcherCallback, recursive: boolean }[]> = {}; - readonly watchedFiles: Map = {}; + readonly watchedDirectories: MapLike<{ cb: DirectoryWatcherCallback, recursive: boolean }[]> = {}; + readonly watchedFiles: MapLike = {}; constructor(public useCaseSensitiveFileNames: boolean, private executingFilePath: string, private currentDirectory: string, fileOrFolderList: FileOrFolder[]) { this.getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); diff --git a/src/server/client.ts b/src/server/client.ts index f04dbd8dc02..3547ac52602 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -21,7 +21,7 @@ namespace ts.server { export class SessionClient implements LanguageService { private sequence: number = 0; - private lineMaps: ts.Map = {}; + private lineMaps: ts.Map = ts.createMap(); private messages: string[] = []; private lastRenameEntry: RenameEntry; diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 6e9adad7f47..a14c42f471e 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -130,7 +130,7 @@ namespace ts.server { const path = toPath(containingFile, this.host.getCurrentDirectory(), this.getCanonicalFileName); const currentResolutionsInFile = cache.get(path); - const newResolutions: Map = {}; + const newResolutions = createMap(); const resolvedModules: R[] = []; const compilerOptions = this.getCompilationSettings(); @@ -378,7 +378,7 @@ namespace ts.server { export interface ProjectOptions { // these fields can be present in the project file files?: string[]; - wildcardDirectories?: ts.Map; + wildcardDirectories?: ts.MapLike; compilerOptions?: ts.CompilerOptions; } @@ -391,7 +391,7 @@ namespace ts.server { // Used to keep track of what directories are watched for this project directoriesWatchedForTsconfig: string[] = []; program: ts.Program; - filenameToSourceFile: ts.Map = {}; + filenameToSourceFile = ts.createMap(); updateGraphSeq = 0; /** Used for configured projects which may have multiple open roots */ openRefCount = 0; @@ -504,7 +504,7 @@ namespace ts.server { return; } - this.filenameToSourceFile = {}; + this.filenameToSourceFile = createMap(); const sourceFiles = this.program.getSourceFiles(); for (let i = 0, len = sourceFiles.length; i < len; i++) { const normFilename = ts.normalizePath(sourceFiles[i].fileName); @@ -613,7 +613,7 @@ namespace ts.server { } export class ProjectService { - filenameToScriptInfo: ts.Map = {}; + filenameToScriptInfo = ts.createMap(); // open, non-configured root files openFileRoots: ScriptInfo[] = []; // projects built from openFileRoots @@ -625,12 +625,12 @@ namespace ts.server { // open files that are roots of a configured project openFileRootsConfigured: ScriptInfo[] = []; // a path to directory watcher map that detects added tsconfig files - directoryWatchersForTsconfig: ts.Map = {}; + directoryWatchersForTsconfig = ts.createMap(); // count of how many projects are using the directory watcher. If the // number becomes 0 for a watcher, then we should close it. - directoryWatchersRefCount: ts.Map = {}; + directoryWatchersRefCount = ts.createMap(); hostConfiguration: HostConfiguration; - timerForDetectingProjectFileListChanges: Map = {}; + timerForDetectingProjectFileListChanges = createMap(); constructor(public host: ServerHost, public psLogger: Logger, public eventHandler?: ProjectServiceEventHandler) { // ts.disableIncrementalParsing = true; diff --git a/src/server/session.ts b/src/server/session.ts index 7e1ca81e2af..13b0e6d6ce4 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1061,7 +1061,7 @@ namespace ts.server { return { response, responseRequired: true }; } - private handlers: Map<(request: protocol.Request) => { response?: any, responseRequired?: boolean }> = { + private handlers: MapLike<(request: protocol.Request) => { response?: any, responseRequired?: boolean }> = { [CommandNames.Exit]: () => { this.exit(); return { responseRequired: false }; diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts index 3b013a4a924..05de0061d39 100644 --- a/src/services/jsTyping.ts +++ b/src/services/jsTyping.ts @@ -47,7 +47,7 @@ namespace ts.JsTyping { { cachedTypingPaths: string[], newTypingNames: string[], filesToWatch: string[] } { // A typing name to typing file path mapping - const inferredTypings: Map = {}; + const inferredTypings = createMap(); if (!typingOptions || !typingOptions.enableAutoDiscovery) { return { cachedTypingPaths: [], newTypingNames: [], filesToWatch: [] }; @@ -62,7 +62,7 @@ namespace ts.JsTyping { safeList = result.config; } else { - safeList = {}; + safeList = createMap(); }; } diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index f105a9fa6e2..ebd6936ea85 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -234,7 +234,7 @@ namespace ts.NavigationBar { /** Merge declarations of the same kind. */ function mergeChildren(children: NavigationBarNode[]): void { - const nameToItems: Map = {}; + const nameToItems = createMap(); filterMutate(children, child => { const decl = child.node; const name = decl.name && nodeText(decl.name); diff --git a/src/services/patternMatcher.ts b/src/services/patternMatcher.ts index 3d20337eca7..cff3f591f8a 100644 --- a/src/services/patternMatcher.ts +++ b/src/services/patternMatcher.ts @@ -113,7 +113,7 @@ namespace ts { // we see the name of a module that is used everywhere, or the name of an overload). As // such, we cache the information we compute about the candidate for the life of this // pattern matcher so we don't have to compute it multiple times. - const stringToWordSpans: Map = {}; + const stringToWordSpans = createMap(); pattern = pattern.trim(); diff --git a/src/services/services.ts b/src/services/services.ts index aea4d5f872e..850e29030d8 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -975,7 +975,7 @@ namespace ts { } private computeNamedDeclarations(): Map { - const result: Map = {}; + const result = createMap(); forEachChild(this, visit); @@ -2025,7 +2025,7 @@ namespace ts { fileName?: string; reportDiagnostics?: boolean; moduleName?: string; - renamedDependencies?: Map; + renamedDependencies?: MapLike; } export interface TranspileOutput { @@ -2243,7 +2243,7 @@ namespace ts { export function createDocumentRegistry(useCaseSensitiveFileNames?: boolean, currentDirectory = ""): DocumentRegistry { // Maps from compiler setting target (ES3, ES5, etc.) to all the cached documents we have // for those settings. - const buckets: Map> = {}; + const buckets = createMap>(); const getCanonicalFileName = createGetCanonicalFileName(!!useCaseSensitiveFileNames); function getKeyForCompilationSettings(settings: CompilerOptions): DocumentRegistryBucketKey { @@ -4102,7 +4102,7 @@ namespace ts { * do not occur at the current position and have not otherwise been typed. */ function filterNamedImportOrExportCompletionItems(exportsOfModule: Symbol[], namedImportsOrExports: ImportOrExportSpecifier[]): Symbol[] { - const existingImportsOrExports: Map = {}; + const existingImportsOrExports = createMap(); for (const element of namedImportsOrExports) { // If this is the current item we are editing right now, do not filter it out @@ -4132,7 +4132,7 @@ namespace ts { return contextualMemberSymbols; } - const existingMemberNames: Map = {}; + const existingMemberNames = createMap(); for (const m of existingMembers) { // Ignore omitted expressions for missing members if (m.kind !== SyntaxKind.PropertyAssignment && @@ -4175,7 +4175,7 @@ namespace ts { * do not occur at the current position and have not otherwise been typed. */ function filterJsxAttributes(symbols: Symbol[], attributes: NodeArray): Symbol[] { - const seenNames: Map = {}; + const seenNames = createMap(); for (const attr of attributes) { // If this is the current item we are editing right now, do not filter it out if (attr.getStart() <= position && position <= attr.getEnd()) { @@ -4317,7 +4317,7 @@ namespace ts { function getCompletionEntriesFromSymbols(symbols: Symbol[], entries: CompletionEntry[], location: Node, performCharacterChecks: boolean): Map { const start = timestamp(); - const uniqueNames: Map = {}; + const uniqueNames = createMap(); if (symbols) { for (const symbol of symbols) { const entry = createCompletionEntry(symbol, location, performCharacterChecks); @@ -5318,7 +5318,7 @@ namespace ts { return undefined; } - const fileNameToDocumentHighlights: Map = {}; + const fileNameToDocumentHighlights = createMap(); const result: DocumentHighlights[] = []; for (const referencedSymbol of referencedSymbols) { for (const referenceEntry of referencedSymbol.references) { @@ -6713,7 +6713,7 @@ namespace ts { // Add symbol of properties/methods of the same name in base classes and implemented interfaces definitions if (rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { - getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.getName(), result, /*previousIterationSymbolsCache*/ {}); + getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.getName(), result, /*previousIterationSymbolsCache*/ createMap()); } }); @@ -6834,7 +6834,7 @@ namespace ts { // see if any is in the list if (rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { const result: Symbol[] = []; - getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.getName(), result, /*previousIterationSymbolsCache*/ {}); + getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.getName(), result, /*previousIterationSymbolsCache*/ createMap()); return forEach(result, s => searchSymbols.indexOf(s) >= 0 ? s : undefined); } @@ -8318,7 +8318,7 @@ namespace ts { } function initializeNameTable(sourceFile: SourceFile): void { - const nameTable: Map = {}; + const nameTable = createMap(); walk(sourceFile); sourceFile.nameTable = nameTable; From cba2e1aacb6c8544629a4114fd4316d85fa1dcb3 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 10 Aug 2016 16:47:35 -0700 Subject: [PATCH 28/72] Update API sample --- tests/baselines/reference/APISample_watcher.js | 2 +- tests/cases/compiler/APISample_watcher.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/baselines/reference/APISample_watcher.js b/tests/baselines/reference/APISample_watcher.js index 7573f2ba99e..cb7fc3a4c5c 100644 --- a/tests/baselines/reference/APISample_watcher.js +++ b/tests/baselines/reference/APISample_watcher.js @@ -20,7 +20,7 @@ declare var path: any; import * as ts from "typescript"; function watch(rootFileNames: string[], options: ts.CompilerOptions) { - const files: ts.Map<{ version: number }> = {}; + const files: ts.MapLike<{ version: number }> = {}; // initialize the list of files rootFileNames.forEach(fileName => { diff --git a/tests/cases/compiler/APISample_watcher.ts b/tests/cases/compiler/APISample_watcher.ts index 34baa04c850..07922bd35c7 100644 --- a/tests/cases/compiler/APISample_watcher.ts +++ b/tests/cases/compiler/APISample_watcher.ts @@ -23,7 +23,7 @@ declare var path: any; import * as ts from "typescript"; function watch(rootFileNames: string[], options: ts.CompilerOptions) { - const files: ts.Map<{ version: number }> = {}; + const files: ts.MapLike<{ version: number }> = {}; // initialize the list of files rootFileNames.forEach(fileName => { From 3ff89f794f8707e9b4a8dd8219b6b5d1f8e43149 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 10 Aug 2016 17:28:52 -0700 Subject: [PATCH 29/72] Fix processDiagnosticMessages script --- scripts/processDiagnosticMessages.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/processDiagnosticMessages.ts b/scripts/processDiagnosticMessages.ts index 26632ba6bab..431cf460180 100644 --- a/scripts/processDiagnosticMessages.ts +++ b/scripts/processDiagnosticMessages.ts @@ -69,7 +69,7 @@ function checkForUniqueCodes(messages: string[], diagnosticTable: InputDiagnosti } function buildUniqueNameMap(names: string[]): ts.Map { - var nameMap: ts.Map = {}; + var nameMap = ts.createMap(); var uniqueNames = NameGenerator.ensureUniqueness(names, /* isCaseSensitive */ false, /* isFixed */ undefined); From 24d8d848f1b85b627482e8a56e50f398b1520cfd Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 11 Aug 2016 10:23:27 -0700 Subject: [PATCH 30/72] Optimize filterType to only call getUnionType if necessary --- src/compiler/checker.ts | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 43ec22df403..2ae238b9175 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8172,9 +8172,26 @@ namespace ts { } function filterType(type: Type, f: (t: Type) => boolean): Type { - return type.flags & TypeFlags.Union ? - getUnionType(filter((type).types, f)) : - f(type) ? type : neverType; + if (!(type.flags & TypeFlags.Union)) { + return f(type) ? type : neverType; + } + let types = (type).types; + let length = types.length; + let i = 0; + while (i < length && f(types[i])) i++; + if (i === length) { + return type; + } + let filtered = types.slice(0, i); + i++; + while (i < length) { + const t = types[i]; + if (f(t)) { + filtered.push(t); + } + i++; + } + return getUnionType(filtered); } function isIncomplete(flowType: FlowType) { From 9ac13abfd196f662ce606d8d99cb8791c2ad61fe Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 11 Aug 2016 11:58:23 -0700 Subject: [PATCH 31/72] Add shorthand types declaration for travis-fold (#10293) --- scripts/types/ambient.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/types/ambient.d.ts b/scripts/types/ambient.d.ts index e77e3fe8c5a..e83489801d7 100644 --- a/scripts/types/ambient.d.ts +++ b/scripts/types/ambient.d.ts @@ -22,3 +22,4 @@ declare module "into-stream" { } declare module "sorcery"; +declare module "travis-fold"; From a3845a95d56cd190fcc0a3fa359aaad37cbb4c74 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 11 Aug 2016 13:44:51 -0700 Subject: [PATCH 32/72] Optimize getTypeWithFacts --- src/compiler/checker.ts | 49 +++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2ae238b9175..e4f650691d5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7924,6 +7924,14 @@ namespace ts { return result; } + function isFunctionObjectType(type: ObjectType): boolean { + // We do a quick check for a "bind" property before performing the more expensive subtype + // check. This gives us a quicker out in the common case where an object type is not a function. + const resolved = resolveStructuredTypeMembers(type); + return !!(resolved.callSignatures.length || resolved.constructSignatures.length || + hasProperty(resolved.members, "bind") && isTypeSubtypeOf(type, globalFunctionType)); + } + function getTypeFacts(type: Type): TypeFacts { const flags = type.flags; if (flags & TypeFlags.String) { @@ -7952,8 +7960,7 @@ namespace ts { type === falseType ? TypeFacts.FalseFacts : TypeFacts.TrueFacts; } if (flags & TypeFlags.ObjectType) { - const resolved = resolveStructuredTypeMembers(type); - return resolved.callSignatures.length || resolved.constructSignatures.length || isTypeSubtypeOf(type, globalFunctionType) ? + return isFunctionObjectType(type) ? strictNullChecks ? TypeFacts.FunctionStrictFacts : TypeFacts.FunctionFacts : strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts; } @@ -7980,23 +7987,23 @@ namespace ts { if (!(type.flags & TypeFlags.Union)) { return getTypeFacts(type) & include ? type : neverType; } - let firstType: Type; - let types: Type[]; - for (const t of (type as UnionType).types) { - if (getTypeFacts(t) & include) { - if (!firstType) { - firstType = t; - } - else { - if (!types) { - types = [firstType]; - } - types.push(t); - } - } + const types = (type).types; + const length = types.length; + let i = 0; + while (i < length && getTypeFacts(types[i]) & include) i++; + if (i === length) { + return type; } - return types ? getUnionType(types) : - firstType ? firstType : neverType; + const filtered = types.slice(0, i); + i++; + while (i < length) { + const t = types[i]; + if (getTypeFacts(t) & include) { + filtered.push(t); + } + i++; + } + return getUnionType(filtered); } function getTypeWithDefault(type: Type, defaultExpression: Expression) { @@ -8175,14 +8182,14 @@ namespace ts { if (!(type.flags & TypeFlags.Union)) { return f(type) ? type : neverType; } - let types = (type).types; - let length = types.length; + const types = (type).types; + const length = types.length; let i = 0; while (i < length && f(types[i])) i++; if (i === length) { return type; } - let filtered = types.slice(0, i); + const filtered = types.slice(0, i); i++; while (i < length) { const t = types[i]; From c22a54fb9507d25ca0b81f1ff82de4b26154cb9a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 11 Aug 2016 13:46:06 -0700 Subject: [PATCH 33/72] Filter out nullable and primitive types in isDiscriminantProperty --- src/compiler/checker.ts | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e4f650691d5..0ba44b34f89 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -245,7 +245,8 @@ namespace ts { NEUndefinedOrNull = 1 << 19, // x != undefined / x != null Truthy = 1 << 20, // x Falsy = 1 << 21, // !x - All = (1 << 22) - 1, + Discriminatable = 1 << 22, // May have discriminant property + All = (1 << 23) - 1, // The following members encode facts about particular kinds of types for use in the getTypeFacts function. // The presence of a particular fact means that the given test is true for some (and possibly all) values // of that kind of type. @@ -275,9 +276,9 @@ namespace ts { TrueFacts = BaseBooleanFacts | Truthy, SymbolStrictFacts = TypeofEQSymbol | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull | Truthy, SymbolFacts = SymbolStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, - ObjectStrictFacts = TypeofEQObject | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | NEUndefined | NENull | NEUndefinedOrNull | Truthy, + ObjectStrictFacts = TypeofEQObject | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | NEUndefined | NENull | NEUndefinedOrNull | Truthy | Discriminatable, ObjectFacts = ObjectStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, - FunctionStrictFacts = TypeofEQFunction | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | NEUndefined | NENull | NEUndefinedOrNull | Truthy, + FunctionStrictFacts = TypeofEQFunction | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | NEUndefined | NENull | NEUndefinedOrNull | Truthy | Discriminatable, FunctionFacts = FunctionStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, UndefinedFacts = TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | EQUndefined | EQUndefinedOrNull | NENull | Falsy, NullFacts = TypeofEQObject | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | TypeofNEHostObject | EQNull | EQUndefinedOrNull | NEUndefined | Falsy, @@ -7848,18 +7849,25 @@ namespace ts { } function isDiscriminantProperty(type: Type, name: string) { - if (type) { - const nonNullType = getNonNullableType(type); - if (nonNullType.flags & TypeFlags.Union) { - const prop = getPropertyOfType(nonNullType, name); - if (prop && prop.flags & SymbolFlags.SyntheticProperty) { - if ((prop).isDiscriminantProperty === undefined) { - (prop).isDiscriminantProperty = !(prop).hasCommonType && - isUnitUnionType(getTypeOfSymbol(prop)); - } - return (prop).isDiscriminantProperty; + if (type && type.flags & TypeFlags.Union) { + let prop = getPropertyOfType(type, name); + if (!prop) { + // The type may be a union that includes nullable or primtive types. If filtering + // those out produces a different type, get the property from that type instead. + // Effectively, we're checking if this *could* be a discriminant property once nullable + // and primitive types are removed by other type guards. + const filteredType = getTypeWithFacts(type, TypeFacts.Discriminatable); + if (filteredType !== type && filteredType.flags & TypeFlags.Union) { + prop = getPropertyOfType(type, name); } } + if (prop && prop.flags & SymbolFlags.SyntheticProperty) { + if ((prop).isDiscriminantProperty === undefined) { + (prop).isDiscriminantProperty = !(prop).hasCommonType && + isUnitUnionType(getTypeOfSymbol(prop)); + } + return (prop).isDiscriminantProperty; + } } return false; } From b336c693eed9745efdabb0c7736a81080d481eb0 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 11 Aug 2016 14:38:01 -0700 Subject: [PATCH 34/72] Fix typo --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0ba44b34f89..b6d03737810 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7858,7 +7858,7 @@ namespace ts { // and primitive types are removed by other type guards. const filteredType = getTypeWithFacts(type, TypeFacts.Discriminatable); if (filteredType !== type && filteredType.flags & TypeFlags.Union) { - prop = getPropertyOfType(type, name); + prop = getPropertyOfType(filteredType, name); } } if (prop && prop.flags & SymbolFlags.SyntheticProperty) { From 29ae2b2cf153ef7d28a50053c85e637ea2d02915 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 11 Aug 2016 14:38:17 -0700 Subject: [PATCH 35/72] Add regression tests --- .../reference/discriminantsAndPrimitives.js | 84 +++++++++++ .../discriminantsAndPrimitives.symbols | 117 +++++++++++++++ .../discriminantsAndPrimitives.types | 141 ++++++++++++++++++ .../compiler/discriminantsAndPrimitives.ts | 49 ++++++ 4 files changed, 391 insertions(+) create mode 100644 tests/baselines/reference/discriminantsAndPrimitives.js create mode 100644 tests/baselines/reference/discriminantsAndPrimitives.symbols create mode 100644 tests/baselines/reference/discriminantsAndPrimitives.types create mode 100644 tests/cases/compiler/discriminantsAndPrimitives.ts diff --git a/tests/baselines/reference/discriminantsAndPrimitives.js b/tests/baselines/reference/discriminantsAndPrimitives.js new file mode 100644 index 00000000000..1d11781a707 --- /dev/null +++ b/tests/baselines/reference/discriminantsAndPrimitives.js @@ -0,0 +1,84 @@ +//// [discriminantsAndPrimitives.ts] + +// Repro from #10257 plus other tests + +interface Foo { + kind: "foo"; + name: string; +} + +interface Bar { + kind: "bar"; + length: string; +} + +function f1(x: Foo | Bar | string) { + if (typeof x !== 'string') { + switch(x.kind) { + case 'foo': + x.name; + } + } +} + +function f2(x: Foo | Bar | string | undefined) { + if (typeof x === "object") { + switch(x.kind) { + case 'foo': + x.name; + } + } +} + +function f3(x: Foo | Bar | string | null) { + if (x && typeof x !== "string") { + switch(x.kind) { + case 'foo': + x.name; + } + } +} + +function f4(x: Foo | Bar | string | number | null) { + if (x && typeof x === "object") { + switch(x.kind) { + case 'foo': + x.name; + } + } +} + +//// [discriminantsAndPrimitives.js] +// Repro from #10257 plus other tests +function f1(x) { + if (typeof x !== 'string') { + switch (x.kind) { + case 'foo': + x.name; + } + } +} +function f2(x) { + if (typeof x === "object") { + switch (x.kind) { + case 'foo': + x.name; + } + } +} +function f3(x) { + if (x && typeof x !== "string") { + switch (x.kind) { + case 'foo': + x.name; + } + } +} +function f4(x) { + if (x && typeof x === "object") { + switch (x.kind) { + case 'foo': + x.name; + } + } +} diff --git a/tests/baselines/reference/discriminantsAndPrimitives.symbols b/tests/baselines/reference/discriminantsAndPrimitives.symbols new file mode 100644 index 00000000000..c84f32cd990 --- /dev/null +++ b/tests/baselines/reference/discriminantsAndPrimitives.symbols @@ -0,0 +1,117 @@ +=== tests/cases/compiler/discriminantsAndPrimitives.ts === + +// Repro from #10257 plus other tests + +interface Foo { +>Foo : Symbol(Foo, Decl(discriminantsAndPrimitives.ts, 0, 0)) + + kind: "foo"; +>kind : Symbol(Foo.kind, Decl(discriminantsAndPrimitives.ts, 3, 15)) + + name: string; +>name : Symbol(Foo.name, Decl(discriminantsAndPrimitives.ts, 4, 16)) +} + +interface Bar { +>Bar : Symbol(Bar, Decl(discriminantsAndPrimitives.ts, 6, 1)) + + kind: "bar"; +>kind : Symbol(Bar.kind, Decl(discriminantsAndPrimitives.ts, 8, 15)) + + length: string; +>length : Symbol(Bar.length, Decl(discriminantsAndPrimitives.ts, 9, 16)) +} + +function f1(x: Foo | Bar | string) { +>f1 : Symbol(f1, Decl(discriminantsAndPrimitives.ts, 11, 1)) +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 13, 12)) +>Foo : Symbol(Foo, Decl(discriminantsAndPrimitives.ts, 0, 0)) +>Bar : Symbol(Bar, Decl(discriminantsAndPrimitives.ts, 6, 1)) + + if (typeof x !== 'string') { +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 13, 12)) + + switch(x.kind) { +>x.kind : Symbol(kind, Decl(discriminantsAndPrimitives.ts, 3, 15), Decl(discriminantsAndPrimitives.ts, 8, 15)) +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 13, 12)) +>kind : Symbol(kind, Decl(discriminantsAndPrimitives.ts, 3, 15), Decl(discriminantsAndPrimitives.ts, 8, 15)) + + case 'foo': + x.name; +>x.name : Symbol(Foo.name, Decl(discriminantsAndPrimitives.ts, 4, 16)) +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 13, 12)) +>name : Symbol(Foo.name, Decl(discriminantsAndPrimitives.ts, 4, 16)) + } + } +} + +function f2(x: Foo | Bar | string | undefined) { +>f2 : Symbol(f2, Decl(discriminantsAndPrimitives.ts, 20, 1)) +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 22, 12)) +>Foo : Symbol(Foo, Decl(discriminantsAndPrimitives.ts, 0, 0)) +>Bar : Symbol(Bar, Decl(discriminantsAndPrimitives.ts, 6, 1)) + + if (typeof x === "object") { +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 22, 12)) + + switch(x.kind) { +>x.kind : Symbol(kind, Decl(discriminantsAndPrimitives.ts, 3, 15), Decl(discriminantsAndPrimitives.ts, 8, 15)) +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 22, 12)) +>kind : Symbol(kind, Decl(discriminantsAndPrimitives.ts, 3, 15), Decl(discriminantsAndPrimitives.ts, 8, 15)) + + case 'foo': + x.name; +>x.name : Symbol(Foo.name, Decl(discriminantsAndPrimitives.ts, 4, 16)) +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 22, 12)) +>name : Symbol(Foo.name, Decl(discriminantsAndPrimitives.ts, 4, 16)) + } + } +} + +function f3(x: Foo | Bar | string | null) { +>f3 : Symbol(f3, Decl(discriminantsAndPrimitives.ts, 29, 1)) +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 31, 12)) +>Foo : Symbol(Foo, Decl(discriminantsAndPrimitives.ts, 0, 0)) +>Bar : Symbol(Bar, Decl(discriminantsAndPrimitives.ts, 6, 1)) + + if (x && typeof x !== "string") { +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 31, 12)) +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 31, 12)) + + switch(x.kind) { +>x.kind : Symbol(kind, Decl(discriminantsAndPrimitives.ts, 3, 15), Decl(discriminantsAndPrimitives.ts, 8, 15)) +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 31, 12)) +>kind : Symbol(kind, Decl(discriminantsAndPrimitives.ts, 3, 15), Decl(discriminantsAndPrimitives.ts, 8, 15)) + + case 'foo': + x.name; +>x.name : Symbol(Foo.name, Decl(discriminantsAndPrimitives.ts, 4, 16)) +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 31, 12)) +>name : Symbol(Foo.name, Decl(discriminantsAndPrimitives.ts, 4, 16)) + } + } +} + +function f4(x: Foo | Bar | string | number | null) { +>f4 : Symbol(f4, Decl(discriminantsAndPrimitives.ts, 38, 1)) +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 40, 12)) +>Foo : Symbol(Foo, Decl(discriminantsAndPrimitives.ts, 0, 0)) +>Bar : Symbol(Bar, Decl(discriminantsAndPrimitives.ts, 6, 1)) + + if (x && typeof x === "object") { +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 40, 12)) +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 40, 12)) + + switch(x.kind) { +>x.kind : Symbol(kind, Decl(discriminantsAndPrimitives.ts, 3, 15), Decl(discriminantsAndPrimitives.ts, 8, 15)) +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 40, 12)) +>kind : Symbol(kind, Decl(discriminantsAndPrimitives.ts, 3, 15), Decl(discriminantsAndPrimitives.ts, 8, 15)) + + case 'foo': + x.name; +>x.name : Symbol(Foo.name, Decl(discriminantsAndPrimitives.ts, 4, 16)) +>x : Symbol(x, Decl(discriminantsAndPrimitives.ts, 40, 12)) +>name : Symbol(Foo.name, Decl(discriminantsAndPrimitives.ts, 4, 16)) + } + } +} diff --git a/tests/baselines/reference/discriminantsAndPrimitives.types b/tests/baselines/reference/discriminantsAndPrimitives.types new file mode 100644 index 00000000000..d3e1b70e599 --- /dev/null +++ b/tests/baselines/reference/discriminantsAndPrimitives.types @@ -0,0 +1,141 @@ +=== tests/cases/compiler/discriminantsAndPrimitives.ts === + +// Repro from #10257 plus other tests + +interface Foo { +>Foo : Foo + + kind: "foo"; +>kind : "foo" + + name: string; +>name : string +} + +interface Bar { +>Bar : Bar + + kind: "bar"; +>kind : "bar" + + length: string; +>length : string +} + +function f1(x: Foo | Bar | string) { +>f1 : (x: string | Foo | Bar) => void +>x : string | Foo | Bar +>Foo : Foo +>Bar : Bar + + if (typeof x !== 'string') { +>typeof x !== 'string' : boolean +>typeof x : string +>x : string | Foo | Bar +>'string' : "string" + + switch(x.kind) { +>x.kind : "foo" | "bar" +>x : Foo | Bar +>kind : "foo" | "bar" + + case 'foo': +>'foo' : "foo" + + x.name; +>x.name : string +>x : Foo +>name : string + } + } +} + +function f2(x: Foo | Bar | string | undefined) { +>f2 : (x: string | Foo | Bar | undefined) => void +>x : string | Foo | Bar | undefined +>Foo : Foo +>Bar : Bar + + if (typeof x === "object") { +>typeof x === "object" : boolean +>typeof x : string +>x : string | Foo | Bar | undefined +>"object" : "object" + + switch(x.kind) { +>x.kind : "foo" | "bar" +>x : Foo | Bar +>kind : "foo" | "bar" + + case 'foo': +>'foo' : "foo" + + x.name; +>x.name : string +>x : Foo +>name : string + } + } +} + +function f3(x: Foo | Bar | string | null) { +>f3 : (x: string | Foo | Bar | null) => void +>x : string | Foo | Bar | null +>Foo : Foo +>Bar : Bar +>null : null + + if (x && typeof x !== "string") { +>x && typeof x !== "string" : boolean | "" | null +>x : string | Foo | Bar | null +>typeof x !== "string" : boolean +>typeof x : string +>x : string | Foo | Bar +>"string" : "string" + + switch(x.kind) { +>x.kind : "foo" | "bar" +>x : Foo | Bar +>kind : "foo" | "bar" + + case 'foo': +>'foo' : "foo" + + x.name; +>x.name : string +>x : Foo +>name : string + } + } +} + +function f4(x: Foo | Bar | string | number | null) { +>f4 : (x: string | number | Foo | Bar | null) => void +>x : string | number | Foo | Bar | null +>Foo : Foo +>Bar : Bar +>null : null + + if (x && typeof x === "object") { +>x && typeof x === "object" : boolean | "" | 0 | null +>x : string | number | Foo | Bar | null +>typeof x === "object" : boolean +>typeof x : string +>x : string | number | Foo | Bar +>"object" : "object" + + switch(x.kind) { +>x.kind : "foo" | "bar" +>x : Foo | Bar +>kind : "foo" | "bar" + + case 'foo': +>'foo' : "foo" + + x.name; +>x.name : string +>x : Foo +>name : string + } + } +} diff --git a/tests/cases/compiler/discriminantsAndPrimitives.ts b/tests/cases/compiler/discriminantsAndPrimitives.ts new file mode 100644 index 00000000000..6352d741808 --- /dev/null +++ b/tests/cases/compiler/discriminantsAndPrimitives.ts @@ -0,0 +1,49 @@ +// @strictNullChecks: true + +// Repro from #10257 plus other tests + +interface Foo { + kind: "foo"; + name: string; +} + +interface Bar { + kind: "bar"; + length: string; +} + +function f1(x: Foo | Bar | string) { + if (typeof x !== 'string') { + switch(x.kind) { + case 'foo': + x.name; + } + } +} + +function f2(x: Foo | Bar | string | undefined) { + if (typeof x === "object") { + switch(x.kind) { + case 'foo': + x.name; + } + } +} + +function f3(x: Foo | Bar | string | null) { + if (x && typeof x !== "string") { + switch(x.kind) { + case 'foo': + x.name; + } + } +} + +function f4(x: Foo | Bar | string | number | null) { + if (x && typeof x === "object") { + switch(x.kind) { + case 'foo': + x.name; + } + } +} \ No newline at end of file From 644d6554fb3d4d9d2afb3ea494d695a303ae7712 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 11 Aug 2016 16:04:46 -0700 Subject: [PATCH 36/72] Optimize core filter function to only allocate when necessary --- src/compiler/core.ts | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 549410159b0..1d89d0092f3 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -147,17 +147,29 @@ namespace ts { return count; } + /** + * Filters an array by a predicate function. Returns the same array instance if the predicate is + * true for all elements, otherwise returns a new array instance containing the filtered subset. + */ export function filter(array: T[], f: (x: T) => boolean): T[] { - let result: T[]; if (array) { - result = []; - for (const item of array) { - if (f(item)) { - result.push(item); + const len = array.length; + let i = 0; + while (i < len && f(array[i])) i++; + if (i < len) { + const result = array.slice(0, i); + i++; + while (i < len) { + const item = array[i]; + if (f(item)) { + result.push(item); + } + i++; } + return result; } } - return result; + return array; } export function filterMutate(array: T[], f: (x: T) => boolean): void { From f1ea145e052cea7af3a9cbdf42f07d4f2728cb45 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 11 Aug 2016 16:06:03 -0700 Subject: [PATCH 37/72] Address CR comments + more optimizations --- src/compiler/checker.ts | 57 +++++++++-------------------------------- 1 file changed, 12 insertions(+), 45 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b6d03737810..0fb626e6281 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7852,7 +7852,7 @@ namespace ts { if (type && type.flags & TypeFlags.Union) { let prop = getPropertyOfType(type, name); if (!prop) { - // The type may be a union that includes nullable or primtive types. If filtering + // The type may be a union that includes nullable or primitive types. If filtering // those out produces a different type, get the property from that type instead. // Effectively, we're checking if this *could* be a discriminant property once nullable // and primitive types are removed by other type guards. @@ -7915,10 +7915,10 @@ namespace ts { // For example, when a variable of type number | string | boolean is assigned a value of type number | boolean, // we remove type string. function getAssignmentReducedType(declaredType: UnionType, assignedType: Type) { - if (declaredType !== assignedType && declaredType.flags & TypeFlags.Union) { - const reducedTypes = filter(declaredType.types, t => typeMaybeAssignableTo(assignedType, t)); - if (reducedTypes.length) { - return reducedTypes.length === 1 ? reducedTypes[0] : getUnionType(reducedTypes); + if (declaredType !== assignedType) { + const reducedType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t)); + if (reducedType !== neverType) { + return reducedType; } } return declaredType; @@ -7992,26 +7992,7 @@ namespace ts { } function getTypeWithFacts(type: Type, include: TypeFacts) { - if (!(type.flags & TypeFlags.Union)) { - return getTypeFacts(type) & include ? type : neverType; - } - const types = (type).types; - const length = types.length; - let i = 0; - while (i < length && getTypeFacts(types[i]) & include) i++; - if (i === length) { - return type; - } - const filtered = types.slice(0, i); - i++; - while (i < length) { - const t = types[i]; - if (getTypeFacts(t) & include) { - filtered.push(t); - } - i++; - } - return getUnionType(filtered); + return filterType(type, t => (getTypeFacts(t) & include) !== 0); } function getTypeWithDefault(type: Type, defaultExpression: Expression) { @@ -8191,22 +8172,8 @@ namespace ts { return f(type) ? type : neverType; } const types = (type).types; - const length = types.length; - let i = 0; - while (i < length && f(types[i])) i++; - if (i === length) { - return type; - } - const filtered = types.slice(0, i); - i++; - while (i < length) { - const t = types[i]; - if (f(t)) { - filtered.push(t); - } - i++; - } - return getUnionType(filtered); + const filtered = filter(types, f); + return filtered === types ? type : getUnionType(filtered); } function isIncomplete(flowType: FlowType) { @@ -8544,7 +8511,7 @@ namespace ts { } if (assumeTrue && !(type.flags & TypeFlags.Union)) { // We narrow a non-union type to an exact primitive type if the non-union type - // is a supertype of that primtive type. For example, type 'any' can be narrowed + // is a supertype of that primitive type. For example, type 'any' can be narrowed // to one of the primitive types. const targetType = getProperty(typeofTypesByName, literal.text); if (targetType && isTypeSubtypeOf(targetType, type)) { @@ -8633,9 +8600,9 @@ namespace ts { // If the current type is a union type, remove all constituents that couldn't be instances of // the candidate type. If one or more constituents remain, return a union of those. if (type.flags & TypeFlags.Union) { - const assignableConstituents = filter((type).types, t => isTypeInstanceOf(t, candidate)); - if (assignableConstituents.length) { - return getUnionType(assignableConstituents); + const assignableType = filterType(type, t => isTypeInstanceOf(t, candidate)); + if (assignableType !== neverType) { + return assignableType; } } // If the candidate type is a subtype of the target type, narrow to the candidate type. From e64675eb85ad9bf346e0cd7495cb98e258be221d Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 12 Aug 2016 06:54:51 -0700 Subject: [PATCH 38/72] Faster path for creating union types from filterType --- src/compiler/checker.ts | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0fb626e6281..4e04c5fc11a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5352,12 +5352,12 @@ namespace ts { } } - // We deduplicate the constituent types based on object identity. If the subtypeReduction flag is - // specified we also reduce the constituent type set to only include types that aren't subtypes of - // other types. Subtype reduction is expensive for large union types and is possible only when union + // We sort and deduplicate the constituent types based on object identity. If the subtypeReduction + // flag is specified we also reduce the constituent type set to only include types that aren't subtypes + // of other types. Subtype reduction is expensive for large union types and is possible only when union // types are known not to circularly reference themselves (as is the case with union types created by // expression constructs such as array literals and the || and ?: operators). Named types can - // circularly reference themselves and therefore cannot be deduplicated during their declaration. + // circularly reference themselves and therefore cannot be subtype reduced during their declaration. // For example, "type Item = string | (() => Item" is a named type that circularly references itself. function getUnionType(types: Type[], subtypeReduction?: boolean, aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type { if (types.length === 0) { @@ -5379,15 +5379,23 @@ namespace ts { typeSet.containsUndefined ? typeSet.containsNonWideningType ? undefinedType : undefinedWideningType : neverType; } - else if (typeSet.length === 1) { - return typeSet[0]; + return getUnionTypeFromSortedList(typeSet, aliasSymbol, aliasTypeArguments); + } + + // This function assumes the constituent type list is sorted and deduplicated. + function getUnionTypeFromSortedList(types: Type[], aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type { + if (types.length === 0) { + return neverType; } - const id = getTypeListId(typeSet); + if (types.length === 1) { + return types[0]; + } + const id = getTypeListId(types); let type = unionTypes[id]; if (!type) { - const propagatedFlags = getPropagatingFlagsOfTypes(typeSet, /*excludeKinds*/ TypeFlags.Nullable); + const propagatedFlags = getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable); type = unionTypes[id] = createObjectType(TypeFlags.Union | propagatedFlags); - type.types = typeSet; + type.types = types; type.aliasSymbol = aliasSymbol; type.aliasTypeArguments = aliasTypeArguments; } @@ -8168,12 +8176,12 @@ namespace ts { } function filterType(type: Type, f: (t: Type) => boolean): Type { - if (!(type.flags & TypeFlags.Union)) { - return f(type) ? type : neverType; + if (type.flags & TypeFlags.Union) { + const types = (type).types; + const filtered = filter(types, f); + return filtered === types ? type : getUnionTypeFromSortedList(filtered); } - const types = (type).types; - const filtered = filter(types, f); - return filtered === types ? type : getUnionType(filtered); + return f(type) ? type : neverType; } function isIncomplete(flowType: FlowType) { From df739fdd50b3ea3fcd8a5bd04614fe2093a4aa39 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Thu, 11 Aug 2016 12:26:22 -0700 Subject: [PATCH 39/72] Allow an @types direcotry to have a package.json which specifies `"typings": null` to disclude it from automatically included typings. --- src/compiler/program.ts | 89 ++++++++++--------- src/harness/compilerRunner.ts | 2 +- src/harness/fourslash.ts | 19 ++-- tests/baselines/reference/typingsLookup2.js | 9 ++ .../reference/typingsLookup2.symbols | 3 + .../reference/typingsLookup2.trace.json | 1 + .../baselines/reference/typingsLookup2.types | 3 + .../conformance/typings/typingsLookup2.ts | 13 +++ 8 files changed, 86 insertions(+), 53 deletions(-) create mode 100644 tests/baselines/reference/typingsLookup2.js create mode 100644 tests/baselines/reference/typingsLookup2.symbols create mode 100644 tests/baselines/reference/typingsLookup2.trace.json create mode 100644 tests/baselines/reference/typingsLookup2.types create mode 100644 tests/cases/conformance/typings/typingsLookup2.ts diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 7d40b2f3219..67a93606cc6 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -119,49 +119,31 @@ namespace ts { } function tryReadTypesSection(packageJsonPath: string, baseDirectory: string, state: ModuleResolutionState): string { - let jsonContent: { typings?: string, types?: string, main?: string }; - try { - const jsonText = state.host.readFile(packageJsonPath); - jsonContent = jsonText ? <{ typings?: string, types?: string, main?: string }>JSON.parse(jsonText) : {}; - } - catch (e) { - // gracefully handle if readFile fails or returns not JSON - jsonContent = {}; + const jsonContent = readJson(packageJsonPath, state.host); + + function tryReadFromField(fieldName: string) { + if (hasProperty(jsonContent, fieldName)) { + const typesFile = (jsonContent)[fieldName]; + if (typeof typesFile === "string") { + const typesFilePath = normalizePath(combinePaths(baseDirectory, typesFile)); + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_has_0_field_1_that_references_2, fieldName, typesFile, typesFilePath); + } + return typesFilePath; + } + else { + if (state.traceEnabled) { + trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_string_got_1, fieldName, typeof typesFile); + } + } + } } - let typesFile: string; - let fieldName: string; - // first try to read content of 'typings' section (backward compatibility) - if (jsonContent.typings) { - if (typeof jsonContent.typings === "string") { - fieldName = "typings"; - typesFile = jsonContent.typings; - } - else { - if (state.traceEnabled) { - trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_string_got_1, "typings", typeof jsonContent.typings); - } - } - } - // then read 'types' - if (!typesFile && jsonContent.types) { - if (typeof jsonContent.types === "string") { - fieldName = "types"; - typesFile = jsonContent.types; - } - else { - if (state.traceEnabled) { - trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_string_got_1, "types", typeof jsonContent.types); - } - } - } - if (typesFile) { - const typesFilePath = normalizePath(combinePaths(baseDirectory, typesFile)); - if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_has_0_field_1_that_references_2, fieldName, typesFile, typesFilePath); - } + const typesFilePath = tryReadFromField("typings") || tryReadFromField("types"); + if (typesFilePath) { return typesFilePath; } + // Use the main module for inferring types if no types package specified and the allowJs is set if (state.compilerOptions.allowJs && jsonContent.main && typeof jsonContent.main === "string") { if (state.traceEnabled) { @@ -173,6 +155,17 @@ namespace ts { return undefined; } + function readJson(path: string, host: ModuleResolutionHost): { typings?: string, types?: string, main?: string } { + try { + const jsonText = host.readFile(path); + return jsonText ? JSON.parse(jsonText) : {}; + } + catch (e) { + // gracefully handle if readFile fails or returns not JSON + return {}; + } + } + const typeReferenceExtensions = [".d.ts"]; function getEffectiveTypeRoots(options: CompilerOptions, host: ModuleResolutionHost) { @@ -717,7 +710,7 @@ namespace ts { } function loadNodeModuleFromDirectory(extensions: string[], candidate: string, failedLookupLocation: string[], onlyRecordFailures: boolean, state: ModuleResolutionState): string { - const packageJsonPath = combinePaths(candidate, "package.json"); + const packageJsonPath = pathToPackageJson(candidate); const directoryExists = !onlyRecordFailures && directoryProbablyExists(candidate, state.host); if (directoryExists && state.host.fileExists(packageJsonPath)) { if (state.traceEnabled) { @@ -747,6 +740,10 @@ namespace ts { return loadModuleFromFile(combinePaths(candidate, "index"), extensions, failedLookupLocation, !directoryExists, state); } + function pathToPackageJson(directory: string): string { + return combinePaths(directory, "package.json"); + } + function loadModuleFromNodeModulesFolder(moduleName: string, directory: string, failedLookupLocations: string[], state: ModuleResolutionState): string { const nodeModulesFolder = combinePaths(directory, "node_modules"); const nodeModulesFolderExists = directoryProbablyExists(nodeModulesFolder, state.host); @@ -1070,15 +1067,21 @@ namespace ts { } // Walk the primary type lookup locations - let result: string[] = []; + const result: string[] = []; if (host.directoryExists && host.getDirectories) { const typeRoots = getEffectiveTypeRoots(options, host); if (typeRoots) { for (const root of typeRoots) { if (host.directoryExists(root)) { for (const typeDirectivePath of host.getDirectories(root)) { - // Return just the type directive names - result = result.concat(getBaseFileName(normalizePath(typeDirectivePath))); + const normalized = normalizePath(typeDirectivePath); + const packageJsonPath = pathToPackageJson(combinePaths(root, normalized)); + // tslint:disable-next-line:no-null-keyword + const isNotNeededPackage = host.fileExists(packageJsonPath) && readJson(packageJsonPath, host).typings === null; + if (!isNotNeededPackage) { + // Return just the type directive names + result.push(getBaseFileName(normalized)); + } } } } diff --git a/src/harness/compilerRunner.ts b/src/harness/compilerRunner.ts index ecdc18394b0..35e09c9c0d0 100644 --- a/src/harness/compilerRunner.ts +++ b/src/harness/compilerRunner.ts @@ -52,7 +52,7 @@ class CompilerBaselineRunner extends RunnerBase { private makeUnitName(name: string, root: string) { const path = ts.toPath(name, root, (fileName) => Harness.Compiler.getCanonicalFileName(fileName)); const pathStart = ts.toPath(Harness.IO.getCurrentDirectory(), "", (fileName) => Harness.Compiler.getCanonicalFileName(fileName)); - return path.replace(pathStart, "/"); + return pathStart ? path.replace(pathStart, "/") : path; }; public checkTestCodeOutput(fileName: string) { diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index a42abbbc609..620eee584e7 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2395,13 +2395,14 @@ ${code} // Comment line, check for global/file @options and record them const match = optionRegex.exec(line.substr(2)); if (match) { - const fileMetadataNamesIndex = fileMetadataNames.indexOf(match[1]); + const [key, value] = match.slice(1); + const fileMetadataNamesIndex = fileMetadataNames.indexOf(key); if (fileMetadataNamesIndex === -1) { // Check if the match is already existed in the global options - if (globalOptions[match[1]] !== undefined) { - throw new Error("Global Option : '" + match[1] + "' is already existed"); + if (globalOptions[key] !== undefined) { + throw new Error(`Global option '${key}' already exists`); } - globalOptions[match[1]] = match[2]; + globalOptions[key] = value; } else { if (fileMetadataNamesIndex === fileMetadataNames.indexOf(metadataOptionNames.fileName)) { @@ -2416,12 +2417,12 @@ ${code} resetLocalData(); } - currentFileName = basePath + "/" + match[2]; - currentFileOptions[match[1]] = match[2]; + currentFileName = basePath + "/" + value; + currentFileOptions[key] = value; } else { // Add other fileMetadata flag - currentFileOptions[match[1]] = match[2]; + currentFileOptions[key] = value; } } } @@ -2509,7 +2510,7 @@ ${code} } const marker: Marker = { - fileName: fileName, + fileName, position: location.position, data: markerValue }; @@ -2526,7 +2527,7 @@ ${code} function recordMarker(fileName: string, location: LocationInformation, name: string, markerMap: MarkerMap, markers: Marker[]): Marker { const marker: Marker = { - fileName: fileName, + fileName, position: location.position }; diff --git a/tests/baselines/reference/typingsLookup2.js b/tests/baselines/reference/typingsLookup2.js new file mode 100644 index 00000000000..3e816526af2 --- /dev/null +++ b/tests/baselines/reference/typingsLookup2.js @@ -0,0 +1,9 @@ +//// [tests/cases/conformance/typings/typingsLookup2.ts] //// + +//// [package.json] +{ "typings": null } + +//// [a.ts] + + +//// [a.js] diff --git a/tests/baselines/reference/typingsLookup2.symbols b/tests/baselines/reference/typingsLookup2.symbols new file mode 100644 index 00000000000..7223c8589a6 --- /dev/null +++ b/tests/baselines/reference/typingsLookup2.symbols @@ -0,0 +1,3 @@ +=== /a.ts === + +No type information for this code. \ No newline at end of file diff --git a/tests/baselines/reference/typingsLookup2.trace.json b/tests/baselines/reference/typingsLookup2.trace.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/tests/baselines/reference/typingsLookup2.trace.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/baselines/reference/typingsLookup2.types b/tests/baselines/reference/typingsLookup2.types new file mode 100644 index 00000000000..7223c8589a6 --- /dev/null +++ b/tests/baselines/reference/typingsLookup2.types @@ -0,0 +1,3 @@ +=== /a.ts === + +No type information for this code. \ No newline at end of file diff --git a/tests/cases/conformance/typings/typingsLookup2.ts b/tests/cases/conformance/typings/typingsLookup2.ts new file mode 100644 index 00000000000..90e1e463f0a --- /dev/null +++ b/tests/cases/conformance/typings/typingsLookup2.ts @@ -0,0 +1,13 @@ +// @traceResolution: true +// @noImplicitReferences: true +// @currentDirectory: / +// This tests that an @types package with `"typings": null` is not automatically included. +// (If it were, this test would break because there are no typings to be found.) + +// @filename: /tsconfig.json +{} + +// @filename: /node_modules/@types/angular2/package.json +{ "typings": null } + +// @filename: /a.ts From b0a4e2785d3febd578b88a825a363a9401285e6c Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Fri, 12 Aug 2016 13:36:54 -0700 Subject: [PATCH 40/72] Lint --- Gulpfile.ts | 11 +++-------- src/harness/harness.ts | 2 +- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/Gulpfile.ts b/Gulpfile.ts index e2d37277bcf..f0e274f3f1e 100644 --- a/Gulpfile.ts +++ b/Gulpfile.ts @@ -531,8 +531,6 @@ const localRwcBaseline = path.join(internalTests, "baselines/rwc/local"); const refRwcBaseline = path.join(internalTests, "baselines/rwc/reference"); const localTest262Baseline = path.join(internalTests, "baselines/test262/local"); -const refTest262Baseline = path.join(internalTests, "baselines/test262/reference"); - gulp.task("tests", "Builds the test infrastructure using the built compiler", [run]); gulp.task("tests-debug", "Builds the test sources and automation in debug mode", () => { @@ -811,8 +809,6 @@ gulp.task("diff-rwc", "Diffs the RWC baselines using the diff tool specified by exec(getDiffTool(), [refRwcBaseline, localRwcBaseline], done, done); }); - -const deleteEnding = '.delete'; gulp.task("baseline-accept", "Makes the most recent test results the new baseline, overwriting the old baseline", () => { return baselineAccept(""); }); @@ -827,13 +823,12 @@ function baselineCopy(subfolder = "") { } function baselineDelete(subfolder = "") { - return gulp.src(['tests/baselines/local/**/*.delete']) + return gulp.src(["tests/baselines/local/**/*.delete"]) .pipe(insert.transform((content, fileObj) => { - const target = path.join(refBaseline, fileObj.relative.substr(0, fileObj.relative.length - '.delete'.length)); - console.log(target); + const target = path.join(refBaseline, fileObj.relative.substr(0, fileObj.relative.length - ".delete".length)); del.sync(target); del.sync(fileObj.path); - return ''; + return ""; })); } diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 18a440c789b..81567265d3f 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -1667,7 +1667,7 @@ namespace Harness { const encoded_actual = Utils.encodeString(actual); if (expected !== encoded_actual) { if (actual === NoContent) { - IO.writeFile(relativeFileName + '.delete', ''); + IO.writeFile(relativeFileName + ".delete", ""); } else { IO.writeFile(relativeFileName, actual); From d2060466590a9cf3064da589cd321f405525f43f Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 12 Aug 2016 14:00:50 -0700 Subject: [PATCH 41/72] Collect timing information for commands running on travis (#10308) --- Jakefile.js | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/Jakefile.js b/Jakefile.js index 0624c92f162..01aac82a47a 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -33,6 +33,28 @@ if (process.env.path !== undefined) { process.env.PATH = nodeModulesPathPrefix + process.env.PATH; } +function toNs(diff) { + return diff[0] * 1e9 + diff[1]; +} + +function mark() { + if (!fold.isTravis()) return; + var stamp = process.hrtime(); + var id = Math.floor(Math.random() * 0xFFFFFFFF).toString(16); + console.log("travis_time:start:" + id + "\r"); + return { + stamp: stamp, + id: id + }; +} + +function measure(marker) { + if (!fold.isTravis()) return; + var diff = process.hrtime(marker.stamp); + var total = [marker.stamp[0] + diff[0], marker.stamp[1] + diff[1]]; + console.log("travis_time:end:" + marker.id + ":start=" + toNs(marker.stamp) + ",finish=" + toNs(total) + ",duration=" + toNs(diff) + "\r"); +} + var compilerSources = [ "core.ts", "performance.ts", @@ -286,6 +308,7 @@ var builtLocalCompiler = path.join(builtLocalDirectory, compilerFilename); */ function compileFile(outFile, sources, prereqs, prefixes, useBuiltCompiler, opts, callback) { file(outFile, prereqs, function() { + var startCompileTime = mark(); opts = opts || {}; var compilerPath = useBuiltCompiler ? builtLocalCompiler : LKGCompiler; var options = "--noImplicitAny --noImplicitThis --noEmitOnError --types " @@ -362,11 +385,13 @@ function compileFile(outFile, sources, prereqs, prefixes, useBuiltCompiler, opts callback(); } + measure(startCompileTime); complete(); }); ex.addListener("error", function() { fs.unlinkSync(outFile); fail("Compilation of " + outFile + " unsuccessful"); + measure(startCompileTime); }); ex.run(); }, {async: true}); @@ -769,6 +794,7 @@ function runConsoleTests(defaultReporter, runInParallel) { // timeout normally isn't necessary but Travis-CI has been timing out on compiler baselines occasionally // default timeout is 2sec which really should be enough, but maybe we just need a small amount longer if(!runInParallel) { + var startTime = mark(); tests = tests ? ' -g "' + tests + '"' : ''; var cmd = "mocha" + (debug ? " --debug-brk" : "") + " -R " + reporter + tests + colors + bail + ' -t ' + testTimeout + ' ' + run; console.log(cmd); @@ -777,10 +803,12 @@ function runConsoleTests(defaultReporter, runInParallel) { process.env.NODE_ENV = "development"; exec(cmd, function () { process.env.NODE_ENV = savedNodeEnv; + measure(startTime); runLinter(); finish(); }, function(e, status) { process.env.NODE_ENV = savedNodeEnv; + measure(startTime); finish(status); }); @@ -788,9 +816,10 @@ function runConsoleTests(defaultReporter, runInParallel) { else { var savedNodeEnv = process.env.NODE_ENV; process.env.NODE_ENV = "development"; + var startTime = mark(); runTestsInParallel(taskConfigsFolder, run, { testTimeout: testTimeout, noColors: colors === " --no-colors " }, function (err) { process.env.NODE_ENV = savedNodeEnv; - + measure(startTime); // last worker clean everything and runs linter in case if there were no errors deleteTemporaryProjectOutput(); jake.rmRf(taskConfigsFolder); @@ -1069,6 +1098,7 @@ var lintTargets = compilerSources desc("Runs tslint on the compiler sources. Optional arguments are: f[iles]=regex"); task("lint", ["build-rules"], function() { if (fold.isTravis()) console.log(fold.start("lint")); + var startTime = mark(); var lintOptions = getLinterOptions(); var failed = 0; var fileMatcher = RegExp(process.env.f || process.env.file || process.env.files || ""); @@ -1084,6 +1114,7 @@ task("lint", ["build-rules"], function() { done[target] = true; } } + measure(startTime); if (fold.isTravis()) console.log(fold.end("lint")); if (failed > 0) { fail('Linter errors.', failed); From c81624435a798249530f6d2b12687db3fa391bde Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Fri, 12 Aug 2016 16:34:35 -0700 Subject: [PATCH 42/72] Simplifies performance API --- src/compiler/binder.ts | 5 +- src/compiler/checker.ts | 7 +-- src/compiler/parser.ts | 6 +- src/compiler/performance.ts | 116 ++++++++++++++++-------------------- src/compiler/program.ts | 21 ++++--- src/compiler/sourcemap.ts | 10 +++- 6 files changed, 80 insertions(+), 85 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index c355d6c9186..ff5a8b77cfd 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -89,9 +89,10 @@ namespace ts { const binder = createBinder(); export function bindSourceFile(file: SourceFile, options: CompilerOptions) { - const start = performance.mark(); + performance.mark("bindStart"); binder(file, options); - performance.measure("Bind", start); + performance.mark("bindEnd"); + performance.measure("Bind", "bindStart", "bindEnd"); } function createBinder(): (file: SourceFile, options: CompilerOptions) => void { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1b7b659e1a6..f1fff6737a3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17535,11 +17535,10 @@ namespace ts { } function checkSourceFile(node: SourceFile) { - const start = performance.mark(); - + performance.mark("checkStart"); checkSourceFileWorker(node); - - performance.measure("Check", start); + performance.mark("checkEnd"); + performance.measure("Check", "checkStart", "checkEnd"); } // Fully type check a source file and collect the relevant diagnostics. diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 6f38783d5ed..cbf767a5410 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -421,10 +421,10 @@ namespace ts { } export function createSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, setParentNodes = false, scriptKind?: ScriptKind): SourceFile { - const start = performance.mark(); + performance.mark("parseStart"); const result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes, scriptKind); - - performance.measure("Parse", start); + performance.mark("parseEnd"); + performance.measure("Parse", "parseStart", "parseEnd"); return result; } diff --git a/src/compiler/performance.ts b/src/compiler/performance.ts index e496353fde8..e8f064e81cd 100644 --- a/src/compiler/performance.ts +++ b/src/compiler/performance.ts @@ -6,71 +6,57 @@ namespace ts { } /*@internal*/ +/** Performance measurements for the compiler. */ namespace ts.performance { - /** Performance measurements for the compiler. */ declare const onProfilerEvent: { (markName: string): void; profiler: boolean; }; - let profilerEvent: (markName: string) => void; - let counters: MapLike; - let measures: MapLike; + + const profilerEvent = typeof onProfilerEvent === "function" && onProfilerEvent.profiler === true + ? onProfilerEvent + : (markName: string) => { }; + + let enabled = false; + let profilerStart = 0; + let counts: Map; + let marks: Map; + let measures: Map; /** - * Emit a performance event if ts-profiler is connected. This is primarily used - * to generate heap snapshots. + * Marks a performance event. * - * @param eventName A name for the event. + * @param markName The name of the mark. */ - export function emit(eventName: string) { - if (profilerEvent) { - profilerEvent(eventName); + export function mark(markName: string) { + if (enabled) { + marks[markName] = timestamp(); + counts[markName] = (counts[markName] || 0) + 1; + profilerEvent(markName); } } - /** - * Increments a counter with the specified name. - * - * @param counterName The name of the counter. - */ - export function increment(counterName: string) { - if (counters) { - counters[counterName] = (getProperty(counters, counterName) || 0) + 1; - } - } - - /** - * Gets the value of the counter with the specified name. - * - * @param counterName The name of the counter. - */ - export function getCount(counterName: string) { - return counters && getProperty(counters, counterName) || 0; - } - - /** - * Marks the start of a performance measurement. - */ - export function mark() { - return measures ? timestamp() : 0; - } - /** * Adds a performance measurement with the specified name. * * @param measureName The name of the performance measurement. - * @param marker The timestamp of the starting mark. + * @param startMarkName The name of the starting mark. If not supplied, the point at which the + * profiler was enabled is used. + * @param endMarkName The name of the ending mark. If not supplied, the current timestamp is + * used. */ - export function measure(measureName: string, marker: number) { - if (measures) { - measures[measureName] = (getProperty(measures, measureName) || 0) + (timestamp() - marker); + export function measure(measureName: string, startMarkName?: string, endMarkName?: string) { + if (enabled) { + const end = endMarkName && marks[endMarkName] || timestamp(); + const start = startMarkName && marks[startMarkName] || profilerStart; + measures[measureName] = (measures[measureName] || 0) + (end - start); } } /** - * Iterate over each measure, performing some action - * - * @param cb The action to perform for each measure + * Gets the number of times a marker was encountered. + * + * @param markName The name of the mark. */ - export function forEachMeasure(cb: (measureName: string, duration: number) => void) { - return forEachKey(measures, key => cb(key, measures[key])); + export function getCount(markName: string) { + return counts && counts[markName] || 0; } /** @@ -79,31 +65,31 @@ namespace ts.performance { * @param measureName The name of the measure whose durations should be accumulated. */ export function getDuration(measureName: string) { - return measures && getProperty(measures, measureName) || 0; + return measures && measures[measureName] || 0; + } + + /** + * Iterate over each measure, performing some action + * + * @param cb The action to perform for each measure + */ + export function forEachMeasure(cb: (measureName: string, duration: number) => void) { + for (const key in measures) { + cb(key, measures[key]); + } } /** Enables (and resets) performance measurements for the compiler. */ export function enable() { - counters = { }; - measures = { - "I/O Read": 0, - "I/O Write": 0, - "Program": 0, - "Parse": 0, - "Bind": 0, - "Check": 0, - "Emit": 0, - }; - - profilerEvent = typeof onProfilerEvent === "function" && onProfilerEvent.profiler === true - ? onProfilerEvent - : undefined; + counts = createMap(); + marks = createMap(); + measures = createMap(); + enabled = true; + profilerStart = timestamp(); } - /** Disables (and clears) performance measurements for the compiler. */ + /** Disables performance measurements for the compiler. */ export function disable() { - counters = undefined; - measures = undefined; - profilerEvent = undefined; + enabled = false; } } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index eb50548750d..a632eed7fd2 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -857,9 +857,10 @@ namespace ts { function getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile { let text: string; try { - const start = performance.mark(); + performance.mark("ioReadStart"); text = sys.readFile(fileName, options.charset); - performance.measure("I/O Read", start); + performance.mark("ioReadEnd"); + performance.measure("I/O Read", "ioReadStart", "ioReadEnd"); } catch (e) { if (onError) { @@ -926,7 +927,7 @@ namespace ts { function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) { try { - const start = performance.mark(); + performance.mark("ioWriteStart"); ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); if (isWatchSet(options) && sys.createHash && sys.getModifiedTime) { @@ -936,7 +937,8 @@ namespace ts { sys.writeFile(fileName, data, writeByteOrderMark); } - performance.measure("I/O Write", start); + performance.mark("ioWriteEnd"); + performance.measure("I/O Write", "ioWriteStart", "ioWriteEnd"); } catch (e) { if (onError) { @@ -1118,7 +1120,7 @@ namespace ts { // Track source files that are source files found by searching under node_modules, as these shouldn't be compiled. const sourceFilesFoundSearchingNodeModules = createMap(); - const start = performance.mark(); + performance.mark("programStart"); host = host || createCompilerHost(options); @@ -1216,8 +1218,8 @@ namespace ts { }; verifyCompilerOptions(); - - performance.measure("Program", start); + performance.mark("programEnd"); + performance.measure("Program", "programStart", "programEnd"); return program; @@ -1460,14 +1462,15 @@ namespace ts { // checked is to not pass the file to getEmitResolver. const emitResolver = getDiagnosticsProducingTypeChecker().getEmitResolver((options.outFile || options.out) ? undefined : sourceFile); - const start = performance.mark(); + performance.mark("emitStart"); const emitResult = emitFiles( emitResolver, getEmitHost(writeFileCallback), sourceFile); - performance.measure("Emit", start); + performance.mark("emitEnd"); + performance.measure("Emit", "emitStart", "emitEnd"); return emitResult; } diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index 2d8c36a3d02..52e8975db76 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -46,6 +46,7 @@ namespace ts { export function createSourceMapWriter(host: EmitHost, writer: EmitTextWriter): SourceMapWriter { const compilerOptions = host.getCompilerOptions(); + const extendedDiagnostics = compilerOptions.extendedDiagnostics; let currentSourceFile: SourceFile; let sourceMapDir: string; // The directory in which sourcemap will be let stopOverridingSpan = false; @@ -240,7 +241,9 @@ namespace ts { return; } - const start = performance.mark(); + if (extendedDiagnostics) { + performance.mark("sourcemapStart"); + } const sourceLinePos = getLineAndCharacterOfPosition(currentSourceFile, pos); @@ -282,7 +285,10 @@ namespace ts { updateLastEncodedAndRecordedSpans(); - performance.measure("Source Map", start); + if (extendedDiagnostics) { + performance.mark("sourcemapEnd"); + performance.measure("Source Map", "sourcemapStart", "sourcemapEnd"); + } } function getStartPos(range: TextRange) { From 73d53e82988ff93fa2a20346127a683333e6ab94 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Sat, 13 Aug 2016 12:18:39 -0700 Subject: [PATCH 43/72] Use 'MapLike' instead of 'Map' in 'preferConstRule.ts'. --- scripts/tslint/preferConstRule.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/scripts/tslint/preferConstRule.ts b/scripts/tslint/preferConstRule.ts index aaa1b0e53d5..1d316692468 100644 --- a/scripts/tslint/preferConstRule.ts +++ b/scripts/tslint/preferConstRule.ts @@ -1,7 +1,6 @@ import * as Lint from "tslint/lib/lint"; import * as ts from "typescript"; - export class Rule extends Lint.Rules.AbstractRule { public static FAILURE_STRING_FACTORY = (identifier: string) => `Identifier '${identifier}' never appears on the LHS of an assignment - use const instead of let for its declaration.`; @@ -64,7 +63,7 @@ interface DeclarationUsages { } class PreferConstWalker extends Lint.RuleWalker { - private inScopeLetDeclarations: ts.Map[] = []; + private inScopeLetDeclarations: ts.MapLike[] = []; private errors: Lint.RuleFailure[] = []; private markAssignment(identifier: ts.Identifier) { const name = identifier.text; @@ -172,7 +171,7 @@ class PreferConstWalker extends Lint.RuleWalker { } private visitAnyForStatement(node: ts.ForOfStatement | ts.ForInStatement) { - const names: ts.Map = {}; + const names: ts.MapLike = {}; if (isLet(node.initializer)) { if (node.initializer.kind === ts.SyntaxKind.VariableDeclarationList) { this.collectLetIdentifiers(node.initializer as ts.VariableDeclarationList, names); @@ -194,7 +193,7 @@ class PreferConstWalker extends Lint.RuleWalker { } visitBlock(node: ts.Block) { - const names: ts.Map = {}; + const names: ts.MapLike = {}; for (const statement of node.statements) { if (statement.kind === ts.SyntaxKind.VariableStatement) { this.collectLetIdentifiers((statement as ts.VariableStatement).declarationList, names); @@ -205,7 +204,7 @@ class PreferConstWalker extends Lint.RuleWalker { this.popDeclarations(); } - private collectLetIdentifiers(list: ts.VariableDeclarationList, ret: ts.Map) { + private collectLetIdentifiers(list: ts.VariableDeclarationList, ret: ts.MapLike) { for (const node of list.declarations) { if (isLet(node) && !isExported(node)) { this.collectNameIdentifiers(node, node.name, ret); @@ -213,7 +212,7 @@ class PreferConstWalker extends Lint.RuleWalker { } } - private collectNameIdentifiers(declaration: ts.VariableDeclaration, node: ts.Identifier | ts.BindingPattern, table: ts.Map) { + private collectNameIdentifiers(declaration: ts.VariableDeclaration, node: ts.Identifier | ts.BindingPattern, table: ts.MapLike) { if (node.kind === ts.SyntaxKind.Identifier) { table[(node as ts.Identifier).text] = { declaration, usages: 0 }; } @@ -222,7 +221,7 @@ class PreferConstWalker extends Lint.RuleWalker { } } - private collectBindingPatternIdentifiers(value: ts.VariableDeclaration, pattern: ts.BindingPattern, table: ts.Map) { + private collectBindingPatternIdentifiers(value: ts.VariableDeclaration, pattern: ts.BindingPattern, table: ts.MapLike) { for (const element of pattern.elements) { this.collectNameIdentifiers(value, element.name, table); } From 38353027aaa9813c646cce62900eb9694012bae5 Mon Sep 17 00:00:00 2001 From: yortus Date: Sat, 13 Aug 2016 13:57:18 +0800 Subject: [PATCH 44/72] narrow from 'any' in most situations instanceof and user-defined typeguards narrow from 'any' unless the narrowed-to type is exactly 'Object' or 'Function'. This is a breaking change. --- src/compiler/checker.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1b7b659e1a6..85e33c5e4a1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8548,10 +8548,6 @@ namespace ts { } return type; } - // We never narrow type any in an instanceof guard - if (isTypeAny(type)) { - return type; - } // Check that right operand is a function type with a prototype property const rightType = checkExpression(expr.right); @@ -8569,6 +8565,11 @@ namespace ts { } } + // Don't narrow from 'any' if the target type is exactly 'Object' or 'Function' + if (isTypeAny(type) && (targetType === globalObjectType || targetType === globalFunctionType)) { + return type; + } + if (!targetType) { // Target type is type of construct signature let constructSignatures: Signature[]; @@ -8615,7 +8616,7 @@ namespace ts { } function narrowTypeByTypePredicate(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type { - if (type.flags & TypeFlags.Any || !hasMatchingArgument(callExpression, reference)) { + if (!hasMatchingArgument(callExpression, reference)) { return type; } const signature = getResolvedSignature(callExpression); @@ -8623,6 +8624,12 @@ namespace ts { if (!predicate) { return type; } + + // Don't narrow from 'any' if the predicate type is exactly 'Object' or 'Function' + if (isTypeAny(type) && (predicate.type === globalObjectType || predicate.type === globalFunctionType)) { + return type; + } + if (isIdentifierTypePredicate(predicate)) { const predicateArgument = callExpression.arguments[predicate.parameterIndex]; if (predicateArgument) { From 59c09d90e638a709ae3fbbecf5662f1e86cefdca Mon Sep 17 00:00:00 2001 From: yortus Date: Sat, 13 Aug 2016 14:49:56 +0800 Subject: [PATCH 45/72] Update instanceof conformance tests --- ...rdsWithInstanceOfByConstructorSignature.ts | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts b/tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts index b81dd26652b..0817954c35c 100644 --- a/tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts +++ b/tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts @@ -13,7 +13,7 @@ if (obj1 instanceof A) { // narrowed to A. } var obj2: any; -if (obj2 instanceof A) { // can't narrow type from 'any' +if (obj2 instanceof A) { obj2.foo; obj2.bar; } @@ -35,7 +35,7 @@ if (obj3 instanceof B) { // narrowed to B. } var obj4: any; -if (obj4 instanceof B) { // can't narrow type from 'any' +if (obj4 instanceof B) { obj4.foo = "str"; obj4.foo = 1; obj4.bar = "str"; @@ -67,7 +67,7 @@ if (obj5 instanceof C) { // narrowed to C1|C2. } var obj6: any; -if (obj6 instanceof C) { // can't narrow type from 'any' +if (obj6 instanceof C) { obj6.foo; obj6.bar1; obj6.bar2; @@ -86,7 +86,7 @@ if (obj7 instanceof D) { // narrowed to D. } var obj8: any; -if (obj8 instanceof D) { // can't narrow type from 'any' +if (obj8 instanceof D) { obj8.foo; obj8.bar; } @@ -113,7 +113,7 @@ if (obj9 instanceof E) { // narrowed to E1 | E2 } var obj10: any; -if (obj10 instanceof E) { // can't narrow type from 'any' +if (obj10 instanceof E) { obj10.foo; obj10.bar1; obj10.bar2; @@ -136,7 +136,7 @@ if (obj11 instanceof F) { // can't type narrowing, construct signature returns a } var obj12: any; -if (obj12 instanceof F) { // can't narrow type from 'any' +if (obj12 instanceof F) { obj12.foo; obj12.bar; } @@ -161,7 +161,7 @@ if (obj13 instanceof G) { // narrowed to G1. G1 is return type of prototype prop } var obj14: any; -if (obj14 instanceof G) { // can't narrow type from 'any' +if (obj14 instanceof G) { obj14.foo1; obj14.foo2; } @@ -183,7 +183,19 @@ if (obj15 instanceof H) { // narrowed to H. } var obj16: any; -if (obj16 instanceof H) { // can't narrow type from 'any' +if (obj16 instanceof H) { obj16.foo1; obj16.foo2; } + +var obj17: any; +if (obj17 instanceof Object) { // can't narrow type from 'any' to 'Object' + obj17.foo1; + obj17.foo2; +} + +var obj18: any; +if (obj18 instanceof Function) { // can't narrow type from 'any' to 'Function' + obj18.foo1; + obj18.foo2; +} From cc8f3269611565ac098aa644d1cc1522a4642bf5 Mon Sep 17 00:00:00 2001 From: yortus Date: Sun, 14 Aug 2016 21:42:31 +0800 Subject: [PATCH 46/72] accept new baselines --- ...nstanceOfByConstructorSignature.errors.txt | 60 ++++++++++++++++--- ...rdsWithInstanceOfByConstructorSignature.js | 38 +++++++++--- 2 files changed, 81 insertions(+), 17 deletions(-) diff --git a/tests/baselines/reference/typeGuardsWithInstanceOfByConstructorSignature.errors.txt b/tests/baselines/reference/typeGuardsWithInstanceOfByConstructorSignature.errors.txt index c574118f91d..b67f7328856 100644 --- a/tests/baselines/reference/typeGuardsWithInstanceOfByConstructorSignature.errors.txt +++ b/tests/baselines/reference/typeGuardsWithInstanceOfByConstructorSignature.errors.txt @@ -1,16 +1,26 @@ tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(12,10): error TS2339: Property 'bar' does not exist on type 'A'. +tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(18,10): error TS2339: Property 'bar' does not exist on type 'A'. tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(33,5): error TS2322: Type 'string' is not assignable to type 'number'. tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(34,10): error TS2339: Property 'bar' does not exist on type 'B'. +tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(41,10): error TS2339: Property 'bar' does not exist on type 'B'. tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(66,10): error TS2339: Property 'bar2' does not exist on type 'C1'. +tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(72,10): error TS2339: Property 'bar1' does not exist on type 'C1 | C2'. +tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(73,10): error TS2339: Property 'bar2' does not exist on type 'C1 | C2'. tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(85,10): error TS2339: Property 'bar' does not exist on type 'D'. +tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(91,10): error TS2339: Property 'bar' does not exist on type 'D'. tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(112,10): error TS2339: Property 'bar2' does not exist on type 'E1'. +tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(118,11): error TS2339: Property 'bar1' does not exist on type 'E1 | E2'. +tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(119,11): error TS2339: Property 'bar2' does not exist on type 'E1 | E2'. tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(134,11): error TS2339: Property 'foo' does not exist on type 'string | F'. tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(135,11): error TS2339: Property 'bar' does not exist on type 'string | F'. tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(160,11): error TS2339: Property 'foo2' does not exist on type 'G1'. +tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(166,11): error TS2339: Property 'foo2' does not exist on type 'G1'. tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(182,11): error TS2339: Property 'bar' does not exist on type 'H'. +tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(187,11): error TS2339: Property 'foo1' does not exist on type 'H'. +tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(188,11): error TS2339: Property 'foo2' does not exist on type 'H'. -==== tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts (10 errors) ==== +==== tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts (20 errors) ==== interface AConstructor { new (): A; } @@ -28,9 +38,11 @@ tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstru } var obj2: any; - if (obj2 instanceof A) { // can't narrow type from 'any' + if (obj2 instanceof A) { obj2.foo; obj2.bar; + ~~~ +!!! error TS2339: Property 'bar' does not exist on type 'A'. } // a construct signature with generics @@ -54,10 +66,12 @@ tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstru } var obj4: any; - if (obj4 instanceof B) { // can't narrow type from 'any' + if (obj4 instanceof B) { obj4.foo = "str"; obj4.foo = 1; obj4.bar = "str"; + ~~~ +!!! error TS2339: Property 'bar' does not exist on type 'B'. } // has multiple construct signature @@ -88,10 +102,14 @@ tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstru } var obj6: any; - if (obj6 instanceof C) { // can't narrow type from 'any' + if (obj6 instanceof C) { obj6.foo; obj6.bar1; + ~~~~ +!!! error TS2339: Property 'bar1' does not exist on type 'C1 | C2'. obj6.bar2; + ~~~~ +!!! error TS2339: Property 'bar2' does not exist on type 'C1 | C2'. } // with object type literal @@ -109,9 +127,11 @@ tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstru } var obj8: any; - if (obj8 instanceof D) { // can't narrow type from 'any' + if (obj8 instanceof D) { obj8.foo; obj8.bar; + ~~~ +!!! error TS2339: Property 'bar' does not exist on type 'D'. } // a construct signature that returns a union type @@ -138,10 +158,14 @@ tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstru } var obj10: any; - if (obj10 instanceof E) { // can't narrow type from 'any' + if (obj10 instanceof E) { obj10.foo; obj10.bar1; + ~~~~ +!!! error TS2339: Property 'bar1' does not exist on type 'E1 | E2'. obj10.bar2; + ~~~~ +!!! error TS2339: Property 'bar2' does not exist on type 'E1 | E2'. } // a construct signature that returns any @@ -165,7 +189,7 @@ tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstru } var obj12: any; - if (obj12 instanceof F) { // can't narrow type from 'any' + if (obj12 instanceof F) { obj12.foo; obj12.bar; } @@ -192,9 +216,11 @@ tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstru } var obj14: any; - if (obj14 instanceof G) { // can't narrow type from 'any' + if (obj14 instanceof G) { obj14.foo1; obj14.foo2; + ~~~~ +!!! error TS2339: Property 'foo2' does not exist on type 'G1'. } // a type with a prototype that has any type @@ -216,8 +242,24 @@ tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstru } var obj16: any; - if (obj16 instanceof H) { // can't narrow type from 'any' + if (obj16 instanceof H) { obj16.foo1; + ~~~~ +!!! error TS2339: Property 'foo1' does not exist on type 'H'. obj16.foo2; + ~~~~ +!!! error TS2339: Property 'foo2' does not exist on type 'H'. + } + + var obj17: any; + if (obj17 instanceof Object) { // can't narrow type from 'any' to 'Object' + obj17.foo1; + obj17.foo2; + } + + var obj18: any; + if (obj18 instanceof Function) { // can't narrow type from 'any' to 'Function' + obj18.foo1; + obj18.foo2; } \ No newline at end of file diff --git a/tests/baselines/reference/typeGuardsWithInstanceOfByConstructorSignature.js b/tests/baselines/reference/typeGuardsWithInstanceOfByConstructorSignature.js index 7e6b3324470..40ef6587e75 100644 --- a/tests/baselines/reference/typeGuardsWithInstanceOfByConstructorSignature.js +++ b/tests/baselines/reference/typeGuardsWithInstanceOfByConstructorSignature.js @@ -14,7 +14,7 @@ if (obj1 instanceof A) { // narrowed to A. } var obj2: any; -if (obj2 instanceof A) { // can't narrow type from 'any' +if (obj2 instanceof A) { obj2.foo; obj2.bar; } @@ -36,7 +36,7 @@ if (obj3 instanceof B) { // narrowed to B. } var obj4: any; -if (obj4 instanceof B) { // can't narrow type from 'any' +if (obj4 instanceof B) { obj4.foo = "str"; obj4.foo = 1; obj4.bar = "str"; @@ -68,7 +68,7 @@ if (obj5 instanceof C) { // narrowed to C1|C2. } var obj6: any; -if (obj6 instanceof C) { // can't narrow type from 'any' +if (obj6 instanceof C) { obj6.foo; obj6.bar1; obj6.bar2; @@ -87,7 +87,7 @@ if (obj7 instanceof D) { // narrowed to D. } var obj8: any; -if (obj8 instanceof D) { // can't narrow type from 'any' +if (obj8 instanceof D) { obj8.foo; obj8.bar; } @@ -114,7 +114,7 @@ if (obj9 instanceof E) { // narrowed to E1 | E2 } var obj10: any; -if (obj10 instanceof E) { // can't narrow type from 'any' +if (obj10 instanceof E) { obj10.foo; obj10.bar1; obj10.bar2; @@ -137,7 +137,7 @@ if (obj11 instanceof F) { // can't type narrowing, construct signature returns a } var obj12: any; -if (obj12 instanceof F) { // can't narrow type from 'any' +if (obj12 instanceof F) { obj12.foo; obj12.bar; } @@ -162,7 +162,7 @@ if (obj13 instanceof G) { // narrowed to G1. G1 is return type of prototype prop } var obj14: any; -if (obj14 instanceof G) { // can't narrow type from 'any' +if (obj14 instanceof G) { obj14.foo1; obj14.foo2; } @@ -184,10 +184,22 @@ if (obj15 instanceof H) { // narrowed to H. } var obj16: any; -if (obj16 instanceof H) { // can't narrow type from 'any' +if (obj16 instanceof H) { obj16.foo1; obj16.foo2; } + +var obj17: any; +if (obj17 instanceof Object) { // can't narrow type from 'any' to 'Object' + obj17.foo1; + obj17.foo2; +} + +var obj18: any; +if (obj18 instanceof Function) { // can't narrow type from 'any' to 'Function' + obj18.foo1; + obj18.foo2; +} //// [typeGuardsWithInstanceOfByConstructorSignature.js] @@ -278,3 +290,13 @@ if (obj16 instanceof H) { obj16.foo1; obj16.foo2; } +var obj17; +if (obj17 instanceof Object) { + obj17.foo1; + obj17.foo2; +} +var obj18; +if (obj18 instanceof Function) { + obj18.foo1; + obj18.foo2; +} From 66047c8b184f231cbf77e6c3ca5d78909e038388 Mon Sep 17 00:00:00 2001 From: yortus Date: Sun, 14 Aug 2016 22:56:36 +0800 Subject: [PATCH 47/72] add tests --- .../narrowExceptionVariableInCatchClause.ts | 23 +++++++++++++ .../types/any/narrowFromAnyWithInstanceof.ts | 23 +++++++++++++ .../any/narrowFromAnyWithTypePredicate.ts | 34 +++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 tests/cases/conformance/types/any/narrowExceptionVariableInCatchClause.ts create mode 100644 tests/cases/conformance/types/any/narrowFromAnyWithInstanceof.ts create mode 100644 tests/cases/conformance/types/any/narrowFromAnyWithTypePredicate.ts diff --git a/tests/cases/conformance/types/any/narrowExceptionVariableInCatchClause.ts b/tests/cases/conformance/types/any/narrowExceptionVariableInCatchClause.ts new file mode 100644 index 00000000000..dfa60a415f9 --- /dev/null +++ b/tests/cases/conformance/types/any/narrowExceptionVariableInCatchClause.ts @@ -0,0 +1,23 @@ +declare function isFooError(x: any): x is { type: 'foo'; dontPanic(); }; + +function tryCatch() { + try { + // do stuff... + } + catch (err) { // err is implicitly 'any' and cannot be annotated + + if (isFooError(err)) { + err.dontPanic(); // OK + err.doPanic(); // ERROR: Property 'doPanic' does not exist on type '{...}' + } + + else if (err instanceof Error) { + err.message; + err.massage; // ERROR: Property 'massage' does not exist on type 'Error' + } + + else { + throw err; + } + } +} diff --git a/tests/cases/conformance/types/any/narrowFromAnyWithInstanceof.ts b/tests/cases/conformance/types/any/narrowFromAnyWithInstanceof.ts new file mode 100644 index 00000000000..4fbfc46060a --- /dev/null +++ b/tests/cases/conformance/types/any/narrowFromAnyWithInstanceof.ts @@ -0,0 +1,23 @@ +declare var x: any; + +if (x instanceof Function) { // 'any' is not narrowed when target type is 'Function' + x(); + x(1, 2, 3); + x("hello!"); + x.prop; +} + +if (x instanceof Object) { // 'any' is not narrowed when target type is 'Object' + x.method(); + x(); +} + +if (x instanceof Error) { // 'any' is narrowed to types other than 'Function'/'Object' + x.message; + x.mesage; +} + +if (x instanceof Date) { + x.getDate(); + x.getHuors(); +} diff --git a/tests/cases/conformance/types/any/narrowFromAnyWithTypePredicate.ts b/tests/cases/conformance/types/any/narrowFromAnyWithTypePredicate.ts new file mode 100644 index 00000000000..473bd349b5f --- /dev/null +++ b/tests/cases/conformance/types/any/narrowFromAnyWithTypePredicate.ts @@ -0,0 +1,34 @@ +declare var x: any; +declare function isFunction(x): x is Function; +declare function isObject(x): x is Object; +declare function isAnything(x): x is {}; +declare function isError(x): x is Error; +declare function isDate(x): x is Date; + + +if (isFunction(x)) { // 'any' is not narrowed when target type is 'Function' + x(); + x(1, 2, 3); + x("hello!"); + x.prop; +} + +if (isObject(x)) { // 'any' is not narrowed when target type is 'Object' + x.method(); + x(); +} + +if (isAnything(x)) { // 'any' is narrowed to types other than 'Function'/'Object' (including {}) + x.method(); + x(); +} + +if (isError(x)) { + x.message; + x.mesage; +} + +if (isDate(x)) { + x.getDate(); + x.getHuors(); +} From 837688f0bec2dcbe15242609fe9f75b02224c2fd Mon Sep 17 00:00:00 2001 From: yortus Date: Sun, 14 Aug 2016 22:58:21 +0800 Subject: [PATCH 48/72] accept new baselines --- ...wExceptionVariableInCatchClause.errors.txt | 33 ++++++++++ .../narrowExceptionVariableInCatchClause.js | 44 ++++++++++++++ .../narrowFromAnyWithInstanceof.errors.txt | 33 ++++++++++ .../reference/narrowFromAnyWithInstanceof.js | 45 ++++++++++++++ .../narrowFromAnyWithTypePredicate.errors.txt | 50 ++++++++++++++++ .../narrowFromAnyWithTypePredicate.js | 60 +++++++++++++++++++ 6 files changed, 265 insertions(+) create mode 100644 tests/baselines/reference/narrowExceptionVariableInCatchClause.errors.txt create mode 100644 tests/baselines/reference/narrowExceptionVariableInCatchClause.js create mode 100644 tests/baselines/reference/narrowFromAnyWithInstanceof.errors.txt create mode 100644 tests/baselines/reference/narrowFromAnyWithInstanceof.js create mode 100644 tests/baselines/reference/narrowFromAnyWithTypePredicate.errors.txt create mode 100644 tests/baselines/reference/narrowFromAnyWithTypePredicate.js diff --git a/tests/baselines/reference/narrowExceptionVariableInCatchClause.errors.txt b/tests/baselines/reference/narrowExceptionVariableInCatchClause.errors.txt new file mode 100644 index 00000000000..e435b98050d --- /dev/null +++ b/tests/baselines/reference/narrowExceptionVariableInCatchClause.errors.txt @@ -0,0 +1,33 @@ +tests/cases/conformance/types/any/narrowExceptionVariableInCatchClause.ts(11,17): error TS2339: Property 'doPanic' does not exist on type '{ type: "foo"; dontPanic(): any; }'. +tests/cases/conformance/types/any/narrowExceptionVariableInCatchClause.ts(16,17): error TS2339: Property 'massage' does not exist on type 'Error'. + + +==== tests/cases/conformance/types/any/narrowExceptionVariableInCatchClause.ts (2 errors) ==== + declare function isFooError(x: any): x is { type: 'foo'; dontPanic(); }; + + function tryCatch() { + try { + // do stuff... + } + catch (err) { // err is implicitly 'any' and cannot be annotated + + if (isFooError(err)) { + err.dontPanic(); // OK + err.doPanic(); // ERROR: Property 'doPanic' does not exist on type '{...}' + ~~~~~~~ +!!! error TS2339: Property 'doPanic' does not exist on type '{ type: "foo"; dontPanic(): any; }'. + } + + else if (err instanceof Error) { + err.message; + err.massage; // ERROR: Property 'massage' does not exist on type 'Error' + ~~~~~~~ +!!! error TS2339: Property 'massage' does not exist on type 'Error'. + } + + else { + throw err; + } + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/narrowExceptionVariableInCatchClause.js b/tests/baselines/reference/narrowExceptionVariableInCatchClause.js new file mode 100644 index 00000000000..5808ed76826 --- /dev/null +++ b/tests/baselines/reference/narrowExceptionVariableInCatchClause.js @@ -0,0 +1,44 @@ +//// [narrowExceptionVariableInCatchClause.ts] +declare function isFooError(x: any): x is { type: 'foo'; dontPanic(); }; + +function tryCatch() { + try { + // do stuff... + } + catch (err) { // err is implicitly 'any' and cannot be annotated + + if (isFooError(err)) { + err.dontPanic(); // OK + err.doPanic(); // ERROR: Property 'doPanic' does not exist on type '{...}' + } + + else if (err instanceof Error) { + err.message; + err.massage; // ERROR: Property 'massage' does not exist on type 'Error' + } + + else { + throw err; + } + } +} + + +//// [narrowExceptionVariableInCatchClause.js] +function tryCatch() { + try { + } + catch (err) { + if (isFooError(err)) { + err.dontPanic(); // OK + err.doPanic(); // ERROR: Property 'doPanic' does not exist on type '{...}' + } + else if (err instanceof Error) { + err.message; + err.massage; // ERROR: Property 'massage' does not exist on type 'Error' + } + else { + throw err; + } + } +} diff --git a/tests/baselines/reference/narrowFromAnyWithInstanceof.errors.txt b/tests/baselines/reference/narrowFromAnyWithInstanceof.errors.txt new file mode 100644 index 00000000000..3e152b0faf4 --- /dev/null +++ b/tests/baselines/reference/narrowFromAnyWithInstanceof.errors.txt @@ -0,0 +1,33 @@ +tests/cases/conformance/types/any/narrowFromAnyWithInstanceof.ts(17,7): error TS2339: Property 'mesage' does not exist on type 'Error'. +tests/cases/conformance/types/any/narrowFromAnyWithInstanceof.ts(22,7): error TS2339: Property 'getHuors' does not exist on type 'Date'. + + +==== tests/cases/conformance/types/any/narrowFromAnyWithInstanceof.ts (2 errors) ==== + declare var x: any; + + if (x instanceof Function) { // 'any' is not narrowed when target type is 'Function' + x(); + x(1, 2, 3); + x("hello!"); + x.prop; + } + + if (x instanceof Object) { // 'any' is not narrowed when target type is 'Object' + x.method(); + x(); + } + + if (x instanceof Error) { // 'any' is narrowed to types other than 'Function'/'Object' + x.message; + x.mesage; + ~~~~~~ +!!! error TS2339: Property 'mesage' does not exist on type 'Error'. + } + + if (x instanceof Date) { + x.getDate(); + x.getHuors(); + ~~~~~~~~ +!!! error TS2339: Property 'getHuors' does not exist on type 'Date'. + } + \ No newline at end of file diff --git a/tests/baselines/reference/narrowFromAnyWithInstanceof.js b/tests/baselines/reference/narrowFromAnyWithInstanceof.js new file mode 100644 index 00000000000..4cf1ca174aa --- /dev/null +++ b/tests/baselines/reference/narrowFromAnyWithInstanceof.js @@ -0,0 +1,45 @@ +//// [narrowFromAnyWithInstanceof.ts] +declare var x: any; + +if (x instanceof Function) { // 'any' is not narrowed when target type is 'Function' + x(); + x(1, 2, 3); + x("hello!"); + x.prop; +} + +if (x instanceof Object) { // 'any' is not narrowed when target type is 'Object' + x.method(); + x(); +} + +if (x instanceof Error) { // 'any' is narrowed to types other than 'Function'/'Object' + x.message; + x.mesage; +} + +if (x instanceof Date) { + x.getDate(); + x.getHuors(); +} + + +//// [narrowFromAnyWithInstanceof.js] +if (x instanceof Function) { + x(); + x(1, 2, 3); + x("hello!"); + x.prop; +} +if (x instanceof Object) { + x.method(); + x(); +} +if (x instanceof Error) { + x.message; + x.mesage; +} +if (x instanceof Date) { + x.getDate(); + x.getHuors(); +} diff --git a/tests/baselines/reference/narrowFromAnyWithTypePredicate.errors.txt b/tests/baselines/reference/narrowFromAnyWithTypePredicate.errors.txt new file mode 100644 index 00000000000..94f41becdad --- /dev/null +++ b/tests/baselines/reference/narrowFromAnyWithTypePredicate.errors.txt @@ -0,0 +1,50 @@ +tests/cases/conformance/types/any/narrowFromAnyWithTypePredicate.ts(22,7): error TS2339: Property 'method' does not exist on type '{}'. +tests/cases/conformance/types/any/narrowFromAnyWithTypePredicate.ts(23,5): error TS2349: Cannot invoke an expression whose type lacks a call signature. +tests/cases/conformance/types/any/narrowFromAnyWithTypePredicate.ts(28,7): error TS2339: Property 'mesage' does not exist on type 'Error'. +tests/cases/conformance/types/any/narrowFromAnyWithTypePredicate.ts(33,7): error TS2339: Property 'getHuors' does not exist on type 'Date'. + + +==== tests/cases/conformance/types/any/narrowFromAnyWithTypePredicate.ts (4 errors) ==== + declare var x: any; + declare function isFunction(x): x is Function; + declare function isObject(x): x is Object; + declare function isAnything(x): x is {}; + declare function isError(x): x is Error; + declare function isDate(x): x is Date; + + + if (isFunction(x)) { // 'any' is not narrowed when target type is 'Function' + x(); + x(1, 2, 3); + x("hello!"); + x.prop; + } + + if (isObject(x)) { // 'any' is not narrowed when target type is 'Object' + x.method(); + x(); + } + + if (isAnything(x)) { // 'any' is narrowed to types other than 'Function'/'Object' (including {}) + x.method(); + ~~~~~~ +!!! error TS2339: Property 'method' does not exist on type '{}'. + x(); + ~~~ +!!! error TS2349: Cannot invoke an expression whose type lacks a call signature. + } + + if (isError(x)) { + x.message; + x.mesage; + ~~~~~~ +!!! error TS2339: Property 'mesage' does not exist on type 'Error'. + } + + if (isDate(x)) { + x.getDate(); + x.getHuors(); + ~~~~~~~~ +!!! error TS2339: Property 'getHuors' does not exist on type 'Date'. + } + \ No newline at end of file diff --git a/tests/baselines/reference/narrowFromAnyWithTypePredicate.js b/tests/baselines/reference/narrowFromAnyWithTypePredicate.js new file mode 100644 index 00000000000..958a3cfd70d --- /dev/null +++ b/tests/baselines/reference/narrowFromAnyWithTypePredicate.js @@ -0,0 +1,60 @@ +//// [narrowFromAnyWithTypePredicate.ts] +declare var x: any; +declare function isFunction(x): x is Function; +declare function isObject(x): x is Object; +declare function isAnything(x): x is {}; +declare function isError(x): x is Error; +declare function isDate(x): x is Date; + + +if (isFunction(x)) { // 'any' is not narrowed when target type is 'Function' + x(); + x(1, 2, 3); + x("hello!"); + x.prop; +} + +if (isObject(x)) { // 'any' is not narrowed when target type is 'Object' + x.method(); + x(); +} + +if (isAnything(x)) { // 'any' is narrowed to types other than 'Function'/'Object' (including {}) + x.method(); + x(); +} + +if (isError(x)) { + x.message; + x.mesage; +} + +if (isDate(x)) { + x.getDate(); + x.getHuors(); +} + + +//// [narrowFromAnyWithTypePredicate.js] +if (isFunction(x)) { + x(); + x(1, 2, 3); + x("hello!"); + x.prop; +} +if (isObject(x)) { + x.method(); + x(); +} +if (isAnything(x)) { + x.method(); + x(); +} +if (isError(x)) { + x.message; + x.mesage; +} +if (isDate(x)) { + x.getDate(); + x.getHuors(); +} From 54735edc72d18966e264132a5be00465c9568e5d Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Mon, 15 Aug 2016 07:40:25 -0700 Subject: [PATCH 49/72] Use lowercase names for type reference directives --- src/compiler/program.ts | 7 ++++--- tests/baselines/reference/typingsLookup3.js | 13 +++++++++++++ tests/baselines/reference/typingsLookup3.symbols | 12 ++++++++++++ .../baselines/reference/typingsLookup3.trace.json | 12 ++++++++++++ tests/baselines/reference/typingsLookup3.types | 12 ++++++++++++ tests/cases/conformance/typings/typingsLookup3.ts | 14 ++++++++++++++ 6 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 tests/baselines/reference/typingsLookup3.js create mode 100644 tests/baselines/reference/typingsLookup3.symbols create mode 100644 tests/baselines/reference/typingsLookup3.trace.json create mode 100644 tests/baselines/reference/typingsLookup3.types create mode 100644 tests/cases/conformance/typings/typingsLookup3.ts diff --git a/src/compiler/program.ts b/src/compiler/program.ts index eb50548750d..5befec5ebc0 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -2012,15 +2012,16 @@ namespace ts { } function processTypeReferenceDirectives(file: SourceFile) { - const typeDirectives = map(file.typeReferenceDirectives, l => l.fileName); + const typeDirectives = map(file.typeReferenceDirectives, ref => ref.fileName.toLocaleLowerCase()); const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeDirectives, file.fileName); for (let i = 0; i < typeDirectives.length; i++) { const ref = file.typeReferenceDirectives[i]; const resolvedTypeReferenceDirective = resolutions[i]; // store resolved type directive on the file - setResolvedTypeReferenceDirective(file, ref.fileName, resolvedTypeReferenceDirective); - processTypeReferenceDirective(ref.fileName, resolvedTypeReferenceDirective, file, ref.pos, ref.end); + const fileName = ref.fileName.toLocaleLowerCase(); + setResolvedTypeReferenceDirective(file, fileName, resolvedTypeReferenceDirective); + processTypeReferenceDirective(fileName, resolvedTypeReferenceDirective, file, ref.pos, ref.end); } } diff --git a/tests/baselines/reference/typingsLookup3.js b/tests/baselines/reference/typingsLookup3.js new file mode 100644 index 00000000000..b3be036b4ae --- /dev/null +++ b/tests/baselines/reference/typingsLookup3.js @@ -0,0 +1,13 @@ +//// [tests/cases/conformance/typings/typingsLookup3.ts] //// + +//// [index.d.ts] +declare var $: { x: any }; + +//// [a.ts] +/// +$.x; + + +//// [a.js] +/// +$.x; diff --git a/tests/baselines/reference/typingsLookup3.symbols b/tests/baselines/reference/typingsLookup3.symbols new file mode 100644 index 00000000000..e641afb183b --- /dev/null +++ b/tests/baselines/reference/typingsLookup3.symbols @@ -0,0 +1,12 @@ +=== /a.ts === +/// +$.x; +>$.x : Symbol(x, Decl(index.d.ts, 0, 16)) +>$ : Symbol($, Decl(index.d.ts, 0, 11)) +>x : Symbol(x, Decl(index.d.ts, 0, 16)) + +=== /node_modules/@types/jquery/index.d.ts === +declare var $: { x: any }; +>$ : Symbol($, Decl(index.d.ts, 0, 11)) +>x : Symbol(x, Decl(index.d.ts, 0, 16)) + diff --git a/tests/baselines/reference/typingsLookup3.trace.json b/tests/baselines/reference/typingsLookup3.trace.json new file mode 100644 index 00000000000..83b0e91d6c7 --- /dev/null +++ b/tests/baselines/reference/typingsLookup3.trace.json @@ -0,0 +1,12 @@ +[ + "======== Resolving type reference directive 'jquery', containing file '/a.ts', root directory '/node_modules/@types'. ========", + "Resolving with primary search path '/node_modules/@types'", + "File '/node_modules/@types/jquery/package.json' does not exist.", + "File '/node_modules/@types/jquery/index.d.ts' exist - use it as a name resolution result.", + "======== Type reference directive 'jquery' was successfully resolved to '/node_modules/@types/jquery/index.d.ts', primary: true. ========", + "======== Resolving type reference directive 'jquery', containing file '/__inferred type names__.ts', root directory '/node_modules/@types'. ========", + "Resolving with primary search path '/node_modules/@types'", + "File '/node_modules/@types/jquery/package.json' does not exist.", + "File '/node_modules/@types/jquery/index.d.ts' exist - use it as a name resolution result.", + "======== Type reference directive 'jquery' was successfully resolved to '/node_modules/@types/jquery/index.d.ts', primary: true. ========" +] \ No newline at end of file diff --git a/tests/baselines/reference/typingsLookup3.types b/tests/baselines/reference/typingsLookup3.types new file mode 100644 index 00000000000..f57a974077e --- /dev/null +++ b/tests/baselines/reference/typingsLookup3.types @@ -0,0 +1,12 @@ +=== /a.ts === +/// +$.x; +>$.x : any +>$ : { x: any; } +>x : any + +=== /node_modules/@types/jquery/index.d.ts === +declare var $: { x: any }; +>$ : { x: any; } +>x : any + diff --git a/tests/cases/conformance/typings/typingsLookup3.ts b/tests/cases/conformance/typings/typingsLookup3.ts new file mode 100644 index 00000000000..a0bb15476b9 --- /dev/null +++ b/tests/cases/conformance/typings/typingsLookup3.ts @@ -0,0 +1,14 @@ +// @traceResolution: true +// @noImplicitReferences: true +// @currentDirectory: / +// This tests that `types` references are automatically lowercased. + +// @filename: /tsconfig.json +{ "files": "a.ts" } + +// @filename: /node_modules/@types/jquery/index.d.ts +declare var $: { x: any }; + +// @filename: /a.ts +/// +$.x; From 592805f486e9ccb755f0e286413af8343e4d0a3a Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Mon, 15 Aug 2016 07:56:45 -0700 Subject: [PATCH 50/72] Use proper response codes in web tests --- tests/webTestServer.ts | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/tests/webTestServer.ts b/tests/webTestServer.ts index ac5f046c1dd..4d2cb7d4672 100644 --- a/tests/webTestServer.ts +++ b/tests/webTestServer.ts @@ -117,19 +117,21 @@ function handleResolutionRequest(filePath: string, res: http.ServerResponse) { let resolvedPath = path.resolve(filePath, ""); resolvedPath = resolvedPath.substring(resolvedPath.indexOf("tests")); resolvedPath = switchToForwardSlashes(resolvedPath); - send("success", res, resolvedPath); - return; + send(ResponseCode.Success, res, resolvedPath); } -function send(result: "fail", res: http.ServerResponse, contents: string, contentType?: string): void; -function send(result: "success", res: http.ServerResponse, contents: string, contentType?: string): void; -function send(result: "unknown", res: http.ServerResponse, contents: string, contentType?: string): void; -function send(result: string, res: http.ServerResponse, contents: string, contentType?: string): void -function send(result: string, res: http.ServerResponse, contents: string, contentType = "binary"): void { - const responseCode = result === "success" ? 200 : result === "fail" ? 500 : result === "unknown" ? 404 : parseInt(result); +const enum ResponseCode { + Success = 200, + BadRequest = 400, + NotFound = 404, + MethodNotAllowed = 405, + PayloadTooLarge = 413, + Fail = 500 +} + +function send(responseCode: number, res: http.ServerResponse, contents: string, contentType = "binary"): void { res.writeHead(responseCode, { "Content-Type": contentType }); res.end(contents); - return; } // Reads the data from a post request and passes it to the given callback @@ -142,7 +144,7 @@ function processPost(req: http.ServerRequest, res: http.ServerResponse, callback queryData += data; if (queryData.length > 1e8) { queryData = ""; - send("413", res, undefined); + send(ResponseCode.PayloadTooLarge, res, undefined); console.log("ERROR: destroying connection"); req.connection.destroy(); } @@ -155,7 +157,7 @@ function processPost(req: http.ServerRequest, res: http.ServerResponse, callback } else { - send("405", res, undefined); + send(ResponseCode.MethodNotAllowed, res, undefined); } } @@ -201,16 +203,16 @@ function handleRequestOperation(req: http.ServerRequest, res: http.ServerRespons switch (operation) { case RequestType.GetDir: const filesInFolder = dir(reqPath, "", { recursive: true }); - send("success", res, filesInFolder.join(",")); + send(ResponseCode.Success, res, filesInFolder.join(",")); break; case RequestType.GetFile: fs.readFile(reqPath, (err, file) => { const contentType = contentTypeForExtension(path.extname(reqPath)); if (err) { - send("fail", res, err.message, contentType); + send(ResponseCode.NotFound, res, err.message, contentType); } else { - send("success", res, file, contentType); + send(ResponseCode.Success, res, file, contentType); } }); break; @@ -222,33 +224,33 @@ function handleRequestOperation(req: http.ServerRequest, res: http.ServerRespons processPost(req, res, (data) => { writeFile(reqPath, data, { recursive: true }); }); - send("success", res, undefined); + send(ResponseCode.Success, res, undefined); break; case RequestType.WriteDir: fs.mkdirSync(reqPath); - send("success", res, undefined); + send(ResponseCode.Success, res, undefined); break; case RequestType.DeleteFile: if (fs.existsSync(reqPath)) { fs.unlinkSync(reqPath); } - send("success", res, undefined); + send(ResponseCode.Success, res, undefined); break; case RequestType.DeleteDir: if (fs.existsSync(reqPath)) { fs.rmdirSync(reqPath); } - send("success", res, undefined); + send(ResponseCode.Success, res, undefined); break; case RequestType.AppendFile: processPost(req, res, (data) => { fs.appendFileSync(reqPath, data); }); - send("success", res, undefined); + send(ResponseCode.Success, res, undefined); break; case RequestType.Unknown: default: - send("unknown", res, undefined); + send(ResponseCode.BadRequest, res, undefined); break; } From ccf5bab8ad0a99c099ea21568ae602e6db165fbe Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Mon, 15 Aug 2016 08:51:15 -0700 Subject: [PATCH 51/72] Treat ambient shorthand declarations as explicit uses of the `any` type --- src/compiler/checker.ts | 5 ----- .../reference/ambientShorthand_isImplicitAny.errors.txt | 8 -------- .../baselines/reference/ambientShorthand_isImplicitAny.js | 5 ----- .../conformance/ambient/ambientShorthand_isImplicitAny.ts | 2 -- 4 files changed, 20 deletions(-) delete mode 100644 tests/baselines/reference/ambientShorthand_isImplicitAny.errors.txt delete mode 100644 tests/baselines/reference/ambientShorthand_isImplicitAny.js delete mode 100644 tests/cases/conformance/ambient/ambientShorthand_isImplicitAny.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 85e33c5e4a1..bf721a96bc3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17034,11 +17034,6 @@ namespace ts { } } - if (compilerOptions.noImplicitAny && !node.body) { - // Ambient shorthand module is an implicit any - reportImplicitAnyError(node, anyType); - } - if (node.body) { checkSourceElement(node.body); if (!isGlobalScopeAugmentation(node)) { diff --git a/tests/baselines/reference/ambientShorthand_isImplicitAny.errors.txt b/tests/baselines/reference/ambientShorthand_isImplicitAny.errors.txt deleted file mode 100644 index 875a656202d..00000000000 --- a/tests/baselines/reference/ambientShorthand_isImplicitAny.errors.txt +++ /dev/null @@ -1,8 +0,0 @@ -tests/cases/conformance/ambient/ambientShorthand_isImplicitAny.ts(1,16): error TS7005: Variable '"jquery"' implicitly has an 'any' type. - - -==== tests/cases/conformance/ambient/ambientShorthand_isImplicitAny.ts (1 errors) ==== - declare module "jquery"; - ~~~~~~~~ -!!! error TS7005: Variable '"jquery"' implicitly has an 'any' type. - \ No newline at end of file diff --git a/tests/baselines/reference/ambientShorthand_isImplicitAny.js b/tests/baselines/reference/ambientShorthand_isImplicitAny.js deleted file mode 100644 index ab05d2a9979..00000000000 --- a/tests/baselines/reference/ambientShorthand_isImplicitAny.js +++ /dev/null @@ -1,5 +0,0 @@ -//// [ambientShorthand_isImplicitAny.ts] -declare module "jquery"; - - -//// [ambientShorthand_isImplicitAny.js] diff --git a/tests/cases/conformance/ambient/ambientShorthand_isImplicitAny.ts b/tests/cases/conformance/ambient/ambientShorthand_isImplicitAny.ts deleted file mode 100644 index bf7de709ef2..00000000000 --- a/tests/cases/conformance/ambient/ambientShorthand_isImplicitAny.ts +++ /dev/null @@ -1,2 +0,0 @@ -// @noImplicitAny: true -declare module "jquery"; From a1dad91120378406332af99c81a5e9946e08e13e Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 15 Aug 2016 10:45:46 -0700 Subject: [PATCH 52/72] Parallel linting (#10313) * A perilous thing, a parallel lint * Use work queue rather than scheduling work * Dont read files for lint on main thread * Fix style --- Gulpfile.ts | 98 ++++++++++++++++----------- Jakefile.js | 142 ++++++++++++++++----------------------- scripts/parallel-lint.js | 45 +++++++++++++ 3 files changed, 162 insertions(+), 123 deletions(-) create mode 100644 scripts/parallel-lint.js diff --git a/Gulpfile.ts b/Gulpfile.ts index d677b042250..2d17325c4d8 100644 --- a/Gulpfile.ts +++ b/Gulpfile.ts @@ -34,7 +34,6 @@ import through2 = require("through2"); import merge2 = require("merge2"); import intoStream = require("into-stream"); import * as os from "os"; -import Linter = require("tslint"); import fold = require("travis-fold"); const gulp = helpMaker(originalGulp); const mochaParallel = require("./scripts/mocha-parallel.js"); @@ -929,26 +928,6 @@ gulp.task("build-rules", "Compiles tslint rules to js", () => { .pipe(gulp.dest(dest)); }); -function getLinterOptions() { - return { - configuration: require("./tslint.json"), - formatter: "prose", - formattersDirectory: undefined, - rulesDirectory: "built/local/tslint" - }; -} - -function lintFileContents(options, path, contents) { - const ll = new Linter(path, contents, options); - console.log("Linting '" + path + "'."); - return ll.lint(); -} - -function lintFile(options, path) { - const contents = fs.readFileSync(path, "utf8"); - return lintFileContents(options, path, contents); -} - const lintTargets = [ "Gulpfile.ts", "src/compiler/**/*.ts", @@ -960,29 +939,72 @@ const lintTargets = [ "tests/*.ts", "tests/webhost/*.ts" // Note: does *not* descend recursively ]; +function sendNextFile(files: {path: string}[], child: cp.ChildProcess, callback: (failures: number) => void, failures: number) { + const file = files.pop(); + if (file) { + console.log(`Linting '${file.path}'.`); + child.send({ kind: "file", name: file.path }); + } + else { + child.send({ kind: "close" }); + callback(failures); + } +} + +function spawnLintWorker(files: {path: string}[], callback: (failures: number) => void) { + const child = cp.fork("./scripts/parallel-lint"); + let failures = 0; + child.on("message", function(data) { + switch (data.kind) { + case "result": + if (data.failures > 0) { + failures += data.failures; + console.log(data.output); + } + sendNextFile(files, child, callback, failures); + break; + case "error": + console.error(data.error); + failures++; + sendNextFile(files, child, callback, failures); + break; + } + }); + sendNextFile(files, child, callback, failures); +} gulp.task("lint", "Runs tslint on the compiler sources. Optional arguments are: --f[iles]=regex", ["build-rules"], () => { const fileMatcher = RegExp(cmdLineOptions["files"]); - const lintOptions = getLinterOptions(); - let failed = 0; if (fold.isTravis()) console.log(fold.start("lint")); - return gulp.src(lintTargets) - .pipe(insert.transform((contents, file) => { - if (!fileMatcher.test(file.path)) return contents; - const result = lintFile(lintOptions, file.path); - if (result.failureCount > 0) { - console.log(result.output); - failed += result.failureCount; + + const files: {stat: fs.Stats, path: string}[] = []; + return gulp.src(lintTargets, { read: false }) + .pipe(through2.obj((chunk, enc, cb) => { + files.push(chunk); + cb(); + }, (cb) => { + files.filter(file => fileMatcher.test(file.path)).sort((filea, fileb) => filea.stat.size - fileb.stat.size); + const workerCount = (process.env.workerCount && +process.env.workerCount) || os.cpus().length; + for (let i = 0; i < workerCount; i++) { + spawnLintWorker(files, finished); } - return contents; // TODO (weswig): Automatically apply fixes? :3 - })) - .on("end", () => { - if (fold.isTravis()) console.log(fold.end("lint")); - if (failed > 0) { - console.error("Linter errors."); - process.exit(1); + + let completed = 0; + let failures = 0; + function finished(fails) { + completed++; + failures += fails; + if (completed === workerCount) { + if (fold.isTravis()) console.log(fold.end("lint")); + if (failures > 0) { + throw new Error(`Linter errors: ${failures}`); + } + else { + cb(); + } + } } - }); + })); }); diff --git a/Jakefile.js b/Jakefile.js index 01aac82a47a..e4aaf330dc7 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -4,7 +4,6 @@ var fs = require("fs"); var os = require("os"); var path = require("path"); var child_process = require("child_process"); -var Linter = require("tslint"); var fold = require("travis-fold"); var runTestsInParallel = require("./scripts/mocha-parallel").runTestsInParallel; @@ -1054,36 +1053,6 @@ task("build-rules-end", [] , function() { if (fold.isTravis()) console.log(fold.end("build-rules")); }); -function getLinterOptions() { - return { - configuration: require("./tslint.json"), - formatter: "prose", - formattersDirectory: undefined, - rulesDirectory: "built/local/tslint" - }; -} - -function lintFileContents(options, path, contents) { - var ll = new Linter(path, contents, options); - console.log("Linting '" + path + "'."); - return ll.lint(); -} - -function lintFile(options, path) { - var contents = fs.readFileSync(path, "utf8"); - return lintFileContents(options, path, contents); -} - -function lintFileAsync(options, path, cb) { - fs.readFile(path, "utf8", function(err, contents) { - if (err) { - return cb(err); - } - var result = lintFileContents(options, path, contents); - cb(undefined, result); - }); -} - var lintTargets = compilerSources .concat(harnessSources) // Other harness sources @@ -1094,75 +1063,78 @@ var lintTargets = compilerSources .concat(["Gulpfile.ts"]) .concat([nodeServerInFile, perftscPath, "tests/perfsys.ts", webhostPath]); +function sendNextFile(files, child, callback, failures) { + var file = files.pop(); + if (file) { + console.log("Linting '" + file + "'."); + child.send({kind: "file", name: file}); + } + else { + child.send({kind: "close"}); + callback(failures); + } +} + +function spawnLintWorker(files, callback) { + var child = child_process.fork("./scripts/parallel-lint"); + var failures = 0; + child.on("message", function(data) { + switch (data.kind) { + case "result": + if (data.failures > 0) { + failures += data.failures; + console.log(data.output); + } + sendNextFile(files, child, callback, failures); + break; + case "error": + console.error(data.error); + failures++; + sendNextFile(files, child, callback, failures); + break; + } + }); + sendNextFile(files, child, callback, failures); +} desc("Runs tslint on the compiler sources. Optional arguments are: f[iles]=regex"); task("lint", ["build-rules"], function() { if (fold.isTravis()) console.log(fold.start("lint")); var startTime = mark(); - var lintOptions = getLinterOptions(); var failed = 0; var fileMatcher = RegExp(process.env.f || process.env.file || process.env.files || ""); var done = {}; for (var i in lintTargets) { var target = lintTargets[i]; if (!done[target] && fileMatcher.test(target)) { - var result = lintFile(lintOptions, target); - if (result.failureCount > 0) { - console.log(result.output); - failed += result.failureCount; - } - done[target] = true; + done[target] = fs.statSync(target).size; } } - measure(startTime); - if (fold.isTravis()) console.log(fold.end("lint")); - if (failed > 0) { - fail('Linter errors.', failed); - } -}); -/** - * This is required because file watches on Windows get fires _twice_ - * when a file changes on some node/windows version configuations - * (node v4 and win 10, for example). By not running a lint for a file - * which already has a pending lint, we avoid duplicating our work. - * (And avoid printing duplicate results!) - */ -var lintSemaphores = {}; + var workerCount = (process.env.workerCount && +process.env.workerCount) || os.cpus().length; -function lintWatchFile(filename) { - fs.watch(filename, {persistent: true}, function(event) { - if (event !== "change") { - return; - } - - if (!lintSemaphores[filename]) { - lintSemaphores[filename] = true; - lintFileAsync(getLinterOptions(), filename, function(err, result) { - delete lintSemaphores[filename]; - if (err) { - console.log(err); - return; - } - if (result.failureCount > 0) { - console.log("***Lint failure***"); - for (var i = 0; i < result.failures.length; i++) { - var failure = result.failures[i]; - var start = failure.startPosition.lineAndCharacter; - var end = failure.endPosition.lineAndCharacter; - console.log("warning " + filename + " (" + (start.line + 1) + "," + (start.character + 1) + "," + (end.line + 1) + "," + (end.character + 1) + "): " + failure.failure); - } - console.log("*** Total " + result.failureCount + " failures."); - } - }); - } + var names = Object.keys(done).sort(function(namea, nameb) { + return done[namea] - done[nameb]; }); -} -desc("Watches files for changes to rerun a lint pass"); -task("lint-server", ["build-rules"], function() { - console.log("Watching ./src for changes to linted files"); - for (var i = 0; i < lintTargets.length; i++) { - lintWatchFile(lintTargets[i]); + for (var i = 0; i < workerCount; i++) { + spawnLintWorker(names, finished); } -}); + + var completed = 0; + var failures = 0; + function finished(fails) { + completed++; + failures += fails; + if (completed === workerCount) { + measure(startTime); + if (fold.isTravis()) console.log(fold.end("lint")); + if (failures > 0) { + fail('Linter errors.', failed); + } + else { + complete(); + } + } + } +}, {async: true}); diff --git a/scripts/parallel-lint.js b/scripts/parallel-lint.js new file mode 100644 index 00000000000..a9aec06c2df --- /dev/null +++ b/scripts/parallel-lint.js @@ -0,0 +1,45 @@ +var Linter = require("tslint"); +var fs = require("fs"); + +function getLinterOptions() { + return { + configuration: require("../tslint.json"), + formatter: "prose", + formattersDirectory: undefined, + rulesDirectory: "built/local/tslint" + }; +} + +function lintFileContents(options, path, contents) { + var ll = new Linter(path, contents, options); + return ll.lint(); +} + +function lintFileAsync(options, path, cb) { + fs.readFile(path, "utf8", function (err, contents) { + if (err) { + return cb(err); + } + var result = lintFileContents(options, path, contents); + cb(undefined, result); + }); +} + +process.on("message", function (data) { + switch (data.kind) { + case "file": + var target = data.name; + var lintOptions = getLinterOptions(); + lintFileAsync(lintOptions, target, function (err, result) { + if (err) { + process.send({ kind: "error", error: err.toString() }); + return; + } + process.send({ kind: "result", failures: result.failureCount, output: result.output }); + }); + break; + case "close": + process.exit(0); + break; + } +}); \ No newline at end of file From 2d1b68fdac39cf65fff40125d7d6cc10ebfb4ecb Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 15 Aug 2016 11:00:57 -0700 Subject: [PATCH 53/72] Fix the style fix (#10344) --- Gulpfile.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gulpfile.ts b/Gulpfile.ts index 2d17325c4d8..2718669b1f7 100644 --- a/Gulpfile.ts +++ b/Gulpfile.ts @@ -977,13 +977,13 @@ gulp.task("lint", "Runs tslint on the compiler sources. Optional arguments are: const fileMatcher = RegExp(cmdLineOptions["files"]); if (fold.isTravis()) console.log(fold.start("lint")); - const files: {stat: fs.Stats, path: string}[] = []; + let files: {stat: fs.Stats, path: string}[] = []; return gulp.src(lintTargets, { read: false }) .pipe(through2.obj((chunk, enc, cb) => { files.push(chunk); cb(); }, (cb) => { - files.filter(file => fileMatcher.test(file.path)).sort((filea, fileb) => filea.stat.size - fileb.stat.size); + files = files.filter(file => fileMatcher.test(file.path)).sort((filea, fileb) => filea.stat.size - fileb.stat.size); const workerCount = (process.env.workerCount && +process.env.workerCount) || os.cpus().length; for (let i = 0; i < workerCount; i++) { spawnLintWorker(files, finished); From 4e04b75d4bc4652408b99ab233de3aaa803a9467 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Mon, 15 Aug 2016 11:07:49 -0700 Subject: [PATCH 54/72] Aligned mark names with values used by ts-perf. --- src/compiler/binder.ts | 6 +++--- src/compiler/checker.ts | 6 +++--- src/compiler/parser.ts | 6 +++--- src/compiler/program.ts | 24 ++++++++++++------------ src/compiler/sourcemap.ts | 6 +++--- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index ff5a8b77cfd..b5ab2306989 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -89,10 +89,10 @@ namespace ts { const binder = createBinder(); export function bindSourceFile(file: SourceFile, options: CompilerOptions) { - performance.mark("bindStart"); + performance.mark("beforeBind"); binder(file, options); - performance.mark("bindEnd"); - performance.measure("Bind", "bindStart", "bindEnd"); + performance.mark("afterBind"); + performance.measure("Bind", "beforeBind", "afterBind"); } function createBinder(): (file: SourceFile, options: CompilerOptions) => void { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f1fff6737a3..b2b6b9442ac 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17535,10 +17535,10 @@ namespace ts { } function checkSourceFile(node: SourceFile) { - performance.mark("checkStart"); + performance.mark("beforeCheck"); checkSourceFileWorker(node); - performance.mark("checkEnd"); - performance.measure("Check", "checkStart", "checkEnd"); + performance.mark("afterCheck"); + performance.measure("Check", "beforeCheck", "afterCheck"); } // Fully type check a source file and collect the relevant diagnostics. diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index cbf767a5410..aca7d745730 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -421,10 +421,10 @@ namespace ts { } export function createSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, setParentNodes = false, scriptKind?: ScriptKind): SourceFile { - performance.mark("parseStart"); + performance.mark("beforeParse"); const result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes, scriptKind); - performance.mark("parseEnd"); - performance.measure("Parse", "parseStart", "parseEnd"); + performance.mark("afterParse"); + performance.measure("Parse", "beforeParse", "afterParse"); return result; } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index a632eed7fd2..767fcb488bb 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -857,10 +857,10 @@ namespace ts { function getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile { let text: string; try { - performance.mark("ioReadStart"); + performance.mark("beforeIORead"); text = sys.readFile(fileName, options.charset); - performance.mark("ioReadEnd"); - performance.measure("I/O Read", "ioReadStart", "ioReadEnd"); + performance.mark("afterIORead"); + performance.measure("I/O Read", "beforeIORead", "afterIORead"); } catch (e) { if (onError) { @@ -927,7 +927,7 @@ namespace ts { function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) { try { - performance.mark("ioWriteStart"); + performance.mark("beforeIOWrite"); ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); if (isWatchSet(options) && sys.createHash && sys.getModifiedTime) { @@ -937,8 +937,8 @@ namespace ts { sys.writeFile(fileName, data, writeByteOrderMark); } - performance.mark("ioWriteEnd"); - performance.measure("I/O Write", "ioWriteStart", "ioWriteEnd"); + performance.mark("afterIOWrite"); + performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); } catch (e) { if (onError) { @@ -1120,7 +1120,7 @@ namespace ts { // Track source files that are source files found by searching under node_modules, as these shouldn't be compiled. const sourceFilesFoundSearchingNodeModules = createMap(); - performance.mark("programStart"); + performance.mark("beforeProgram"); host = host || createCompilerHost(options); @@ -1218,8 +1218,8 @@ namespace ts { }; verifyCompilerOptions(); - performance.mark("programEnd"); - performance.measure("Program", "programStart", "programEnd"); + performance.mark("afterProgram"); + performance.measure("Program", "beforeProgram", "afterProgram"); return program; @@ -1462,15 +1462,15 @@ namespace ts { // checked is to not pass the file to getEmitResolver. const emitResolver = getDiagnosticsProducingTypeChecker().getEmitResolver((options.outFile || options.out) ? undefined : sourceFile); - performance.mark("emitStart"); + performance.mark("beforeEmit"); const emitResult = emitFiles( emitResolver, getEmitHost(writeFileCallback), sourceFile); - performance.mark("emitEnd"); - performance.measure("Emit", "emitStart", "emitEnd"); + performance.mark("afterEmit"); + performance.measure("Emit", "beforeEmit", "afterEmit"); return emitResult; } diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index 52e8975db76..4046867faf1 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -242,7 +242,7 @@ namespace ts { } if (extendedDiagnostics) { - performance.mark("sourcemapStart"); + performance.mark("beforeSourcemap"); } const sourceLinePos = getLineAndCharacterOfPosition(currentSourceFile, pos); @@ -286,8 +286,8 @@ namespace ts { updateLastEncodedAndRecordedSpans(); if (extendedDiagnostics) { - performance.mark("sourcemapEnd"); - performance.measure("Source Map", "sourcemapStart", "sourcemapEnd"); + performance.mark("afterSourcemap"); + performance.measure("Source Map", "beforeSourcemap", "afterSourcemap"); } } From 2953c7f0b2f187aa68664d1e8f7fb4118f777992 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Mon, 15 Aug 2016 11:16:36 -0700 Subject: [PATCH 55/72] Use an enum in checkClassForDuplicateDeclarations to aid readability --- src/compiler/checker.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ab76950ec4a..6f5d2dce121 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13794,15 +13794,19 @@ namespace ts { } function checkClassForDuplicateDeclarations(node: ClassLikeDeclaration) { - const getter = 1, setter = 2, property = getter | setter; + const enum Meaning { + Getter = 1, + Setter = 2, + Property = Getter | Setter + } - const instanceNames = createMap(); - const staticNames = createMap(); + const instanceNames = createMap(); + const staticNames = createMap(); for (const member of node.members) { if (member.kind === SyntaxKind.Constructor) { for (const param of (member as ConstructorDeclaration).parameters) { if (isParameterPropertyDeclaration(param)) { - addName(instanceNames, param.name, (param.name as Identifier).text, property); + addName(instanceNames, param.name, (param.name as Identifier).text, Meaning.Property); } } } @@ -13814,22 +13818,22 @@ namespace ts { if (memberName) { switch (member.kind) { case SyntaxKind.GetAccessor: - addName(names, member.name, memberName, getter); + addName(names, member.name, memberName, Meaning.Getter); break; case SyntaxKind.SetAccessor: - addName(names, member.name, memberName, setter); + addName(names, member.name, memberName, Meaning.Setter); break; case SyntaxKind.PropertyDeclaration: - addName(names, member.name, memberName, property); + addName(names, member.name, memberName, Meaning.Property); break; } } } } - function addName(names: Map, location: Node, name: string, meaning: number) { + function addName(names: Map, location: Node, name: string, meaning: Meaning) { const prev = names[name]; if (prev) { if (prev & meaning) { From 8f1960fd3474fdbfd70d3dd1d0d86aa6d3e17a2e Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Mon, 15 Aug 2016 11:43:40 -0700 Subject: [PATCH 56/72] Rename to Accessor --- src/compiler/checker.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6f5d2dce121..6e51e418ee4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13794,19 +13794,19 @@ namespace ts { } function checkClassForDuplicateDeclarations(node: ClassLikeDeclaration) { - const enum Meaning { + const enum Accessor { Getter = 1, Setter = 2, Property = Getter | Setter } - const instanceNames = createMap(); - const staticNames = createMap(); + const instanceNames = createMap(); + const staticNames = createMap(); for (const member of node.members) { if (member.kind === SyntaxKind.Constructor) { for (const param of (member as ConstructorDeclaration).parameters) { if (isParameterPropertyDeclaration(param)) { - addName(instanceNames, param.name, (param.name as Identifier).text, Meaning.Property); + addName(instanceNames, param.name, (param.name as Identifier).text, Accessor.Property); } } } @@ -13818,22 +13818,22 @@ namespace ts { if (memberName) { switch (member.kind) { case SyntaxKind.GetAccessor: - addName(names, member.name, memberName, Meaning.Getter); + addName(names, member.name, memberName, Accessor.Getter); break; case SyntaxKind.SetAccessor: - addName(names, member.name, memberName, Meaning.Setter); + addName(names, member.name, memberName, Accessor.Setter); break; case SyntaxKind.PropertyDeclaration: - addName(names, member.name, memberName, Meaning.Property); + addName(names, member.name, memberName, Accessor.Property); break; } } } } - function addName(names: Map, location: Node, name: string, meaning: Meaning) { + function addName(names: Map, location: Node, name: string, meaning: Accessor) { const prev = names[name]; if (prev) { if (prev & meaning) { From 7f0a02ff024a33fcf7b056b4273506e364dbde15 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Mon, 15 Aug 2016 12:03:39 -0700 Subject: [PATCH 57/72] Migrated more MapLikes to Maps --- src/compiler/checker.ts | 24 +- src/compiler/commandLineParser.ts | 33 +- src/compiler/core.ts | 287 +++++++++++++----- src/compiler/declarationEmitter.ts | 10 +- src/compiler/emitter.ts | 20 +- src/compiler/program.ts | 27 +- src/compiler/tsc.ts | 16 +- src/compiler/utilities.ts | 21 +- src/harness/fourslash.ts | 8 +- src/harness/harness.ts | 2 +- src/harness/harnessLanguageService.ts | 4 +- .../unittests/cachingInServerLSHost.ts | 2 +- .../unittests/reuseProgramStructure.ts | 16 +- .../unittests/tsserverProjectSystem.ts | 26 +- src/server/client.ts | 2 +- src/server/editorServices.ts | 20 +- src/services/jsTyping.ts | 8 +- src/services/navigateTo.ts | 2 +- src/services/navigationBar.ts | 2 +- src/services/services.ts | 28 +- src/services/shims.ts | 8 +- src/services/signatureHelp.ts | 2 +- tslint.json | 3 +- 23 files changed, 319 insertions(+), 252 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b2b6b9442ac..6a7cc108625 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -403,8 +403,8 @@ namespace ts { result.parent = symbol.parent; if (symbol.valueDeclaration) result.valueDeclaration = symbol.valueDeclaration; if (symbol.constEnumOnlyModule) result.constEnumOnlyModule = true; - if (symbol.members) result.members = cloneSymbolTable(symbol.members); - if (symbol.exports) result.exports = cloneSymbolTable(symbol.exports); + if (symbol.members) result.members = cloneMap(symbol.members); + if (symbol.exports) result.exports = cloneMap(symbol.exports); recordMergedSymbol(result, symbol); return result; } @@ -447,14 +447,6 @@ namespace ts { } } - function cloneSymbolTable(symbolTable: SymbolTable): SymbolTable { - const result = createMap(); - for (const id in symbolTable) { - result[id] = symbolTable[id]; - } - return result; - } - function mergeSymbolTable(target: SymbolTable, source: SymbolTable) { for (const id in source) { let targetSymbol = target[id]; @@ -1450,7 +1442,7 @@ namespace ts { return; } visitedSymbols.push(symbol); - const symbols = cloneSymbolTable(symbol.exports); + const symbols = cloneMap(symbol.exports); // All export * declarations are collected in an __export symbol by the binder const exportStars = symbol.exports["__export"]; if (exportStars) { @@ -1655,12 +1647,12 @@ namespace ts { } // If symbol is directly available by its name in the symbol table - if (isAccessible(lookUp(symbols, symbol.name))) { + if (isAccessible(symbols[symbol.name])) { return [symbol]; } // Check if symbol is any of the alias - return forEachValue(symbols, symbolFromSymbolTable => { + return forEachProperty(symbols, symbolFromSymbolTable => { if (symbolFromSymbolTable.flags & SymbolFlags.Alias && symbolFromSymbolTable.name !== "export=" && !getDeclarationOfKind(symbolFromSymbolTable, SyntaxKind.ExportSpecifier)) { @@ -6622,7 +6614,7 @@ namespace ts { const maybeCache = maybeStack[depth]; // If result is definitely true, copy assumptions to global cache, else copy to next level up const destinationCache = (result === Ternary.True || depth === 0) ? relation : maybeStack[depth - 1]; - copyMap(maybeCache, destinationCache); + copyProperties(maybeCache, destinationCache); } else { // A false result goes straight into global cache (when something is false under assumptions it @@ -7934,7 +7926,7 @@ namespace ts { // check. This gives us a quicker out in the common case where an object type is not a function. const resolved = resolveStructuredTypeMembers(type); return !!(resolved.callSignatures.length || resolved.constructSignatures.length || - hasProperty(resolved.members, "bind") && isTypeSubtypeOf(type, globalFunctionType)); + resolved.members["bind"] && isTypeSubtypeOf(type, globalFunctionType)); } function getTypeFacts(type: Type): TypeFacts { @@ -18192,7 +18184,7 @@ namespace ts { // otherwise - check if at least one export is value symbolLinks.exportsSomeValue = hasExportAssignment ? !!(moduleSymbol.flags & SymbolFlags.Value) - : forEachValue(getExportsOfModule(moduleSymbol), isValue); + : forEachProperty(getExportsOfModule(moduleSymbol), isValue); } return symbolLinks.exportsSomeValue; diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index eaf17f00dd0..44befb3943d 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -486,10 +486,11 @@ namespace ts { /* @internal */ export function createCompilerDiagnosticForInvalidCustomType(opt: CommandLineOptionOfCustomType): Diagnostic { const namesOfType: string[] = []; - forEachKey(opt.type, key => { - namesOfType.push(` '${key}'`); - }); - + for (const key in opt.type) { + if (hasProperty(opt.type, key)) { + namesOfType.push(` '${key}'`); + } + } return createCompilerDiagnostic(Diagnostics.Argument_for_0_option_must_be_Colon_1, `--${opt.name}`, namesOfType); } @@ -551,11 +552,11 @@ namespace ts { s = s.slice(s.charCodeAt(1) === CharacterCodes.minus ? 2 : 1).toLowerCase(); // Try to translate short option names to their full equivalents. - if (hasProperty(shortOptionNames, s)) { + if (s in shortOptionNames) { s = shortOptionNames[s]; } - if (hasProperty(optionNameMap, s)) { + if (s in optionNameMap) { const opt = optionNameMap[s]; if (opt.isTSConfigOnly) { @@ -811,7 +812,7 @@ namespace ts { const optionNameMap = arrayToMap(optionDeclarations, opt => opt.name); for (const id in jsonOptions) { - if (hasProperty(optionNameMap, id)) { + if (id in optionNameMap) { const opt = optionNameMap[id]; defaultOptions[opt.name] = convertJsonOption(opt, jsonOptions[id], basePath, errors); } @@ -1011,14 +1012,14 @@ namespace ts { removeWildcardFilesWithLowerPriorityExtension(file, wildcardFileMap, supportedExtensions, keyMapper); const key = keyMapper(file); - if (!hasProperty(literalFileMap, key) && !hasProperty(wildcardFileMap, key)) { + if (!(key in literalFileMap) && !(key in wildcardFileMap)) { wildcardFileMap[key] = file; } } } - const literalFiles = reduceProperties(literalFileMap, addFileToOutput, []); - const wildcardFiles = reduceProperties(wildcardFileMap, addFileToOutput, []); + const literalFiles = reduceOwnProperties(literalFileMap, addFileToOutput, []); + const wildcardFiles = reduceOwnProperties(wildcardFileMap, addFileToOutput, []); wildcardFiles.sort(host.useCaseSensitiveFileNames ? compareStrings : compareStringsCaseInsensitive); return { fileNames: literalFiles.concat(wildcardFiles), @@ -1076,7 +1077,7 @@ namespace ts { if (match) { const key = useCaseSensitiveFileNames ? match[0] : match[0].toLowerCase(); const flags = watchRecursivePattern.test(name) ? WatchDirectoryFlags.Recursive : WatchDirectoryFlags.None; - const existingFlags = getProperty(wildcardDirectories, key); + const existingFlags = wildcardDirectories[key]; if (existingFlags === undefined || existingFlags < flags) { wildcardDirectories[key] = flags; if (flags === WatchDirectoryFlags.Recursive) { @@ -1088,11 +1089,9 @@ namespace ts { // Remove any subpaths under an existing recursively watched directory. for (const key in wildcardDirectories) { - if (hasProperty(wildcardDirectories, key)) { - for (const recursiveKey of recursiveKeys) { - if (key !== recursiveKey && containsPath(recursiveKey, key, path, !useCaseSensitiveFileNames)) { - delete wildcardDirectories[key]; - } + for (const recursiveKey of recursiveKeys) { + if (key !== recursiveKey && containsPath(recursiveKey, key, path, !useCaseSensitiveFileNames)) { + delete wildcardDirectories[key]; } } } @@ -1115,7 +1114,7 @@ namespace ts { for (let i = ExtensionPriority.Highest; i < adjustedExtensionPriority; i++) { const higherPriorityExtension = extensions[i]; const higherPriorityPath = keyMapper(changeExtension(file, higherPriorityExtension)); - if (hasProperty(literalFiles, higherPriorityPath) || hasProperty(wildcardFiles, higherPriorityPath)) { + if (higherPriorityPath in literalFiles || higherPriorityPath in wildcardFiles) { return true; } } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 168a201c388..7bb5dbef2c5 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -21,10 +21,8 @@ namespace ts { const createObject = Object.create; - export function createMap(): Map { - /* tslint:disable:no-null-keyword */ - const map: Map = createObject(null); - /* tslint:enable:no-null-keyword */ + export function createMap(template?: MapLike): Map { + const map: Map = createObject(null); // tslint:disable-line:no-null-keyword // Using 'delete' on an object causes V8 to put the object in dictionary mode. // This disables creation of hidden classes, which are expensive when an object is @@ -32,6 +30,10 @@ namespace ts { map["__"] = undefined; delete map["__"]; + if (template) { + copyOwnProperties(template, map); + } + return map; } @@ -62,7 +64,7 @@ namespace ts { } function contains(path: Path) { - return hasProperty(files, toKey(path)); + return toKey(path) in files; } function remove(path: Path) { @@ -350,82 +352,192 @@ namespace ts { const hasOwnProperty = Object.prototype.hasOwnProperty; + /** + * Indicates whether a map-like contains an own property with the specified key. + * + * NOTE: This is intended for use only with MapLike objects. For Map objects, use + * the 'in' operator. + * + * @param map A map-like. + * @param key A property key. + */ export function hasProperty(map: MapLike, key: string): boolean { return hasOwnProperty.call(map, key); } - export function getKeys(map: MapLike): string[] { + /** + * Gets the value of an owned property in a map-like. + * + * NOTE: This is intended for use only with MapLike objects. For Map objects, use + * an indexer. + * + * @param map A map-like. + * @param key A property key. + */ + export function getProperty(map: MapLike, key: string): T | undefined { + return hasOwnProperty.call(map, key) ? map[key] : undefined; + } + + /** + * Gets the owned, enumerable property keys of a map-like. + * + * @param map A map-like. + */ + export function getOwnKeys(map: MapLike): string[] { const keys: string[] = []; - for (const key in map) { + for (const key in map) if (hasOwnProperty.call(map, key)) { keys.push(key); } return keys; } - export function getProperty(map: MapLike, key: string): T | undefined { - return hasProperty(map, key) ? map[key] : undefined; + /** + * Enumerates the properties of a Map, invoking a callback and returning the first truthy result. + * + * NOTE: This is intended for use with Map objects. For MapLike objects, use + * forEachOwnProperties instead as it offers better runtime safety. + * + * @param map A map for which properties should be enumerated. + * @param callback A callback to invoke for each property. + */ + export function forEachProperty(map: Map, callback: (value: T, key: string) => U): U { + let result: U; + for (const key in map) { + if (result = callback(map[key], key)) break; + } + return result; } - export function getOrUpdateProperty(map: MapLike, key: string, makeValue: () => T): T { - return hasProperty(map, key) ? map[key] : map[key] = makeValue(); + /** + * Enumerates the owned properties of a MapLike, invoking a callback and returning the first truthy result. + * + * NOTE: This is intended for use with MapLike objects. For Map objects, use + * forEachProperty instead as it offers better performance. + * + * @param map A map for which properties should be enumerated. + * @param callback A callback to invoke for each property. + */ + export function forEachOwnProperty(map: MapLike, callback: (value: T, key: string) => U): U { + let result: U; + for (const key in map) if (hasOwnProperty.call(map, key)) { + if (result = callback(map[key], key)) break; + } + return result; } - export function isEmpty(map: MapLike) { - for (const id in map) { - if (hasProperty(map, id)) { - return false; + /** + * Returns true if a Map has some matching property. + * + * @param map A map whose properties should be tested. + * @param predicate An optional callback used to test each property. + */ + export function someProperties(map: Map, predicate?: (value: T, key: string) => boolean) { + for (const key in map) { + if (!predicate || predicate(map[key], key)) return true; + } + return false; + } + + /** + * Performs a shallow copy of the properties from a source Map to a target MapLike + * + * NOTE: This is intended for use with Map objects. For MapLike objects, use + * copyOwnProperties instead as it offers better runtime safety. + * + * @param source A map from which properties should be copied. + * @param target A map to which properties should be copied. + */ + export function copyProperties(source: Map, target: MapLike): void { + for (const key in source) { + target[key] = source[key]; + } + } + + /** + * Performs a shallow copy of the owned properties from a source map to a target map-like. + * + * NOTE: This is intended for use with MapLike objects. For Map objects, use + * copyProperties instead as it offers better performance. + * + * @param source A map-like from which properties should be copied. + * @param target A map-like to which properties should be copied. + */ + export function copyOwnProperties(source: MapLike, target: MapLike): void { + for (const key in source) if (hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } + + /** + * Reduce the properties of a map. + * + * NOTE: This is intended for use with Map objects. For MapLike objects, use + * reduceOwnProperties instead as it offers better runtime safety. + * + * @param map The map to reduce + * @param callback An aggregation function that is called for each entry in the map + * @param initial The initial value for the reduction. + */ + export function reduceProperties(map: Map, callback: (aggregate: U, value: T, key: string) => U, initial: U): U { + let result = initial; + for (const key in map) { + result = callback(result, map[key], String(key)); + } + return result; + } + + /** + * Reduce the properties defined on a map-like (but not from its prototype chain). + * + * NOTE: This is intended for use with MapLike objects. For Map objects, use + * reduceProperties instead as it offers better performance. + * + * @param map The map-like to reduce + * @param callback An aggregation function that is called for each entry in the map + * @param initial The initial value for the reduction. + */ + export function reduceOwnProperties(map: MapLike, callback: (aggregate: U, value: T, key: string) => U, initial: U): U { + let result = initial; + for (const key in map) if (hasOwnProperty.call(map, key)) { + result = callback(result, map[key], String(key)); + } + return result; + } + + /** + * Counts the owned properties of a map-like. + * + * @param map A map-like whose properties should be counted. + * @param predicate An optional callback used to limit which properties should be counted. + */ + export function countOwnProperties(map: MapLike, predicate?: (value: T, key: string) => boolean) { + let count = 0; + for (const key in map) if (hasOwnProperty.call(map, key)) { + if (!predicate || predicate(map[key], key)) { + count++; } } + return count; + } + + /** + * Performs a shallow equality comparison of the contents of two map-likes. + * + * @param left A map whose properties should be compared. + * @param right A map whose properties should be compared. + */ + export function equalOwnProperties(left: MapLike, right: MapLike) { + if (left === right) return true; + if (!left || !right) return false; + for (const key in left) if (hasOwnProperty.call(left, key)) { + if (!hasOwnProperty.call(right, key) === undefined || left[key] !== right[key]) return false; + } + for (const key in right) if (hasOwnProperty.call(right, key)) { + if (!hasOwnProperty.call(left, key)) return false; + } return true; } - export function clone(object: T): T { - const result: any = {}; - for (const id in object) { - result[id] = (object)[id]; - } - return result; - } - - export function extend, T2 extends MapLike<{}>>(first: T1 , second: T2): T1 & T2 { - const result: T1 & T2 = {}; - for (const id in first) { - (result as any)[id] = first[id]; - } - for (const id in second) { - if (!hasProperty(result, id)) { - (result as any)[id] = second[id]; - } - } - return result; - } - - export function forEachValue(map: MapLike, callback: (value: T) => U): U { - let result: U; - for (const id in map) { - if (result = callback(map[id])) break; - } - return result; - } - - export function forEachKey(map: MapLike, callback: (key: string) => U): U { - let result: U; - for (const id in map) { - if (result = callback(id)) break; - } - return result; - } - - export function lookUp(map: MapLike, key: string): T { - return hasProperty(map, key) ? map[key] : undefined; - } - - export function copyMap(source: MapLike, target: MapLike): void { - for (const p in source) { - target[p] = source[p]; - } - } - /** * Creates a map from the elements of an array. * @@ -436,33 +548,42 @@ namespace ts { * the same key with the given 'makeKey' function, then the element with the higher * index in the array will be the one associated with the produced key. */ - export function arrayToMap(array: T[], makeKey: (value: T) => string): Map { - const result = createMap(); - - forEach(array, value => { - result[makeKey(value)] = value; - }); - + export function arrayToMap(array: T[], makeKey: (value: T) => string): Map; + export function arrayToMap(array: T[], makeKey: (value: T) => string, makeValue: (value: T) => U): Map; + export function arrayToMap(array: T[], makeKey: (value: T) => string, makeValue?: (value: T) => U): Map { + const result = createMap(); + for (const value of array) { + result[makeKey(value)] = makeValue ? makeValue(value) : value; + } return result; } - /** - * Reduce the properties of a map. - * - * @param map The map to reduce - * @param callback An aggregation function that is called for each entry in the map - * @param initial The initial value for the reduction. - */ - export function reduceProperties(map: MapLike, callback: (aggregate: U, value: T, key: string) => U, initial: U): U { - let result = initial; - if (map) { - for (const key in map) { - if (hasProperty(map, key)) { - result = callback(result, map[key], String(key)); - } + export function cloneMap(map: Map) { + const clone = createMap(); + copyProperties(map, clone); + return clone; + } + + export function clone(object: T): T { + const result: any = {}; + for (const id in object) { + if (hasOwnProperty.call(object, id)) { + result[id] = (object)[id]; } } + return result; + } + export function extend, T2 extends MapLike<{}>>(first: T1 , second: T2): T1 & T2 { + const result: T1 & T2 = {}; + for (const id in first) { + (result as any)[id] = first[id]; + } + for (const id in second) { + if (!hasProperty(result, id)) { + (result as any)[id] = second[id]; + } + } return result; } diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index 56ff206ab01..3642ee7f10a 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -157,9 +157,7 @@ namespace ts { if (usedTypeDirectiveReferences) { for (const directive in usedTypeDirectiveReferences) { - if (hasProperty(usedTypeDirectiveReferences, directive)) { - referencesOutput += `/// ${newLine}`; - } + referencesOutput += `/// ${newLine}`; } } @@ -272,7 +270,7 @@ namespace ts { usedTypeDirectiveReferences = createMap(); } for (const directive of typeReferenceDirectives) { - if (!hasProperty(usedTypeDirectiveReferences, directive)) { + if (!(directive in usedTypeDirectiveReferences)) { usedTypeDirectiveReferences[directive] = directive; } } @@ -537,14 +535,14 @@ namespace ts { // do not need to keep track of created temp names. function getExportDefaultTempVariableName(): string { const baseName = "_default"; - if (!hasProperty(currentIdentifiers, baseName)) { + if (!(baseName in currentIdentifiers)) { return baseName; } let count = 0; while (true) { count++; const name = baseName + "_" + count; - if (!hasProperty(currentIdentifiers, name)) { + if (!(name in currentIdentifiers)) { return name; } } diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 1d62a4a4f46..47495cc69bb 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -407,7 +407,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge function isUniqueLocalName(name: string, container: Node): boolean { for (let node = container; isNodeDescendentOf(node, container); node = node.nextContainer) { - if (node.locals && hasProperty(node.locals, name)) { + if (node.locals && name in node.locals) { // We conservatively include alias symbols to cover cases where they're emitted as locals if (node.locals[name].flags & (SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias)) { return false; @@ -669,8 +669,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge function isUniqueName(name: string): boolean { return !resolver.hasGlobalName(name) && - !hasProperty(currentFileIdentifiers, name) && - !hasProperty(generatedNameSet, name); + !(name in currentFileIdentifiers) && + !(name in generatedNameSet); } // Return the next available name in the pattern _a ... _z, _0, _1, ... @@ -2646,7 +2646,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge return false; } - return !exportEquals && exportSpecifiers && hasProperty(exportSpecifiers, (node).text); + return !exportEquals && exportSpecifiers && (node).text in exportSpecifiers; } function emitPrefixUnaryExpression(node: PrefixUnaryExpression) { @@ -3263,7 +3263,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge write(", "); } - if (!hasProperty(seen, id.text)) { + if (!(id.text in seen)) { emit(id); seen[id.text] = id.text; } @@ -3970,7 +3970,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge return; } - if (!exportEquals && exportSpecifiers && hasProperty(exportSpecifiers, name.text)) { + if (!exportEquals && exportSpecifiers && name.text in exportSpecifiers) { for (const specifier of exportSpecifiers[name.text]) { writeLine(); emitStart(specifier.name); @@ -6842,7 +6842,7 @@ const _super = (function (geti, seti) { // export { x, y } for (const specifier of (node).exportClause.elements) { const name = (specifier.propertyName || specifier.name).text; - getOrUpdateProperty(exportSpecifiers, name, () => []).push(specifier); + (exportSpecifiers[name] || (exportSpecifiers[name] = [])).push(specifier); } } break; @@ -6941,7 +6941,7 @@ const _super = (function (geti, seti) { } // local names set should only be added if we have anything exported - if (!exportedDeclarations && isEmpty(exportSpecifiers)) { + if (!exportedDeclarations && !someProperties(exportSpecifiers)) { // no exported declarations (export var ...) or export specifiers (export {x}) // check if we have any non star export declarations. let hasExportDeclarationWithExportClause = false; @@ -7091,7 +7091,7 @@ const _super = (function (geti, seti) { if (name) { // do not emit duplicate entries (in case of declaration merging) in the list of hoisted variables const text = unescapeIdentifier(name.text); - if (hasProperty(seen, text)) { + if (text in seen) { continue; } else { @@ -7460,7 +7460,7 @@ const _super = (function (geti, seti) { // for deduplication purposes in key remove leading and trailing quotes so 'a' and "a" will be considered the same const key = text.substr(1, text.length - 2); - if (hasProperty(groupIndices, key)) { + if (key in groupIndices) { // deduplicate/group entries in dependency list by the dependency name const groupIndex = groupIndices[key]; dependencyGroups[groupIndex].push(externalImports[i]); diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 767fcb488bb..0bf67b0ddb0 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -501,7 +501,7 @@ namespace ts { if (state.traceEnabled) { trace(state.host, Diagnostics.paths_option_is_specified_looking_for_a_pattern_to_match_module_name_0, moduleName); } - matchedPattern = matchPatternOrExact(getKeys(state.compilerOptions.paths), moduleName); + matchedPattern = matchPatternOrExact(getOwnKeys(state.compilerOptions.paths), moduleName); } if (matchedPattern) { @@ -875,7 +875,7 @@ namespace ts { } function directoryExists(directoryPath: string): boolean { - if (hasProperty(existingDirectories, directoryPath)) { + if (directoryPath in existingDirectories) { return true; } if (sys.directoryExists(directoryPath)) { @@ -903,7 +903,7 @@ namespace ts { const hash = sys.createHash(data); const mtimeBefore = sys.getModifiedTime(fileName); - if (mtimeBefore && hasProperty(outputFingerprints, fileName)) { + if (mtimeBefore && fileName in outputFingerprints) { const fingerprint = outputFingerprints[fileName]; // If output has not been changed, and the file has no external modification @@ -1041,14 +1041,9 @@ namespace ts { const resolutions: T[] = []; const cache = createMap(); for (const name of names) { - let result: T; - if (hasProperty(cache, name)) { - result = cache[name]; - } - else { - result = loader(name, containingFile); - cache[name] = result; - } + const result = name in cache + ? cache[name] + : cache[name] = loader(name, containingFile); resolutions.push(result); } return resolutions; @@ -1249,7 +1244,7 @@ namespace ts { classifiableNames = createMap(); for (const sourceFile of files) { - copyMap(sourceFile.classifiableNames, classifiableNames); + copyProperties(sourceFile.classifiableNames, classifiableNames); } } @@ -1277,7 +1272,7 @@ namespace ts { (oldOptions.maxNodeModuleJsDepth !== options.maxNodeModuleJsDepth) || !arrayIsEqualTo(oldOptions.typeRoots, oldOptions.typeRoots) || !arrayIsEqualTo(oldOptions.rootDirs, options.rootDirs) || - !mapIsEqualTo(oldOptions.paths, options.paths)) { + !equalOwnProperties(oldOptions.paths, options.paths)) { return false; } @@ -1399,7 +1394,7 @@ namespace ts { getSourceFile: program.getSourceFile, getSourceFileByPath: program.getSourceFileByPath, getSourceFiles: program.getSourceFiles, - isSourceFileFromExternalLibrary: (file: SourceFile) => !!lookUp(sourceFilesFoundSearchingNodeModules, file.path), + isSourceFileFromExternalLibrary: (file: SourceFile) => !!sourceFilesFoundSearchingNodeModules[file.path], writeFile: writeFileCallback || ( (fileName, data, writeByteOrderMark, onError, sourceFiles) => host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles)), isEmitBlocked, @@ -1937,7 +1932,7 @@ namespace ts { // If the file was previously found via a node_modules search, but is now being processed as a root file, // then everything it sucks in may also be marked incorrectly, and needs to be checked again. - if (file && lookUp(sourceFilesFoundSearchingNodeModules, file.path) && currentNodeModulesDepth == 0) { + if (file && sourceFilesFoundSearchingNodeModules[file.path] && currentNodeModulesDepth == 0) { sourceFilesFoundSearchingNodeModules[file.path] = false; if (!options.noResolve) { processReferencedFiles(file, getDirectoryPath(fileName), isDefaultLib); @@ -1948,7 +1943,7 @@ namespace ts { processImportedModules(file, getDirectoryPath(fileName)); } // See if we need to reprocess the imports due to prior skipped imports - else if (file && lookUp(modulesWithElidedImports, file.path)) { + else if (file && modulesWithElidedImports[file.path]) { if (currentNodeModulesDepth < maxNodeModulesJsDepth) { modulesWithElidedImports[file.path] = false; processImportedModules(file, getDirectoryPath(fileName)); diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 3b588edd101..73cf8e9c4e2 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -445,10 +445,9 @@ namespace ts { } function cachedFileExists(fileName: string): boolean { - if (hasProperty(cachedExistingFiles, fileName)) { - return cachedExistingFiles[fileName]; - } - return cachedExistingFiles[fileName] = hostFileExists(fileName); + return fileName in cachedExistingFiles + ? cachedExistingFiles[fileName] + : cachedExistingFiles[fileName] = hostFileExists(fileName); } function getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void) { @@ -704,9 +703,12 @@ namespace ts { description = getDiagnosticText(option.description); const options: string[] = []; const element = (option).element; - forEachKey(>element.type, key => { - options.push(`'${key}'`); - }); + const typeMap = >element.type; + for (const key in typeMap) { + if (hasProperty(typeMap, key)) { + options.push(`'${key}'`); + } + } optionsDescriptionMap[description] = options; } else { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index fcda8f0ce1a..779e479cc43 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -87,25 +87,6 @@ namespace ts { return node.end - node.pos; } - export function mapIsEqualTo(map1: MapLike, map2: MapLike): boolean { - if (!map1 || !map2) { - return map1 === map2; - } - return containsAll(map1, map2) && containsAll(map2, map1); - } - - function containsAll(map: MapLike, other: MapLike): boolean { - for (const key in map) { - if (!hasProperty(map, key)) { - continue; - } - if (!hasProperty(other, key) || map[key] !== other[key]) { - return false; - } - } - return true; - } - export function arrayIsEqualTo(array1: T[], array2: T[], equaler?: (a: T, b: T) => boolean): boolean { if (!array1 || !array2) { return array1 === array2; @@ -2792,7 +2773,7 @@ namespace ts { } function stringifyObject(value: any) { - return `{${reduceProperties(value, stringifyProperty, "")}}`; + return `{${reduceOwnProperties(value, stringifyProperty, "")}}`; } function stringifyProperty(memo: string, value: any, key: string) { diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index e7e076a9eae..345ce19f6ae 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -204,7 +204,7 @@ namespace FourSlash { public formatCodeOptions: ts.FormatCodeOptions; - private inputFiles: ts.MapLike = {}; // Map between inputFile's fileName and its content for easily looking up when resolving references + private inputFiles = ts.createMap(); // Map between inputFile's fileName and its content for easily looking up when resolving references // Add input file which has matched file name with the given reference-file path. // This is necessary when resolveReference flag is specified @@ -300,11 +300,11 @@ namespace FourSlash { } else { // resolveReference file-option is not specified then do not resolve any files and include all inputFiles - ts.forEachKey(this.inputFiles, fileName => { + for (const fileName in this.inputFiles) { if (!Harness.isDefaultLibraryFile(fileName)) { this.languageServiceAdapterHost.addScript(fileName, this.inputFiles[fileName], /*isRootFile*/ true); } - }); + } this.languageServiceAdapterHost.addScript(Harness.Compiler.defaultLibFileName, Harness.Compiler.getDefaultLibrarySourceFile().text, /*isRootFile*/ false); } @@ -773,7 +773,7 @@ namespace FourSlash { } public verifyRangesWithSameTextReferenceEachOther() { - ts.forEachValue(this.rangesByText(), ranges => this.verifyRangesReferenceEachOther(ranges)); + ts.forEachOwnProperty(this.rangesByText(), ranges => this.verifyRangesReferenceEachOther(ranges)); } private verifyReferencesWorker(references: ts.ReferenceEntry[], fileName: string, start: number, end: number, isWriteAccess?: boolean, isDefinition?: boolean) { diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 5ae33303d55..1f7c48d21bc 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -1011,7 +1011,7 @@ namespace Harness { optionsIndex[option.name.toLowerCase()] = option; } } - return ts.lookUp(optionsIndex, name.toLowerCase()); + return ts.getProperty(optionsIndex, name.toLowerCase()); } export function setCompilerOptionsFromHarnessSetting(settings: Harness.TestCaseParser.CompilerSettings, options: ts.CompilerOptions & HarnessOptions): void { diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 4f95f19e144..b03d8788165 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -135,7 +135,7 @@ namespace Harness.LanguageService { public getFilenames(): string[] { const fileNames: string[] = []; - ts.forEachValue(this.fileNameToScript, (scriptInfo) => { + ts.forEachOwnProperty(this.fileNameToScript, (scriptInfo) => { if (scriptInfo.isRootFile) { // only include root files here // usually it means that we won't include lib.d.ts in the list of root files so it won't mess the computation of compilation root dir. @@ -146,7 +146,7 @@ namespace Harness.LanguageService { } public getScriptInfo(fileName: string): ScriptInfo { - return ts.lookUp(this.fileNameToScript, fileName); + return ts.getProperty(this.fileNameToScript, fileName); } public addScript(fileName: string, content: string, isRootFile: boolean): void { diff --git a/src/harness/unittests/cachingInServerLSHost.ts b/src/harness/unittests/cachingInServerLSHost.ts index a1e010b97df..61ab38b2d8f 100644 --- a/src/harness/unittests/cachingInServerLSHost.ts +++ b/src/harness/unittests/cachingInServerLSHost.ts @@ -8,7 +8,7 @@ namespace ts { function createDefaultServerHost(fileMap: MapLike): server.ServerHost { const existingDirectories: MapLike = {}; - forEachValue(fileMap, v => { + forEachOwnProperty(fileMap, v => { let dir = getDirectoryPath(v.name); let previous: string; do { diff --git a/src/harness/unittests/reuseProgramStructure.ts b/src/harness/unittests/reuseProgramStructure.ts index 76015667232..9d8b176a6a8 100644 --- a/src/harness/unittests/reuseProgramStructure.ts +++ b/src/harness/unittests/reuseProgramStructure.ts @@ -130,7 +130,7 @@ namespace ts { }, fileExists: fileName => hasProperty(files, fileName), readFile: fileName => { - const file = lookUp(files, fileName); + const file = getProperty(files, fileName); return file && file.text; } }; @@ -152,16 +152,6 @@ namespace ts { return program; } - function getSizeOfMap(map: MapLike): number { - let size = 0; - for (const id in map) { - if (hasProperty(map, id)) { - size++; - } - } - return size; - } - function checkResolvedModule(expected: ResolvedModule, actual: ResolvedModule): void { assert.isTrue(actual !== undefined); assert.isTrue(expected.resolvedFileName === actual.resolvedFileName, `'resolvedFileName': expected '${expected.resolvedFileName}' to be equal to '${actual.resolvedFileName}'`); @@ -183,8 +173,8 @@ namespace ts { } else { assert.isTrue(cache !== undefined, `expected ${caption} to be set`); - const actualCacheSize = getSizeOfMap(cache); - const expectedSize = getSizeOfMap(expectedContent); + const actualCacheSize = countOwnProperties(cache); + const expectedSize = countOwnProperties(expectedContent); assert.isTrue(actualCacheSize === expectedSize, `expected actual size: ${actualCacheSize} to be equal to ${expectedSize}`); for (const id in expectedContent) { diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 74b9e303cf7..1e76a636c9a 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -68,20 +68,10 @@ namespace ts { return entry; } - function sizeOfMap(map: MapLike): number { - let n = 0; - for (const name in map) { - if (hasProperty(map, name)) { - n++; - } - } - return n; - } - function checkMapKeys(caption: string, map: MapLike, expectedKeys: string[]) { - assert.equal(sizeOfMap(map), expectedKeys.length, `${caption}: incorrect size of map`); + assert.equal(countOwnProperties(map), expectedKeys.length, `${caption}: incorrect size of map`); for (const name of expectedKeys) { - assert.isTrue(hasProperty(map, name), `${caption} is expected to contain ${name}, actual keys: ${getKeys(map)}`); + assert.isTrue(hasProperty(map, name), `${caption} is expected to contain ${name}, actual keys: ${getOwnKeys(map)}`); } } @@ -208,7 +198,7 @@ namespace ts { watchDirectory(directoryName: string, callback: DirectoryWatcherCallback, recursive: boolean): DirectoryWatcher { const path = this.toPath(directoryName); - const callbacks = lookUp(this.watchedDirectories, path) || (this.watchedDirectories[path] = []); + const callbacks = getProperty(this.watchedDirectories, path) || (this.watchedDirectories[path] = []); callbacks.push({ cb: callback, recursive }); return { referenceCount: 0, @@ -229,7 +219,7 @@ namespace ts { triggerDirectoryWatcherCallback(directoryName: string, fileName: string): void { const path = this.toPath(directoryName); - const callbacks = lookUp(this.watchedDirectories, path); + const callbacks = getProperty(this.watchedDirectories, path); if (callbacks) { for (const callback of callbacks) { callback.cb(fileName); @@ -239,7 +229,7 @@ namespace ts { triggerFileWatcherCallback(fileName: string, removed?: boolean): void { const path = this.toPath(fileName); - const callbacks = lookUp(this.watchedFiles, path); + const callbacks = getProperty(this.watchedFiles, path); if (callbacks) { for (const callback of callbacks) { callback(path, removed); @@ -249,7 +239,7 @@ namespace ts { watchFile(fileName: string, callback: FileWatcherCallback) { const path = this.toPath(fileName); - const callbacks = lookUp(this.watchedFiles, path) || (this.watchedFiles[path] = []); + const callbacks = getProperty(this.watchedFiles, path) || (this.watchedFiles[path] = []); callbacks.push(callback); return { close: () => { @@ -594,7 +584,7 @@ namespace ts { content: `{ "compilerOptions": { "target": "es6" - }, + }, "files": [ "main.ts" ] }` }; @@ -621,7 +611,7 @@ namespace ts { content: `{ "compilerOptions": { "target": "es6" - }, + }, "files": [ "main.ts" ] }` }; diff --git a/src/server/client.ts b/src/server/client.ts index 3547ac52602..88177e91fad 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -37,7 +37,7 @@ namespace ts.server { } private getLineMap(fileName: string): number[] { - let lineMap = ts.lookUp(this.lineMaps, fileName); + let lineMap = this.lineMaps[fileName]; if (!lineMap) { const scriptSnapshot = this.host.getScriptSnapshot(fileName); lineMap = this.lineMaps[fileName] = ts.computeLineStarts(scriptSnapshot.getText(0, scriptSnapshot.getLength())); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index a14c42f471e..9ba243d9704 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -136,9 +136,9 @@ namespace ts.server { for (const name of names) { // check if this is a duplicate entry in the list - let resolution = lookUp(newResolutions, name); + let resolution = newResolutions[name]; if (!resolution) { - const existingResolution = currentResolutionsInFile && ts.lookUp(currentResolutionsInFile, name); + const existingResolution = currentResolutionsInFile && currentResolutionsInFile[name]; if (moduleResolutionIsValid(existingResolution)) { // ok, it is safe to use existing name resolution results resolution = existingResolution; @@ -563,7 +563,7 @@ namespace ts.server { } let strBuilder = ""; - ts.forEachValue(this.filenameToSourceFile, + ts.forEachProperty(this.filenameToSourceFile, sourceFile => { strBuilder += sourceFile.fileName + "\n"; }); return strBuilder; } @@ -857,7 +857,7 @@ namespace ts.server { if (project.isConfiguredProject()) { project.projectFileWatcher.close(); project.directoryWatcher.close(); - forEachValue(project.directoriesWatchedForWildcards, watcher => { watcher.close(); }); + forEachProperty(project.directoriesWatchedForWildcards, watcher => { watcher.close(); }); delete project.directoriesWatchedForWildcards; this.configuredProjects = copyListRemovingItem(project, this.configuredProjects); } @@ -1124,7 +1124,7 @@ namespace ts.server { getScriptInfo(filename: string) { filename = ts.normalizePath(filename); - return ts.lookUp(this.filenameToScriptInfo, filename); + return this.filenameToScriptInfo[filename]; } /** @@ -1133,7 +1133,7 @@ namespace ts.server { */ openFile(fileName: string, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind) { fileName = ts.normalizePath(fileName); - let info = ts.lookUp(this.filenameToScriptInfo, fileName); + let info = this.filenameToScriptInfo[fileName]; if (!info) { let content: string; if (this.host.fileExists(fileName)) { @@ -1246,7 +1246,7 @@ namespace ts.server { * @param filename is absolute pathname */ closeClientFile(filename: string) { - const info = ts.lookUp(this.filenameToScriptInfo, filename); + const info = this.filenameToScriptInfo[filename]; if (info) { this.closeOpenFile(info); info.isOpen = false; @@ -1255,14 +1255,14 @@ namespace ts.server { } getProjectForFile(filename: string) { - const scriptInfo = ts.lookUp(this.filenameToScriptInfo, filename); + const scriptInfo = this.filenameToScriptInfo[filename]; if (scriptInfo) { return scriptInfo.defaultProject; } } printProjectsForFile(filename: string) { - const scriptInfo = ts.lookUp(this.filenameToScriptInfo, filename); + const scriptInfo = this.filenameToScriptInfo[filename]; if (scriptInfo) { this.psLogger.startGroup(); this.psLogger.info("Projects for " + filename); @@ -1419,7 +1419,7 @@ namespace ts.server { /*recursive*/ true ); - project.directoriesWatchedForWildcards = reduceProperties(projectOptions.wildcardDirectories, (watchers, flag, directory) => { + project.directoriesWatchedForWildcards = reduceOwnProperties(projectOptions.wildcardDirectories, (watchers, flag, directory) => { if (comparePaths(configDirectoryPath, directory, ".", !this.host.useCaseSensitiveFileNames) !== Comparison.EqualTo) { const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0; this.log(`Add ${ recursive ? "recursive " : ""}watcher for: ${directory}`); diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts index 05de0061d39..bac33a6bdfb 100644 --- a/src/services/jsTyping.ts +++ b/src/services/jsTyping.ts @@ -139,16 +139,16 @@ namespace ts.JsTyping { const jsonConfig: PackageJson = result.config; filesToWatch.push(jsonPath); if (jsonConfig.dependencies) { - mergeTypings(getKeys(jsonConfig.dependencies)); + mergeTypings(getOwnKeys(jsonConfig.dependencies)); } if (jsonConfig.devDependencies) { - mergeTypings(getKeys(jsonConfig.devDependencies)); + mergeTypings(getOwnKeys(jsonConfig.devDependencies)); } if (jsonConfig.optionalDependencies) { - mergeTypings(getKeys(jsonConfig.optionalDependencies)); + mergeTypings(getOwnKeys(jsonConfig.optionalDependencies)); } if (jsonConfig.peerDependencies) { - mergeTypings(getKeys(jsonConfig.peerDependencies)); + mergeTypings(getOwnKeys(jsonConfig.peerDependencies)); } } } diff --git a/src/services/navigateTo.ts b/src/services/navigateTo.ts index e9afb6925b5..ccded188e61 100644 --- a/src/services/navigateTo.ts +++ b/src/services/navigateTo.ts @@ -15,7 +15,7 @@ namespace ts.NavigateTo { const nameToDeclarations = sourceFile.getNamedDeclarations(); for (const name in nameToDeclarations) { - const declarations = getProperty(nameToDeclarations, name); + const declarations = nameToDeclarations[name]; if (declarations) { // First do a quick check to see if the name of the declaration matches the // last portion of the (possibly) dotted name they're searching for. diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index ebd6936ea85..dda9ef485d2 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -243,7 +243,7 @@ namespace ts.NavigationBar { return true; } - const itemsWithSameName = getProperty(nameToItems, name); + const itemsWithSameName = nameToItems[name]; if (!itemsWithSameName) { nameToItems[name] = child; return true; diff --git a/src/services/services.ts b/src/services/services.ts index 850e29030d8..0c1f5929fbf 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -990,7 +990,7 @@ namespace ts { } function getDeclarations(name: string) { - return getProperty(result, name) || (result[name] = []); + return result[name] || (result[name] = []); } function getDeclarationName(declaration: Declaration) { @@ -2042,7 +2042,7 @@ namespace ts { function fixupCompilerOptions(options: CompilerOptions, diagnostics: Diagnostic[]): CompilerOptions { // Lazily create this value to fix module loading errors. commandLineOptionsStringToEnum = commandLineOptionsStringToEnum || filter(optionDeclarations, o => - typeof o.type === "object" && !forEachValue(>o.type, v => typeof v !== "number")); + typeof o.type === "object" && !forEachOwnProperty(>o.type, v => typeof v !== "number")); options = clone(options); @@ -2058,7 +2058,7 @@ namespace ts { options[opt.name] = parseCustomTypeOption(opt, value, diagnostics); } else { - if (!forEachValue(opt.type, v => v === value)) { + if (!forEachOwnProperty(opt.type, v => v === value)) { // Supplied value isn't a valid enum value. diagnostics.push(createCompilerDiagnosticForInvalidCustomType(opt)); } @@ -2251,7 +2251,7 @@ namespace ts { } function getBucketForCompilationSettings(key: DocumentRegistryBucketKey, createIfMissing: boolean): FileMap { - let bucket = lookUp(buckets, key); + let bucket = buckets[key]; if (!bucket && createIfMissing) { buckets[key] = bucket = createFileMap(); } @@ -2260,7 +2260,7 @@ namespace ts { function reportStats() { const bucketInfoArray = Object.keys(buckets).filter(name => name && name.charAt(0) === "_").map(name => { - const entries = lookUp(buckets, name); + const entries = buckets[name]; const sourceFiles: { name: string; refCount: number; references: string[]; }[] = []; entries.forEachValue((key, entry) => { sourceFiles.push({ @@ -3099,7 +3099,7 @@ namespace ts { oldSettings.allowJs !== newSettings.allowJs || oldSettings.disableSizeLimit !== oldSettings.disableSizeLimit || oldSettings.baseUrl !== newSettings.baseUrl || - !mapIsEqualTo(oldSettings.paths, newSettings.paths)); + !equalOwnProperties(oldSettings.paths, newSettings.paths)); // Now create a new compiler const compilerHost: CompilerHost = { @@ -4114,11 +4114,11 @@ namespace ts { existingImportsOrExports[name.text] = true; } - if (isEmpty(existingImportsOrExports)) { + if (!someProperties(existingImportsOrExports)) { return filter(exportsOfModule, e => e.name !== "default"); } - return filter(exportsOfModule, e => e.name !== "default" && !lookUp(existingImportsOrExports, e.name)); + return filter(exportsOfModule, e => e.name !== "default" && !existingImportsOrExports[e.name]); } /** @@ -4165,7 +4165,7 @@ namespace ts { existingMemberNames[existingName] = true; } - return filter(contextualMemberSymbols, m => !lookUp(existingMemberNames, m.name)); + return filter(contextualMemberSymbols, m => !existingMemberNames[m.name]); } /** @@ -4187,7 +4187,7 @@ namespace ts { } } - return filter(symbols, a => !lookUp(seenNames, a.name)); + return filter(symbols, a => !seenNames[a.name]); } } @@ -4323,7 +4323,7 @@ namespace ts { const entry = createCompletionEntry(symbol, location, performCharacterChecks); if (entry) { const id = escapeIdentifier(entry.name); - if (!lookUp(uniqueNames, id)) { + if (!uniqueNames[id]) { entries.push(entry); uniqueNames[id] = id; } @@ -5151,7 +5151,7 @@ namespace ts { // Type reference directives const typeReferenceDirective = findReferenceInPosition(sourceFile.typeReferenceDirectives, position); if (typeReferenceDirective) { - const referenceFile = lookUp(program.getResolvedTypeReferenceDirectives(), typeReferenceDirective.fileName); + const referenceFile = program.getResolvedTypeReferenceDirectives()[typeReferenceDirective.fileName]; if (referenceFile && referenceFile.resolvedFileName) { return [getDefinitionInfoForFileReference(typeReferenceDirective.fileName, referenceFile.resolvedFileName)]; } @@ -5323,7 +5323,7 @@ namespace ts { for (const referencedSymbol of referencedSymbols) { for (const referenceEntry of referencedSymbol.references) { const fileName = referenceEntry.fileName; - let documentHighlights = getProperty(fileNameToDocumentHighlights, fileName); + let documentHighlights = fileNameToDocumentHighlights[fileName]; if (!documentHighlights) { documentHighlights = { fileName, highlightSpans: [] }; @@ -6068,7 +6068,7 @@ namespace ts { const nameTable = getNameTable(sourceFile); - if (lookUp(nameTable, internedName) !== undefined) { + if (nameTable[internedName] !== undefined) { result = result || []; getReferencesInNode(sourceFile, symbol, declaredName, node, searchMeaning, findInStrings, findInComments, result, symbolToIndex); } diff --git a/src/services/shims.ts b/src/services/shims.ts index 45c4b284ae7..ff57dd9cf7a 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -311,9 +311,9 @@ namespace ts { // 'in' does not have this effect. if ("getModuleResolutionsForFile" in this.shimHost) { this.resolveModuleNames = (moduleNames: string[], containingFile: string) => { - const resolutionsInFile = >JSON.parse(this.shimHost.getModuleResolutionsForFile(containingFile)); + const resolutionsInFile = >JSON.parse(this.shimHost.getModuleResolutionsForFile(containingFile)); return map(moduleNames, name => { - const result = lookUp(resolutionsInFile, name); + const result = getProperty(resolutionsInFile, name); return result ? { resolvedFileName: result } : undefined; }); }; @@ -323,8 +323,8 @@ namespace ts { } if ("getTypeReferenceDirectiveResolutionsForFile" in this.shimHost) { this.resolveTypeReferenceDirectives = (typeDirectiveNames: string[], containingFile: string) => { - const typeDirectivesForFile = >JSON.parse(this.shimHost.getTypeReferenceDirectiveResolutionsForFile(containingFile)); - return map(typeDirectiveNames, name => lookUp(typeDirectivesForFile, name)); + const typeDirectivesForFile = >JSON.parse(this.shimHost.getTypeReferenceDirectiveResolutionsForFile(containingFile)); + return map(typeDirectiveNames, name => getProperty(typeDirectivesForFile, name)); }; } } diff --git a/src/services/signatureHelp.ts b/src/services/signatureHelp.ts index 50378aa64b1..211e55b23ba 100644 --- a/src/services/signatureHelp.ts +++ b/src/services/signatureHelp.ts @@ -237,7 +237,7 @@ namespace ts.SignatureHelp { const typeChecker = program.getTypeChecker(); for (const sourceFile of program.getSourceFiles()) { const nameToDeclarations = sourceFile.getNamedDeclarations(); - const declarations = getProperty(nameToDeclarations, name.text); + const declarations = nameToDeclarations[name.text]; if (declarations) { for (const declaration of declarations) { diff --git a/tslint.json b/tslint.json index a46e58f8c29..f058eab3dd6 100644 --- a/tslint.json +++ b/tslint.json @@ -32,7 +32,7 @@ "property-declaration": "nospace", "variable-declaration": "nospace" }], - "next-line": [true, + "next-line": [true, "check-catch", "check-else" ], @@ -44,7 +44,6 @@ "boolean-trivia": true, "type-operator-spacing": true, "prefer-const": true, - "no-in-operator": true, "no-increment-decrement": true, "object-literal-surrounding-space": true, "no-type-assertion-whitespace": true From 9c83243f33ee7084eaa07b3ee944f9f53559a930 Mon Sep 17 00:00:00 2001 From: Yui Date: Mon, 15 Aug 2016 15:16:54 -0700 Subject: [PATCH 58/72] Add ES2015 Date constructor signature that accepts another Date (#10353) --- src/lib/es2015.core.d.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/es2015.core.d.ts b/src/lib/es2015.core.d.ts index 980f610ca7d..15fbb1b77c5 100644 --- a/src/lib/es2015.core.d.ts +++ b/src/lib/es2015.core.d.ts @@ -68,6 +68,10 @@ interface ArrayConstructor { of(...items: T[]): Array; } +interface DateConstructor { + new (value: Date): Date; +} + interface Function { /** * Returns the name of the function. Function names are read-only and can not be changed. From 8f847c50340e03f39f369e6faace627bd76fd91c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 15 Aug 2016 15:20:58 -0700 Subject: [PATCH 59/72] Parameters with no assignments implicitly considered const --- src/compiler/checker.ts | 80 ++++++++++++++++++++++++++++++----------- src/compiler/types.ts | 20 ++++++----- 2 files changed, 70 insertions(+), 30 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1b7b659e1a6..8a32e23e1ff 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -531,7 +531,7 @@ namespace ts { function getNodeLinks(node: Node): NodeLinks { const nodeId = getNodeId(node); - return nodeLinks[nodeId] || (nodeLinks[nodeId] = {}); + return nodeLinks[nodeId] || (nodeLinks[nodeId] = { flags: 0 }); } function isGlobalSourceFile(node: Node) { @@ -8185,7 +8185,7 @@ namespace ts { return incomplete ? { flags: 0, type } : type; } - function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean, includeOuterFunctions: boolean) { + function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean, flowContainer: Node) { let key: string; if (!reference.flowNode || assumeInitialized && !(declaredType.flags & TypeFlags.Narrowable)) { return declaredType; @@ -8237,7 +8237,7 @@ namespace ts { else if (flow.flags & FlowFlags.Start) { // Check if we should continue with the control flow of the containing function. const container = (flow).container; - if (container && includeOuterFunctions) { + if (container && container !== flowContainer && reference.kind !== SyntaxKind.PropertyAccessExpression) { flow = container.flowNode; continue; } @@ -8708,21 +8708,52 @@ namespace ts { function getControlFlowContainer(node: Node): Node { while (true) { node = node.parent; - if (isFunctionLike(node) || node.kind === SyntaxKind.ModuleBlock || node.kind === SyntaxKind.SourceFile || node.kind === SyntaxKind.PropertyDeclaration) { + if (isFunctionLike(node) && !getImmediatelyInvokedFunctionExpression(node) || + node.kind === SyntaxKind.ModuleBlock || + node.kind === SyntaxKind.SourceFile || + node.kind === SyntaxKind.PropertyDeclaration) { return node; } } } - function isDeclarationIncludedInFlow(reference: Node, declaration: Declaration, includeOuterFunctions: boolean) { - const declarationContainer = getControlFlowContainer(declaration); - let container = getControlFlowContainer(reference); - while (container !== declarationContainer && - (container.kind === SyntaxKind.FunctionExpression || container.kind === SyntaxKind.ArrowFunction) && - (includeOuterFunctions || getImmediatelyInvokedFunctionExpression(container))) { - container = getControlFlowContainer(container); + // Check if a parameter is assigned anywhere within its declaring function. + function isParameterAssigned(symbol: Symbol) { + const func = getRootDeclaration(symbol.valueDeclaration).parent; + const links = getNodeLinks(func); + if (!(links.flags & NodeCheckFlags.AssignmentsMarked)) { + links.flags |= NodeCheckFlags.AssignmentsMarked; + if (!hasParentWithAssignmentsMarked(func)) { + markParameterAssignments(func); + } + } + return symbol.isAssigned || false; + } + + function hasParentWithAssignmentsMarked(node: Node) { + while (true) { + node = node.parent; + if (!node) { + return false; + } + if (isFunctionLike(node) && getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked) { + return true; + } + } + } + + function markParameterAssignments(node: Node) { + if (node.kind === SyntaxKind.Identifier) { + if (isAssignmentTarget(node)) { + const symbol = getResolvedSymbol(node); + if (symbol.valueDeclaration && getRootDeclaration(symbol.valueDeclaration).kind === SyntaxKind.Parameter) { + symbol.isAssigned = true; + } + } + } + else { + forEachChild(node, markParameterAssignments); } - return container === declarationContainer; } function checkIdentifier(node: Identifier): Type { @@ -8777,15 +8808,22 @@ namespace ts { checkNestedBlockScopedBinding(node, symbol); const type = getTypeOfSymbol(localOrExportSymbol); - if (!(localOrExportSymbol.flags & SymbolFlags.Variable) || isAssignmentTarget(node)) { + const declaration = localOrExportSymbol.valueDeclaration; + if (!(localOrExportSymbol.flags & SymbolFlags.Variable) || isAssignmentTarget(node) || !declaration) { return type; } - const declaration = localOrExportSymbol.valueDeclaration; - const includeOuterFunctions = isReadonlySymbol(localOrExportSymbol); - const assumeInitialized = !strictNullChecks || (type.flags & TypeFlags.Any) !== 0 || !declaration || - getRootDeclaration(declaration).kind === SyntaxKind.Parameter || isInAmbientContext(declaration) || - !isDeclarationIncludedInFlow(node, declaration, includeOuterFunctions); - const flowType = getFlowTypeOfReference(node, type, assumeInitialized, includeOuterFunctions); + + const isParameter = getRootDeclaration(declaration).kind === SyntaxKind.Parameter; + const declarationContainer = getControlFlowContainer(declaration); + let flowContainer = getControlFlowContainer(node); + while (flowContainer !== declarationContainer && + (flowContainer.kind === SyntaxKind.FunctionExpression || flowContainer.kind === SyntaxKind.ArrowFunction) && + (isReadonlySymbol(localOrExportSymbol) || isParameter && !isParameterAssigned(localOrExportSymbol))) { + flowContainer = getControlFlowContainer(flowContainer); + } + const assumeInitialized = !strictNullChecks || (type.flags & TypeFlags.Any) !== 0 || isParameter || + flowContainer !== declarationContainer || isInAmbientContext(declaration); + const flowType = getFlowTypeOfReference(node, type, assumeInitialized, flowContainer); if (!assumeInitialized && !(getFalsyFlags(type) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) { error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol)); // Return the declared type to reduce follow-on errors @@ -9038,7 +9076,7 @@ namespace ts { if (isClassLike(container.parent)) { const symbol = getSymbolOfNode(container.parent); const type = container.flags & NodeFlags.Static ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol)).thisType; - return getFlowTypeOfReference(node, type, /*assumeInitialized*/ true, /*includeOuterFunctions*/ true); + return getFlowTypeOfReference(node, type, /*assumeInitialized*/ true, /*flowContainer*/ undefined); } if (isInJavaScriptFile(node)) { @@ -10699,7 +10737,7 @@ namespace ts { !(prop.flags & SymbolFlags.Method && propType.flags & TypeFlags.Union)) { return propType; } - return getFlowTypeOfReference(node, propType, /*assumeInitialized*/ true, /*includeOuterFunctions*/ false); + return getFlowTypeOfReference(node, propType, /*assumeInitialized*/ true, /*flowContainer*/ undefined); } function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index d144c984a3f..cca94c0adc1 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2157,6 +2157,7 @@ namespace ts { /* @internal */ exportSymbol?: Symbol; // Exported symbol associated with this symbol /* @internal */ constEnumOnlyModule?: boolean; // True if module contains only const enums or other modules with only const enums /* @internal */ isReferenced?: boolean; // True if the symbol is referenced elsewhere + /* @internal */ isAssigned?: boolean; // True if the symbol has assignments } /* @internal */ @@ -2209,23 +2210,24 @@ namespace ts { AsyncMethodWithSuper = 0x00000800, // An async method that reads a value from a member of 'super'. AsyncMethodWithSuperBinding = 0x00001000, // An async method that assigns a value to a member of 'super'. CaptureArguments = 0x00002000, // Lexical 'arguments' used in body (for async functions) - EnumValuesComputed = 0x00004000, // Values for enum members have been computed, and any errors have been reported for them. - LexicalModuleMergesWithClass = 0x00008000, // Instantiated lexical module declaration is merged with a previous class declaration. - LoopWithCapturedBlockScopedBinding = 0x00010000, // Loop that contains block scoped variable captured in closure - CapturedBlockScopedBinding = 0x00020000, // Block-scoped binding that is captured in some function - BlockScopedBindingInLoop = 0x00040000, // Block-scoped binding with declaration nested inside iteration statement - ClassWithBodyScopedClassBinding = 0x00080000, // Decorated class that contains a binding to itself inside of the class body. - BodyScopedClassBinding = 0x00100000, // Binding to a decorated class inside of the class's body. - NeedsLoopOutParameter = 0x00200000, // Block scoped binding whose value should be explicitly copied outside of the converted loop + EnumValuesComputed = 0x00004000, // Values for enum members have been computed, and any errors have been reported for them. + LexicalModuleMergesWithClass = 0x00008000, // Instantiated lexical module declaration is merged with a previous class declaration. + LoopWithCapturedBlockScopedBinding = 0x00010000, // Loop that contains block scoped variable captured in closure + CapturedBlockScopedBinding = 0x00020000, // Block-scoped binding that is captured in some function + BlockScopedBindingInLoop = 0x00040000, // Block-scoped binding with declaration nested inside iteration statement + ClassWithBodyScopedClassBinding = 0x00080000, // Decorated class that contains a binding to itself inside of the class body. + BodyScopedClassBinding = 0x00100000, // Binding to a decorated class inside of the class's body. + NeedsLoopOutParameter = 0x00200000, // Block scoped binding whose value should be explicitly copied outside of the converted loop + AssignmentsMarked = 0x00400000, // Parameter assignments have been marked } /* @internal */ export interface NodeLinks { + flags?: NodeCheckFlags; // Set of flags specific to Node resolvedType?: Type; // Cached type of type node resolvedSignature?: Signature; // Cached signature of signature node or call expression resolvedSymbol?: Symbol; // Cached name resolution result resolvedIndexInfo?: IndexInfo; // Cached indexing info resolution result - flags?: NodeCheckFlags; // Set of flags specific to Node enumMemberValue?: number; // Constant value of enum member isVisible?: boolean; // Is this node visible hasReportedStatementInAmbientContext?: boolean; // Cache boolean if we report statements in ambient context From 15dae3fd8a9f76ecec3dde1b1098ca73a3263c92 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 15 Aug 2016 15:21:12 -0700 Subject: [PATCH 60/72] Add tests --- .../implicitConstParameters.errors.txt | 65 +++++++++++ .../reference/implicitConstParameters.js | 106 ++++++++++++++++++ .../cases/compiler/implicitConstParameters.ts | 57 ++++++++++ 3 files changed, 228 insertions(+) create mode 100644 tests/baselines/reference/implicitConstParameters.errors.txt create mode 100644 tests/baselines/reference/implicitConstParameters.js create mode 100644 tests/cases/compiler/implicitConstParameters.ts diff --git a/tests/baselines/reference/implicitConstParameters.errors.txt b/tests/baselines/reference/implicitConstParameters.errors.txt new file mode 100644 index 00000000000..95ff60d71f8 --- /dev/null +++ b/tests/baselines/reference/implicitConstParameters.errors.txt @@ -0,0 +1,65 @@ +tests/cases/compiler/implicitConstParameters.ts(39,27): error TS2532: Object is possibly 'undefined'. +tests/cases/compiler/implicitConstParameters.ts(45,27): error TS2532: Object is possibly 'undefined'. + + +==== tests/cases/compiler/implicitConstParameters.ts (2 errors) ==== + + function doSomething(cb: () => void) { + cb(); + } + + function fn(x: number | string) { + if (typeof x === 'number') { + doSomething(() => x.toFixed()); + } + } + + function f1(x: string | undefined) { + if (!x) { + return; + } + doSomething(() => x.length); + } + + function f2(x: string | undefined) { + if (x) { + doSomething(() => { + doSomething(() => x.length); + }); + } + } + + function f3(x: string | undefined) { + inner(); + function inner() { + if (x) { + doSomething(() => x.length); + } + } + } + + function f4(x: string | undefined) { + x = "abc"; // causes x to be considered non-const + if (x) { + doSomething(() => x.length); + ~ +!!! error TS2532: Object is possibly 'undefined'. + } + } + + function f5(x: string | undefined) { + if (x) { + doSomething(() => x.length); + ~ +!!! error TS2532: Object is possibly 'undefined'. + } + x = "abc"; // causes x to be considered non-const + } + + + function f6(x: string | undefined) { + const y = x || ""; + if (x) { + doSomething(() => y.length); + } + } \ No newline at end of file diff --git a/tests/baselines/reference/implicitConstParameters.js b/tests/baselines/reference/implicitConstParameters.js new file mode 100644 index 00000000000..a5faf5b1253 --- /dev/null +++ b/tests/baselines/reference/implicitConstParameters.js @@ -0,0 +1,106 @@ +//// [implicitConstParameters.ts] + +function doSomething(cb: () => void) { + cb(); +} + +function fn(x: number | string) { + if (typeof x === 'number') { + doSomething(() => x.toFixed()); + } +} + +function f1(x: string | undefined) { + if (!x) { + return; + } + doSomething(() => x.length); +} + +function f2(x: string | undefined) { + if (x) { + doSomething(() => { + doSomething(() => x.length); + }); + } +} + +function f3(x: string | undefined) { + inner(); + function inner() { + if (x) { + doSomething(() => x.length); + } + } +} + +function f4(x: string | undefined) { + x = "abc"; // causes x to be considered non-const + if (x) { + doSomething(() => x.length); + } +} + +function f5(x: string | undefined) { + if (x) { + doSomething(() => x.length); + } + x = "abc"; // causes x to be considered non-const +} + + +function f6(x: string | undefined) { + const y = x || ""; + if (x) { + doSomething(() => y.length); + } +} + +//// [implicitConstParameters.js] +function doSomething(cb) { + cb(); +} +function fn(x) { + if (typeof x === 'number') { + doSomething(function () { return x.toFixed(); }); + } +} +function f1(x) { + if (!x) { + return; + } + doSomething(function () { return x.length; }); +} +function f2(x) { + if (x) { + doSomething(function () { + doSomething(function () { return x.length; }); + }); + } +} +function f3(x) { + inner(); + function inner() { + if (x) { + doSomething(function () { return x.length; }); + } + } +} +function f4(x) { + x = "abc"; // causes x to be considered non-const + if (x) { + doSomething(function () { return x.length; }); + } +} +function f5(x) { + if (x) { + doSomething(function () { return x.length; }); + } + x = "abc"; // causes x to be considered non-const +} +function f6(x) { + var y = x || ""; + if (x) { + doSomething(function () { return y.length; }); + } +} diff --git a/tests/cases/compiler/implicitConstParameters.ts b/tests/cases/compiler/implicitConstParameters.ts new file mode 100644 index 00000000000..97996789124 --- /dev/null +++ b/tests/cases/compiler/implicitConstParameters.ts @@ -0,0 +1,57 @@ +// @strictNullChecks: true + +function doSomething(cb: () => void) { + cb(); +} + +function fn(x: number | string) { + if (typeof x === 'number') { + doSomething(() => x.toFixed()); + } +} + +function f1(x: string | undefined) { + if (!x) { + return; + } + doSomething(() => x.length); +} + +function f2(x: string | undefined) { + if (x) { + doSomething(() => { + doSomething(() => x.length); + }); + } +} + +function f3(x: string | undefined) { + inner(); + function inner() { + if (x) { + doSomething(() => x.length); + } + } +} + +function f4(x: string | undefined) { + x = "abc"; // causes x to be considered non-const + if (x) { + doSomething(() => x.length); + } +} + +function f5(x: string | undefined) { + if (x) { + doSomething(() => x.length); + } + x = "abc"; // causes x to be considered non-const +} + + +function f6(x: string | undefined) { + const y = x || ""; + if (x) { + doSomething(() => y.length); + } +} \ No newline at end of file From 1dc495adf83e143e3d5558157c939e529106beba Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Mon, 15 Aug 2016 16:41:32 -0700 Subject: [PATCH 61/72] Migrate additional MapLikes to Maps. --- src/compiler/checker.ts | 30 ++--- src/compiler/commandLineParser.ts | 32 +++--- src/compiler/core.ts | 108 +++++++++++++++++- src/compiler/emitter.ts | 16 +-- src/compiler/scanner.ts | 10 +- src/compiler/tsc.ts | 15 +-- src/compiler/types.ts | 6 +- src/compiler/utilities.ts | 4 +- src/harness/compilerRunner.ts | 6 +- src/harness/fourslash.ts | 21 ++-- src/harness/harness.ts | 10 +- src/harness/harnessLanguageService.ts | 8 +- src/harness/projectsRunner.ts | 9 +- .../unittests/cachingInServerLSHost.ts | 20 ++-- src/harness/unittests/moduleResolution.ts | 78 ++++++------- .../unittests/reuseProgramStructure.ts | 82 +++++++------ src/harness/unittests/session.ts | 8 +- .../unittests/tsserverProjectSystem.ts | 18 +-- src/server/session.ts | 7 +- src/services/jsTyping.ts | 13 +-- src/services/patternMatcher.ts | 2 +- src/services/services.ts | 8 +- 22 files changed, 295 insertions(+), 216 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6a7cc108625..c309b9e36c0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -284,7 +284,7 @@ namespace ts { NullFacts = TypeofEQObject | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | TypeofNEHostObject | EQNull | EQUndefinedOrNull | NEUndefined | Falsy, } - const typeofEQFacts: MapLike = { + const typeofEQFacts = createMap({ "string": TypeFacts.TypeofEQString, "number": TypeFacts.TypeofEQNumber, "boolean": TypeFacts.TypeofEQBoolean, @@ -292,9 +292,9 @@ namespace ts { "undefined": TypeFacts.EQUndefined, "object": TypeFacts.TypeofEQObject, "function": TypeFacts.TypeofEQFunction - }; + }); - const typeofNEFacts: MapLike = { + const typeofNEFacts = createMap({ "string": TypeFacts.TypeofNEString, "number": TypeFacts.TypeofNENumber, "boolean": TypeFacts.TypeofNEBoolean, @@ -302,15 +302,15 @@ namespace ts { "undefined": TypeFacts.NEUndefined, "object": TypeFacts.TypeofNEObject, "function": TypeFacts.TypeofNEFunction - }; + }); - const typeofTypesByName: MapLike = { + const typeofTypesByName = createMap({ "string": stringType, "number": numberType, "boolean": booleanType, "symbol": esSymbolType, "undefined": undefinedType - }; + }); let jsxElementType: ObjectType; /** Things we lazy load from the JSX namespace */ @@ -8467,11 +8467,11 @@ namespace ts { return type; } const doubleEquals = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken; - const facts = doubleEquals ? - assumeTrue ? TypeFacts.EQUndefinedOrNull : TypeFacts.NEUndefinedOrNull : - value.kind === SyntaxKind.NullKeyword ? - assumeTrue ? TypeFacts.EQNull : TypeFacts.NENull : - assumeTrue ? TypeFacts.EQUndefined : TypeFacts.NEUndefined; + const facts = doubleEquals + ? assumeTrue ? TypeFacts.EQUndefinedOrNull : TypeFacts.NEUndefinedOrNull + : value.kind === SyntaxKind.NullKeyword + ? assumeTrue ? TypeFacts.EQNull : TypeFacts.NENull + : assumeTrue ? TypeFacts.EQUndefined : TypeFacts.NEUndefined; return getTypeWithFacts(type, facts); } if (type.flags & TypeFlags.NotUnionOrUnit) { @@ -8502,14 +8502,14 @@ namespace ts { // We narrow a non-union type to an exact primitive type if the non-union type // is a supertype of that primitive type. For example, type 'any' can be narrowed // to one of the primitive types. - const targetType = getProperty(typeofTypesByName, literal.text); + const targetType = typeofTypesByName[literal.text]; if (targetType && isTypeSubtypeOf(targetType, type)) { return targetType; } } - const facts = assumeTrue ? - getProperty(typeofEQFacts, literal.text) || TypeFacts.TypeofEQHostObject : - getProperty(typeofNEFacts, literal.text) || TypeFacts.TypeofNEHostObject; + const facts = assumeTrue + ? typeofEQFacts[literal.text] || TypeFacts.TypeofEQHostObject + : typeofNEFacts[literal.text] || TypeFacts.TypeofNEHostObject; return getTypeWithFacts(type, facts); } diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 44befb3943d..35140855d81 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -61,10 +61,10 @@ namespace ts { }, { name: "jsx", - type: { + type: createMap({ "preserve": JsxEmit.Preserve, "react": JsxEmit.React - }, + }), paramType: Diagnostics.KIND, description: Diagnostics.Specify_JSX_code_generation_Colon_preserve_or_react, }, @@ -91,7 +91,7 @@ namespace ts { { name: "module", shortName: "m", - type: { + type: createMap({ "none": ModuleKind.None, "commonjs": ModuleKind.CommonJS, "amd": ModuleKind.AMD, @@ -99,16 +99,16 @@ namespace ts { "umd": ModuleKind.UMD, "es6": ModuleKind.ES6, "es2015": ModuleKind.ES2015, - }, + }), description: Diagnostics.Specify_module_code_generation_Colon_commonjs_amd_system_umd_or_es2015, paramType: Diagnostics.KIND, }, { name: "newLine", - type: { + type: createMap({ "crlf": NewLineKind.CarriageReturnLineFeed, "lf": NewLineKind.LineFeed - }, + }), description: Diagnostics.Specify_the_end_of_line_sequence_to_be_used_when_emitting_files_Colon_CRLF_dos_or_LF_unix, paramType: Diagnostics.NEWLINE, }, @@ -250,12 +250,12 @@ namespace ts { { name: "target", shortName: "t", - type: { + type: createMap({ "es3": ScriptTarget.ES3, "es5": ScriptTarget.ES5, "es6": ScriptTarget.ES6, "es2015": ScriptTarget.ES2015, - }, + }), description: Diagnostics.Specify_ECMAScript_target_version_Colon_ES3_default_ES5_or_ES2015, paramType: Diagnostics.VERSION, }, @@ -284,10 +284,10 @@ namespace ts { }, { name: "moduleResolution", - type: { + type: createMap({ "node": ModuleResolutionKind.NodeJs, "classic": ModuleResolutionKind.Classic, - }, + }), description: Diagnostics.Specify_module_resolution_strategy_Colon_node_Node_js_or_classic_TypeScript_pre_1_6, }, { @@ -392,7 +392,7 @@ namespace ts { type: "list", element: { name: "lib", - type: { + type: createMap({ // JavaScript only "es5": "lib.es5.d.ts", "es6": "lib.es2015.d.ts", @@ -417,7 +417,7 @@ namespace ts { "es2016.array.include": "lib.es2016.array.include.d.ts", "es2017.object": "lib.es2017.object.d.ts", "es2017.sharedmemory": "lib.es2017.sharedmemory.d.ts" - }, + }), }, description: Diagnostics.Specify_library_files_to_be_included_in_the_compilation_Colon }, @@ -487,9 +487,7 @@ namespace ts { export function createCompilerDiagnosticForInvalidCustomType(opt: CommandLineOptionOfCustomType): Diagnostic { const namesOfType: string[] = []; for (const key in opt.type) { - if (hasProperty(opt.type, key)) { - namesOfType.push(` '${key}'`); - } + namesOfType.push(` '${key}'`); } return createCompilerDiagnostic(Diagnostics.Argument_for_0_option_must_be_Colon_1, `--${opt.name}`, namesOfType); } @@ -498,7 +496,7 @@ namespace ts { export function parseCustomTypeOption(opt: CommandLineOptionOfCustomType, value: string, errors: Diagnostic[]) { const key = trimString((value || "")).toLowerCase(); const map = opt.type; - if (hasProperty(map, key)) { + if (key in map) { return map[key]; } else { @@ -849,7 +847,7 @@ namespace ts { function convertJsonOptionOfCustomType(opt: CommandLineOptionOfCustomType, value: string, errors: Diagnostic[]) { const key = value.toLowerCase(); - if (hasProperty(opt.type, key)) { + if (key in opt.type) { return opt.type[key]; } else { diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 7bb5dbef2c5..d10d76b3492 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -190,6 +190,21 @@ namespace ts { return array; } + export function removeWhere(array: T[], f: (x: T) => boolean): boolean { + let outIndex = 0; + for (const item of array) { + if (!f(item)) { + array[outIndex] = item; + outIndex++; + } + } + if (outIndex !== array.length) { + array.length = outIndex; + return true; + } + return false; + } + export function filterMutate(array: T[], f: (x: T) => boolean): void { let outIndex = 0; for (const item of array) { @@ -381,6 +396,9 @@ namespace ts { /** * Gets the owned, enumerable property keys of a map-like. * + * NOTE: This is intended for use with MapLike objects. For Map objects, use + * Object.keys instead as it offers better performance. + * * @param map A map-like. */ export function getOwnKeys(map: MapLike): string[] { @@ -425,6 +443,36 @@ namespace ts { return result; } + /** + * Maps key-value pairs of a map into a new map. + * + * NOTE: The key-value pair passed to the callback is *not* safe to cache between invocations + * of the callback. + * + * @param map A map. + * @param callback A callback that maps a key-value pair into a new key-value pair. + */ + export function mapPairs(map: Map, callback: (entry: [string, T]) => [string, U]): Map { + let result: Map; + if (map) { + result = createMap(); + let inPair: [string, T]; + for (const key in map) { + if (inPair) { + inPair[0] = key; + inPair[1] = map[key]; + } + else { + inPair = [key, map[key]]; + } + + const outPair = callback(inPair); + result[outPair[0]] = outPair[1]; + } + } + return result; + } + /** * Returns true if a Map has some matching property. * @@ -504,9 +552,31 @@ namespace ts { return result; } + /** + * Counts the properties of a map. + * + * NOTE: This is intended for use with Map objects. For MapLike objects, use + * countOwnProperties instead as it offers better runtime safety. + * + * @param map A map whose properties should be counted. + * @param predicate An optional callback used to limit which properties should be counted. + */ + export function countProperties(map: Map, predicate?: (value: T, key: string) => boolean) { + let count = 0; + for (const key in map) { + if (!predicate || predicate(map[key], key)) { + count++; + } + } + return count; + } + /** * Counts the owned properties of a map-like. * + * NOTE: This is intended for use with MapLike objects. For Map objects, use + * countProperties instead as it offers better performance. + * * @param map A map-like whose properties should be counted. * @param predicate An optional callback used to limit which properties should be counted. */ @@ -521,16 +591,42 @@ namespace ts { } /** - * Performs a shallow equality comparison of the contents of two map-likes. + * Performs a shallow equality comparison of the contents of two maps. + * + * NOTE: This is intended for use with Map objects. For MapLike objects, use + * equalOwnProperties instead as it offers better runtime safety. * * @param left A map whose properties should be compared. * @param right A map whose properties should be compared. */ - export function equalOwnProperties(left: MapLike, right: MapLike) { + export function equalProperties(left: Map, right: Map, equalityComparer?: (left: T, right: T) => boolean) { + if (left === right) return true; + if (!left || !right) return false; + for (const key in left) { + if (!(key in right)) return false; + if (equalityComparer ? !equalityComparer(left[key], right[key]) : left[key] !== right[key]) return false; + } + for (const key in right) { + if (!(key in left)) return false; + } + return true; + } + + /** + * Performs a shallow equality comparison of the contents of two map-likes. + * + * NOTE: This is intended for use with MapLike objects. For Map objects, use + * equalProperties instead as it offers better performance. + * + * @param left A map-like whose properties should be compared. + * @param right A map-like whose properties should be compared. + */ + export function equalOwnProperties(left: MapLike, right: MapLike, equalityComparer?: (left: T, right: T) => boolean) { if (left === right) return true; if (!left || !right) return false; for (const key in left) if (hasOwnProperty.call(left, key)) { - if (!hasOwnProperty.call(right, key) === undefined || left[key] !== right[key]) return false; + if (!hasOwnProperty.call(right, key) === undefined) return false; + if (equalityComparer ? !equalityComparer(left[key], right[key]) : left[key] !== right[key]) return false; } for (const key in right) if (hasOwnProperty.call(right, key)) { if (!hasOwnProperty.call(left, key)) return false; @@ -577,10 +673,12 @@ namespace ts { export function extend, T2 extends MapLike<{}>>(first: T1 , second: T2): T1 & T2 { const result: T1 & T2 = {}; for (const id in first) { - (result as any)[id] = first[id]; + if (hasOwnProperty.call(first, id)) { + (result as any)[id] = first[id]; + } } for (const id in second) { - if (!hasProperty(result, id)) { + if (hasOwnProperty.call(second, id) && !hasOwnProperty.call(result, id)) { (result as any)[id] = second[id]; } } diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 47495cc69bb..5c834ad0ac2 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -24,7 +24,7 @@ namespace ts { Return = 1 << 3 } - const entities: MapLike = { + const entities = createMap({ "quot": 0x0022, "amp": 0x0026, "apos": 0x0027, @@ -278,7 +278,7 @@ namespace ts { "clubs": 0x2663, "hearts": 0x2665, "diams": 0x2666 - }; + }); // Flags enum to track count of temp variables and a few dedicated names const enum TempFlags { @@ -531,7 +531,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge let currentText: string; let currentLineMap: number[]; let currentFileIdentifiers: Map; - let renamedDependencies: MapLike; + let renamedDependencies: Map; let isEs6Module: boolean; let isCurrentFileExternalModule: boolean; @@ -577,21 +577,21 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge const setSourceMapWriterEmit = compilerOptions.sourceMap || compilerOptions.inlineSourceMap ? changeSourceMapEmit : function (writer: SourceMapWriter) { }; - const moduleEmitDelegates: MapLike<(node: SourceFile, emitRelativePathAsModuleName?: boolean) => void> = { + const moduleEmitDelegates = createMap<(node: SourceFile, emitRelativePathAsModuleName?: boolean) => void>({ [ModuleKind.ES6]: emitES6Module, [ModuleKind.AMD]: emitAMDModule, [ModuleKind.System]: emitSystemModule, [ModuleKind.UMD]: emitUMDModule, [ModuleKind.CommonJS]: emitCommonJSModule, - }; + }); - const bundleEmitDelegates: MapLike<(node: SourceFile, emitRelativePathAsModuleName?: boolean) => void> = { + const bundleEmitDelegates = createMap<(node: SourceFile, emitRelativePathAsModuleName?: boolean) => void>({ [ModuleKind.ES6]() {}, [ModuleKind.AMD]: emitAMDModule, [ModuleKind.System]: emitSystemModule, [ModuleKind.UMD]() {}, [ModuleKind.CommonJS]() {}, - }; + }); return doEmit; @@ -6461,7 +6461,7 @@ const _super = (function (geti, seti) { * Here we check if alternative name was provided for a given moduleName and return it if possible. */ function tryRenameExternalModule(moduleName: LiteralExpression): string { - if (renamedDependencies && hasProperty(renamedDependencies, moduleName.text)) { + if (renamedDependencies && moduleName.text in renamedDependencies) { return `"${renamedDependencies[moduleName.text]}"`; } return undefined; diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 134dc489b2a..c1431ca23ed 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -55,7 +55,7 @@ namespace ts { tryScan(callback: () => T): T; } - const textToToken: MapLike = { + const textToToken = createMap({ "abstract": SyntaxKind.AbstractKeyword, "any": SyntaxKind.AnyKeyword, "as": SyntaxKind.AsKeyword, @@ -179,7 +179,7 @@ namespace ts { "|=": SyntaxKind.BarEqualsToken, "^=": SyntaxKind.CaretEqualsToken, "@": SyntaxKind.AtToken, - }; + }); /* As per ECMAScript Language Specification 3th Edition, Section 7.6: Identifiers @@ -271,12 +271,10 @@ namespace ts { lookupInUnicodeMap(code, unicodeES3IdentifierPart); } - function makeReverseMap(source: MapLike): string[] { + function makeReverseMap(source: Map): string[] { const result: string[] = []; for (const name in source) { - if (source.hasOwnProperty(name)) { - result[source[name]] = name; - } + result[source[name]] = name; } return result; } diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 73cf8e9c4e2..8b445bcb626 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -122,11 +122,11 @@ namespace ts { const gutterSeparator = " "; const resetEscapeSequence = "\u001b[0m"; const ellipsis = "..."; - const categoryFormatMap: MapLike = { + const categoryFormatMap = createMap({ [DiagnosticCategory.Warning]: yellowForegroundEscapeSequence, [DiagnosticCategory.Error]: redForegroundEscapeSequence, [DiagnosticCategory.Message]: blueForegroundEscapeSequence, - }; + }); function formatAndReset(text: string, formatStyle: string) { return formatStyle + text + resetEscapeSequence; @@ -703,11 +703,9 @@ namespace ts { description = getDiagnosticText(option.description); const options: string[] = []; const element = (option).element; - const typeMap = >element.type; + const typeMap = >element.type; for (const key in typeMap) { - if (hasProperty(typeMap, key)) { - options.push(`'${key}'`); - } + options.push(`'${key}'`); } optionsDescriptionMap[description] = options; } @@ -814,9 +812,8 @@ namespace ts { // Enum const typeMap = >optionDefinition.type; for (const key in typeMap) { - if (hasProperty(typeMap, key)) { - if (typeMap[key] === value) - result[name] = key; + if (typeMap[key] === value) { + result[name] = key; } } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index d144c984a3f..83d299583d2 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1650,7 +1650,7 @@ namespace ts { // this map is used by transpiler to supply alternative names for dependencies (i.e. in case of bundling) /* @internal */ - renamedDependencies?: MapLike; + renamedDependencies?: Map; /** * lib.d.ts should have a reference comment like @@ -2742,7 +2742,7 @@ namespace ts { /* @internal */ export interface CommandLineOptionBase { name: string; - type: "string" | "number" | "boolean" | "object" | "list" | MapLike; // a value of a primitive type, or an object literal mapping named values to actual values + type: "string" | "number" | "boolean" | "object" | "list" | Map; // a value of a primitive type, or an object literal mapping named values to actual values isFilePath?: boolean; // True if option value is a path or fileName shortName?: string; // A short mnemonic for convenience - for instance, 'h' can be used in place of 'help' description?: DiagnosticMessage; // The message describing what the command line switch does @@ -2758,7 +2758,7 @@ namespace ts { /* @internal */ export interface CommandLineOptionOfCustomType extends CommandLineOptionBase { - type: MapLike; // an object literal mapping named values to actual values + type: Map; // an object literal mapping named values to actual values } /* @internal */ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 779e479cc43..0a1f43203ce 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2040,7 +2040,7 @@ namespace ts { // the map below must be updated. Note that this regexp *does not* include the 'delete' character. // There is no reason for this other than that JSON.stringify does not handle it either. const escapedCharsRegExp = /[\\\"\u0000-\u001f\t\v\f\b\r\n\u2028\u2029\u0085]/g; - const escapedCharsMap: MapLike = { + const escapedCharsMap = createMap({ "\0": "\\0", "\t": "\\t", "\v": "\\v", @@ -2053,7 +2053,7 @@ namespace ts { "\u2028": "\\u2028", // lineSeparator "\u2029": "\\u2029", // paragraphSeparator "\u0085": "\\u0085" // nextLine - }; + }); /** diff --git a/src/harness/compilerRunner.ts b/src/harness/compilerRunner.ts index fe8986984d3..66396293dc2 100644 --- a/src/harness/compilerRunner.ts +++ b/src/harness/compilerRunner.ts @@ -291,8 +291,8 @@ class CompilerBaselineRunner extends RunnerBase { const fullWalker = new TypeWriterWalker(program, /*fullTypeCheck*/ true); - const fullResults: ts.MapLike = {}; - const pullResults: ts.MapLike = {}; + const fullResults = ts.createMap(); + const pullResults = ts.createMap(); for (const sourceFile of allFiles) { fullResults[sourceFile.unitName] = fullWalker.getTypeAndSymbols(sourceFile.unitName); @@ -338,7 +338,7 @@ class CompilerBaselineRunner extends RunnerBase { } } - function generateBaseLine(typeWriterResults: ts.MapLike, isSymbolBaseline: boolean): string { + function generateBaseLine(typeWriterResults: ts.Map, isSymbolBaseline: boolean): string { const typeLines: string[] = []; const typeMap: { [fileName: string]: { [lineNum: number]: string[]; } } = {}; diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 345ce19f6ae..ba1994c8141 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -95,14 +95,14 @@ namespace FourSlash { export import IndentStyle = ts.IndentStyle; - const entityMap: ts.MapLike = { + const entityMap = ts.createMap({ "&": "&", "\"": """, "'": "'", "/": "/", "<": "<", ">": ">" - }; + }); export function escapeXmlAttributeValue(s: string) { return s.replace(/[&<>"'\/]/g, ch => entityMap[ch]); @@ -593,9 +593,9 @@ namespace FourSlash { public noItemsWithSameNameButDifferentKind(): void { const completions = this.getCompletionListAtCaret(); - const uniqueItems: ts.MapLike = {}; + const uniqueItems = ts.createMap(); for (const item of completions.entries) { - if (!ts.hasProperty(uniqueItems, item.name)) { + if (!(item.name in uniqueItems)) { uniqueItems[item.name] = item.kind; } else { @@ -1638,11 +1638,12 @@ namespace FourSlash { return this.testData.ranges; } - public rangesByText(): ts.MapLike { - const result: ts.MapLike = {}; + public rangesByText(): ts.Map { + const result = ts.createMap(); for (const range of this.getRanges()) { const text = this.rangeText(range); - (ts.getProperty(result, text) || (result[text] = [])).push(range); + const ranges = result[text] || (result[text] = []); + ranges.push(range); } return result; } @@ -1897,7 +1898,7 @@ namespace FourSlash { public verifyBraceCompletionAtPosition(negative: boolean, openingBrace: string) { - const openBraceMap: ts.MapLike = { + const openBraceMap = ts.createMap({ "(": ts.CharacterCodes.openParen, "{": ts.CharacterCodes.openBrace, "[": ts.CharacterCodes.openBracket, @@ -1905,7 +1906,7 @@ namespace FourSlash { '"': ts.CharacterCodes.doubleQuote, "`": ts.CharacterCodes.backtick, "<": ts.CharacterCodes.lessThan - }; + }); const charCode = openBraceMap[openingBrace]; @@ -2773,7 +2774,7 @@ namespace FourSlashInterface { return this.state.getRanges(); } - public rangesByText(): ts.MapLike { + public rangesByText(): ts.Map { return this.state.rangesByText(); } diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 1f7c48d21bc..e3bcb001863 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -848,9 +848,9 @@ namespace Harness { export const defaultLibFileName = "lib.d.ts"; export const es2015DefaultLibFileName = "lib.es2015.d.ts"; - const libFileNameSourceFileMap: ts.MapLike = { + const libFileNameSourceFileMap= ts.createMap({ [defaultLibFileName]: createSourceFileAndAssertInvariants(defaultLibFileName, IO.readFile(libFolder + "lib.es5.d.ts"), /*languageVersion*/ ts.ScriptTarget.Latest) - }; + }); export function getDefaultLibrarySourceFile(fileName = defaultLibFileName): ts.SourceFile { if (!isDefaultLibraryFile(fileName)) { @@ -1002,16 +1002,16 @@ namespace Harness { { name: "symlink", type: "string" } ]; - let optionsIndex: ts.MapLike; + let optionsIndex: ts.Map; function getCommandLineOption(name: string): ts.CommandLineOption { if (!optionsIndex) { - optionsIndex = {}; + optionsIndex = ts.createMap(); const optionDeclarations = harnessOptionDeclarations.concat(ts.optionDeclarations); for (const option of optionDeclarations) { optionsIndex[option.name.toLowerCase()] = option; } } - return ts.getProperty(optionsIndex, name.toLowerCase()); + return optionsIndex[name.toLowerCase()]; } export function setCompilerOptionsFromHarnessSetting(settings: Harness.TestCaseParser.CompilerSettings, options: ts.CompilerOptions & HarnessOptions): void { diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index b03d8788165..b37a6a90899 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -123,7 +123,7 @@ namespace Harness.LanguageService { } export class LanguageServiceAdapterHost { - protected fileNameToScript: ts.MapLike = {}; + protected fileNameToScript = ts.createMap(); constructor(protected cancellationToken = DefaultHostCancellationToken.Instance, protected settings = ts.getDefaultCompilerOptions()) { @@ -146,7 +146,7 @@ namespace Harness.LanguageService { } public getScriptInfo(fileName: string): ScriptInfo { - return ts.getProperty(this.fileNameToScript, fileName); + return this.fileNameToScript[fileName]; } public addScript(fileName: string, content: string, isRootFile: boolean): void { @@ -235,7 +235,7 @@ namespace Harness.LanguageService { this.getModuleResolutionsForFile = (fileName) => { const scriptInfo = this.getScriptInfo(fileName); const preprocessInfo = ts.preProcessFile(scriptInfo.content, /*readImportFiles*/ true); - const imports: ts.MapLike = {}; + const imports = ts.createMap(); for (const module of preprocessInfo.importedFiles) { const resolutionInfo = ts.resolveModuleName(module.fileName, fileName, compilerOptions, moduleResolutionHost); if (resolutionInfo.resolvedModule) { @@ -248,7 +248,7 @@ namespace Harness.LanguageService { const scriptInfo = this.getScriptInfo(fileName); if (scriptInfo) { const preprocessInfo = ts.preProcessFile(scriptInfo.content, /*readImportFiles*/ false); - const resolutions: ts.MapLike = {}; + const resolutions = ts.createMap(); const settings = this.nativeHost.getCompilationSettings(); for (const typeReferenceDirective of preprocessInfo.typeReferenceDirectives) { const resolutionInfo = ts.resolveTypeReferenceDirective(typeReferenceDirective.fileName, fileName, settings, moduleResolutionHost); diff --git a/src/harness/projectsRunner.ts b/src/harness/projectsRunner.ts index be3030a6dd6..76f042834bb 100644 --- a/src/harness/projectsRunner.ts +++ b/src/harness/projectsRunner.ts @@ -253,18 +253,15 @@ class ProjectRunner extends RunnerBase { moduleResolution: ts.ModuleResolutionKind.Classic, // currently all tests use classic module resolution kind, this will change in the future }; // Set the values specified using json - const optionNameMap: ts.MapLike = {}; - ts.forEach(ts.optionDeclarations, option => { - optionNameMap[option.name] = option; - }); + const optionNameMap = ts.arrayToMap(ts.optionDeclarations, option => option.name); for (const name in testCase) { - if (name !== "mapRoot" && name !== "sourceRoot" && ts.hasProperty(optionNameMap, name)) { + if (name !== "mapRoot" && name !== "sourceRoot" && name in optionNameMap) { const option = optionNameMap[name]; const optType = option.type; let value = testCase[name]; if (typeof optType !== "string") { const key = value.toLowerCase(); - if (ts.hasProperty(optType, key)) { + if (key in optType) { value = optType[key]; } } diff --git a/src/harness/unittests/cachingInServerLSHost.ts b/src/harness/unittests/cachingInServerLSHost.ts index 61ab38b2d8f..2885b310605 100644 --- a/src/harness/unittests/cachingInServerLSHost.ts +++ b/src/harness/unittests/cachingInServerLSHost.ts @@ -6,17 +6,17 @@ namespace ts { content: string; } - function createDefaultServerHost(fileMap: MapLike): server.ServerHost { - const existingDirectories: MapLike = {}; - forEachOwnProperty(fileMap, v => { - let dir = getDirectoryPath(v.name); + function createDefaultServerHost(fileMap: Map): server.ServerHost { + const existingDirectories = createMap(); + for (const name in fileMap) { + let dir = getDirectoryPath(name); let previous: string; do { existingDirectories[dir] = true; previous = dir; dir = getDirectoryPath(dir); } while (dir !== previous); - }); + } return { args: [], newLine: "\r\n", @@ -24,7 +24,7 @@ namespace ts { write: (s: string) => { }, readFile: (path: string, encoding?: string): string => { - return hasProperty(fileMap, path) && fileMap[path].content; + return path in fileMap ? fileMap[path].content : undefined; }, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => { throw new Error("NYI"); @@ -33,10 +33,10 @@ namespace ts { throw new Error("NYI"); }, fileExists: (path: string): boolean => { - return hasProperty(fileMap, path); + return path in fileMap; }, directoryExists: (path: string): boolean => { - return hasProperty(existingDirectories, path); + return existingDirectories[path] || false; }, createDirectory: (path: string) => { }, @@ -101,7 +101,7 @@ namespace ts { content: `foo()` }; - const serverHost = createDefaultServerHost({ [root.name]: root, [imported.name]: imported }); + const serverHost = createDefaultServerHost(createMap({ [root.name]: root, [imported.name]: imported })); const { project, rootScriptInfo } = createProject(root.name, serverHost); // ensure that imported file was found @@ -193,7 +193,7 @@ namespace ts { content: `export var y = 1` }; - const fileMap: MapLike = { [root.name]: root }; + const fileMap = createMap({ [root.name]: root }); const serverHost = createDefaultServerHost(fileMap); const originalFileExists = serverHost.fileExists; diff --git a/src/harness/unittests/moduleResolution.ts b/src/harness/unittests/moduleResolution.ts index 4b5ef9f24f1..ff0151c40a5 100644 --- a/src/harness/unittests/moduleResolution.ts +++ b/src/harness/unittests/moduleResolution.ts @@ -10,7 +10,7 @@ namespace ts { const map = arrayToMap(files, f => f.name); if (hasDirectoryExists) { - const directories: MapLike = {}; + const directories = createMap(); for (const f of files) { let name = getDirectoryPath(f.name); while (true) { @@ -25,19 +25,19 @@ namespace ts { return { readFile, directoryExists: path => { - return hasProperty(directories, path); + return path in directories; }, fileExists: path => { - assert.isTrue(hasProperty(directories, getDirectoryPath(path)), `'fileExists' '${path}' request in non-existing directory`); - return hasProperty(map, path); + assert.isTrue(getDirectoryPath(path) in directories, `'fileExists' '${path}' request in non-existing directory`); + return path in map; } }; } else { - return { readFile, fileExists: path => hasProperty(map, path), }; + return { readFile, fileExists: path => path in map, }; } function readFile(path: string): string { - return hasProperty(map, path) ? map[path].content : undefined; + return path in map ? map[path].content : undefined; } } @@ -282,12 +282,12 @@ namespace ts { }); describe("Module resolution - relative imports", () => { - function test(files: MapLike, currentDirectory: string, rootFiles: string[], expectedFilesCount: number, relativeNamesToCheck: string[]) { + function test(files: Map, currentDirectory: string, rootFiles: string[], expectedFilesCount: number, relativeNamesToCheck: string[]) { const options: CompilerOptions = { module: ModuleKind.CommonJS }; const host: CompilerHost = { getSourceFile: (fileName: string, languageVersion: ScriptTarget) => { const path = normalizePath(combinePaths(currentDirectory, fileName)); - return hasProperty(files, path) ? createSourceFile(fileName, files[path], languageVersion) : undefined; + return path in files ? createSourceFile(fileName, files[path], languageVersion) : undefined; }, getDefaultLibFileName: () => "lib.d.ts", writeFile: (fileName, content): void => { throw new Error("NotImplemented"); }, @@ -298,7 +298,7 @@ namespace ts { useCaseSensitiveFileNames: () => false, fileExists: fileName => { const path = normalizePath(combinePaths(currentDirectory, fileName)); - return hasProperty(files, path); + return path in files; }, readFile: (fileName): string => { throw new Error("NotImplemented"); } }; @@ -318,7 +318,7 @@ namespace ts { } it("should find all modules", () => { - const files: MapLike = { + const files = createMap({ "/a/b/c/first/shared.ts": ` class A {} export = A`, @@ -332,37 +332,33 @@ import Shared = require('../first/shared'); class C {} export = C; ` - }; + }); test(files, "/a/b/c/first/second", ["class_a.ts"], 3, ["../../../c/third/class_c.ts"]); }); it("should find modules in node_modules", () => { - const files: MapLike = { + const files = createMap({ "/parent/node_modules/mod/index.d.ts": "export var x", "/parent/app/myapp.ts": `import {x} from "mod"` - }; + }); test(files, "/parent/app", ["myapp.ts"], 2, []); }); it("should find file referenced via absolute and relative names", () => { - const files: MapLike = { + const files = createMap({ "/a/b/c.ts": `/// `, "/a/b/b.ts": "var x" - }; + }); test(files, "/a/b", ["c.ts", "/a/b/b.ts"], 2, []); }); }); describe("Files with different casing", () => { const library = createSourceFile("lib.d.ts", "", ScriptTarget.ES5); - function test(files: MapLike, options: CompilerOptions, currentDirectory: string, useCaseSensitiveFileNames: boolean, rootFiles: string[], diagnosticCodes: number[]): void { + function test(files: Map, options: CompilerOptions, currentDirectory: string, useCaseSensitiveFileNames: boolean, rootFiles: string[], diagnosticCodes: number[]): void { const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); if (!useCaseSensitiveFileNames) { - const f: MapLike = {}; - for (const fileName in files) { - f[getCanonicalFileName(fileName)] = files[fileName]; - } - files = f; + files = mapPairs(files, ([fileName, file]) => [getCanonicalFileName(fileName), file]); } const host: CompilerHost = { @@ -371,7 +367,7 @@ export = C; return library; } const path = getCanonicalFileName(normalizePath(combinePaths(currentDirectory, fileName))); - return hasProperty(files, path) ? createSourceFile(fileName, files[path], languageVersion) : undefined; + return path in files ? createSourceFile(fileName, files[path], languageVersion) : undefined; }, getDefaultLibFileName: () => "lib.d.ts", writeFile: (fileName, content): void => { throw new Error("NotImplemented"); }, @@ -382,7 +378,7 @@ export = C; useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, fileExists: fileName => { const path = getCanonicalFileName(normalizePath(combinePaths(currentDirectory, fileName))); - return hasProperty(files, path); + return path in files; }, readFile: (fileName): string => { throw new Error("NotImplemented"); } }; @@ -395,57 +391,57 @@ export = C; } it("should succeed when the same file is referenced using absolute and relative names", () => { - const files: MapLike = { + const files = createMap({ "/a/b/c.ts": `/// `, "/a/b/d.ts": "var x" - }; + }); test(files, { module: ts.ModuleKind.AMD }, "/a/b", /*useCaseSensitiveFileNames*/ false, ["c.ts", "/a/b/d.ts"], []); }); it("should fail when two files used in program differ only in casing (tripleslash references)", () => { - const files: MapLike = { + const files = createMap({ "/a/b/c.ts": `/// `, "/a/b/d.ts": "var x" - }; + }); test(files, { module: ts.ModuleKind.AMD, forceConsistentCasingInFileNames: true }, "/a/b", /*useCaseSensitiveFileNames*/ false, ["c.ts", "d.ts"], [1149]); }); it("should fail when two files used in program differ only in casing (imports)", () => { - const files: MapLike = { + const files = createMap({ "/a/b/c.ts": `import {x} from "D"`, "/a/b/d.ts": "export var x" - }; + }); test(files, { module: ts.ModuleKind.AMD, forceConsistentCasingInFileNames: true }, "/a/b", /*useCaseSensitiveFileNames*/ false, ["c.ts", "d.ts"], [1149]); }); it("should fail when two files used in program differ only in casing (imports, relative module names)", () => { - const files: MapLike = { + const files = createMap({ "moduleA.ts": `import {x} from "./ModuleB"`, "moduleB.ts": "export var x" - }; + }); test(files, { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, "", /*useCaseSensitiveFileNames*/ false, ["moduleA.ts", "moduleB.ts"], [1149]); }); it("should fail when two files exist on disk that differs only in casing", () => { - const files: MapLike = { + const files = createMap({ "/a/b/c.ts": `import {x} from "D"`, "/a/b/D.ts": "export var x", "/a/b/d.ts": "export var y" - }; + }); test(files, { module: ts.ModuleKind.AMD }, "/a/b", /*useCaseSensitiveFileNames*/ true, ["c.ts", "d.ts"], [1149]); }); it("should fail when module name in 'require' calls has inconsistent casing", () => { - const files: MapLike = { + const files = createMap({ "moduleA.ts": `import a = require("./ModuleC")`, "moduleB.ts": `import a = require("./moduleC")`, "moduleC.ts": "export var x" - }; + }); test(files, { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, "", /*useCaseSensitiveFileNames*/ false, ["moduleA.ts", "moduleB.ts", "moduleC.ts"], [1149, 1149]); }); it("should fail when module names in 'require' calls has inconsistent casing and current directory has uppercase chars", () => { - const files: MapLike = { + const files = createMap({ "/a/B/c/moduleA.ts": `import a = require("./ModuleC")`, "/a/B/c/moduleB.ts": `import a = require("./moduleC")`, "/a/B/c/moduleC.ts": "export var x", @@ -453,11 +449,11 @@ export = C; import a = require("./moduleA.ts"); import b = require("./moduleB.ts"); ` - }; + }); test(files, { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, "/a/B/c", /*useCaseSensitiveFileNames*/ false, ["moduleD.ts"], [1149]); }); it("should not fail when module names in 'require' calls has consistent casing and current directory has uppercase chars", () => { - const files: MapLike = { + const files = createMap({ "/a/B/c/moduleA.ts": `import a = require("./moduleC")`, "/a/B/c/moduleB.ts": `import a = require("./moduleC")`, "/a/B/c/moduleC.ts": "export var x", @@ -465,7 +461,7 @@ import b = require("./moduleB.ts"); import a = require("./moduleA.ts"); import b = require("./moduleB.ts"); ` - }; + }); test(files, { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, "/a/B/c", /*useCaseSensitiveFileNames*/ false, ["moduleD.ts"], []); }); }); @@ -1023,7 +1019,7 @@ import b = require("./moduleB.ts"); const names = map(files, f => f.name); const sourceFiles = arrayToMap(map(files, f => createSourceFile(f.name, f.content, ScriptTarget.ES6)), f => f.fileName); const compilerHost: CompilerHost = { - fileExists : fileName => hasProperty(sourceFiles, fileName), + fileExists : fileName => fileName in sourceFiles, getSourceFile: fileName => sourceFiles[fileName], getDefaultLibFileName: () => "lib.d.ts", writeFile(file, text) { @@ -1034,7 +1030,7 @@ import b = require("./moduleB.ts"); getCanonicalFileName: f => f.toLowerCase(), getNewLine: () => "\r\n", useCaseSensitiveFileNames: () => false, - readFile: fileName => hasProperty(sourceFiles, fileName) ? sourceFiles[fileName].text : undefined + readFile: fileName => fileName in sourceFiles ? sourceFiles[fileName].text : undefined }; const program1 = createProgram(names, {}, compilerHost); const diagnostics1 = program1.getFileProcessingDiagnostics().getDiagnostics(); diff --git a/src/harness/unittests/reuseProgramStructure.ts b/src/harness/unittests/reuseProgramStructure.ts index 9d8b176a6a8..9f2b0c343c2 100644 --- a/src/harness/unittests/reuseProgramStructure.ts +++ b/src/harness/unittests/reuseProgramStructure.ts @@ -95,13 +95,14 @@ namespace ts { } } + function createSourceFileWithText(fileName: string, sourceText: SourceText, target: ScriptTarget) { + const file = createSourceFile(fileName, sourceText.getFullText(), target); + file.sourceText = sourceText; + return file; + } + function createTestCompilerHost(texts: NamedSourceText[], target: ScriptTarget): CompilerHost { - const files: MapLike = {}; - for (const t of texts) { - const file = createSourceFile(t.name, t.text.getFullText(), target); - file.sourceText = t.text; - files[t.name] = file; - } + const files = arrayToMap(texts, t => t.name, t => createSourceFileWithText(t.name, t.text, target)); return { getSourceFile(fileName): SourceFile { @@ -128,10 +129,9 @@ namespace ts { getNewLine(): string { return sys ? sys.newLine : newLine; }, - fileExists: fileName => hasProperty(files, fileName), + fileExists: fileName => fileName in files, readFile: fileName => { - const file = getProperty(files, fileName); - return file && file.text; + return fileName in files ? files[fileName].text : undefined; } }; } @@ -152,19 +152,29 @@ namespace ts { return program; } - function checkResolvedModule(expected: ResolvedModule, actual: ResolvedModule): void { - assert.isTrue(actual !== undefined); - assert.isTrue(expected.resolvedFileName === actual.resolvedFileName, `'resolvedFileName': expected '${expected.resolvedFileName}' to be equal to '${actual.resolvedFileName}'`); - assert.isTrue(expected.isExternalLibraryImport === actual.isExternalLibraryImport, `'isExternalLibraryImport': expected '${expected.isExternalLibraryImport}' to be equal to '${actual.isExternalLibraryImport}'`); + function checkResolvedModule(expected: ResolvedModule, actual: ResolvedModule): boolean { + if (!expected === !actual) { + if (expected) { + assert.isTrue(expected.resolvedFileName === actual.resolvedFileName, `'resolvedFileName': expected '${expected.resolvedFileName}' to be equal to '${actual.resolvedFileName}'`); + assert.isTrue(expected.isExternalLibraryImport === actual.isExternalLibraryImport, `'isExternalLibraryImport': expected '${expected.isExternalLibraryImport}' to be equal to '${actual.isExternalLibraryImport}'`); + } + return true; + } + return false; } - function checkResolvedTypeDirective(expected: ResolvedTypeReferenceDirective, actual: ResolvedTypeReferenceDirective): void { - assert.isTrue(actual !== undefined); - assert.isTrue(expected.resolvedFileName === actual.resolvedFileName, `'resolvedFileName': expected '${expected.resolvedFileName}' to be equal to '${actual.resolvedFileName}'`); - assert.isTrue(expected.primary === actual.primary, `'primary': expected '${expected.primary}' to be equal to '${actual.primary}'`); + function checkResolvedTypeDirective(expected: ResolvedTypeReferenceDirective, actual: ResolvedTypeReferenceDirective): boolean { + if (!expected === !actual) { + if (expected) { + assert.isTrue(expected.resolvedFileName === actual.resolvedFileName, `'resolvedFileName': expected '${expected.resolvedFileName}' to be equal to '${actual.resolvedFileName}'`); + assert.isTrue(expected.primary === actual.primary, `'primary': expected '${expected.primary}' to be equal to '${actual.primary}'`); + } + return true; + } + return false; } - function checkCache(caption: string, program: Program, fileName: string, expectedContent: MapLike, getCache: (f: SourceFile) => MapLike, entryChecker: (expected: T, original: T) => void): void { + function checkCache(caption: string, program: Program, fileName: string, expectedContent: Map, getCache: (f: SourceFile) => Map, entryChecker: (expected: T, original: T) => boolean): void { const file = program.getSourceFile(fileName); assert.isTrue(file !== undefined, `cannot find file ${fileName}`); const cache = getCache(file); @@ -173,31 +183,15 @@ namespace ts { } else { assert.isTrue(cache !== undefined, `expected ${caption} to be set`); - const actualCacheSize = countOwnProperties(cache); - const expectedSize = countOwnProperties(expectedContent); - assert.isTrue(actualCacheSize === expectedSize, `expected actual size: ${actualCacheSize} to be equal to ${expectedSize}`); - - for (const id in expectedContent) { - if (hasProperty(expectedContent, id)) { - - if (expectedContent[id]) { - const expected = expectedContent[id]; - const actual = cache[id]; - entryChecker(expected, actual); - } - } - else { - assert.isTrue(cache[id] === undefined); - } - } + assert.isTrue(equalProperties(expectedContent, cache, entryChecker), `contents of ${caption} did not match the expected contents.`); } } - function checkResolvedModulesCache(program: Program, fileName: string, expectedContent: MapLike): void { + function checkResolvedModulesCache(program: Program, fileName: string, expectedContent: Map): void { checkCache("resolved modules", program, fileName, expectedContent, f => f.resolvedModules, checkResolvedModule); } - function checkResolvedTypeDirectivesCache(program: Program, fileName: string, expectedContent: MapLike): void { + function checkResolvedTypeDirectivesCache(program: Program, fileName: string, expectedContent: Map): void { checkCache("resolved type directives", program, fileName, expectedContent, f => f.resolvedTypeReferenceDirectiveNames, checkResolvedTypeDirective); } @@ -303,6 +297,8 @@ namespace ts { }); it("resolution cache follows imports", () => { + (Error).stackTraceLimit = Infinity; + const files = [ { name: "a.ts", text: SourceText.New("", "import {_} from 'b'", "var x = 1") }, { name: "b.ts", text: SourceText.New("", "", "var y = 2") }, @@ -310,7 +306,7 @@ namespace ts { const options: CompilerOptions = { target }; const program_1 = newProgram(files, ["a.ts"], options); - checkResolvedModulesCache(program_1, "a.ts", { "b": { resolvedFileName: "b.ts" } }); + checkResolvedModulesCache(program_1, "a.ts", createMap({ "b": { resolvedFileName: "b.ts" } })); checkResolvedModulesCache(program_1, "b.ts", undefined); const program_2 = updateProgram(program_1, ["a.ts"], options, files => { @@ -319,7 +315,7 @@ namespace ts { assert.isTrue(program_1.structureIsReused); // content of resolution cache should not change - checkResolvedModulesCache(program_1, "a.ts", { "b": { resolvedFileName: "b.ts" } }); + checkResolvedModulesCache(program_1, "a.ts", createMap({ "b": { resolvedFileName: "b.ts" } })); checkResolvedModulesCache(program_1, "b.ts", undefined); // imports has changed - program is not reused @@ -336,7 +332,7 @@ namespace ts { files[0].text = files[0].text.updateImportsAndExports(newImports); }); assert.isTrue(!program_3.structureIsReused); - checkResolvedModulesCache(program_4, "a.ts", { "b": { resolvedFileName: "b.ts" }, "c": undefined }); + checkResolvedModulesCache(program_4, "a.ts", createMap({ "b": { resolvedFileName: "b.ts" }, "c": undefined })); }); it("resolved type directives cache follows type directives", () => { @@ -347,7 +343,7 @@ namespace ts { const options: CompilerOptions = { target, typeRoots: ["/types"] }; const program_1 = newProgram(files, ["/a.ts"], options); - checkResolvedTypeDirectivesCache(program_1, "/a.ts", { "typedefs": { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }); + checkResolvedTypeDirectivesCache(program_1, "/a.ts", createMap({ "typedefs": { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } })); checkResolvedTypeDirectivesCache(program_1, "/types/typedefs/index.d.ts", undefined); const program_2 = updateProgram(program_1, ["/a.ts"], options, files => { @@ -356,7 +352,7 @@ namespace ts { assert.isTrue(program_1.structureIsReused); // content of resolution cache should not change - checkResolvedTypeDirectivesCache(program_1, "/a.ts", { "typedefs": { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }); + checkResolvedTypeDirectivesCache(program_1, "/a.ts", createMap({ "typedefs": { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } })); checkResolvedTypeDirectivesCache(program_1, "/types/typedefs/index.d.ts", undefined); // type reference directives has changed - program is not reused @@ -374,7 +370,7 @@ namespace ts { files[0].text = files[0].text.updateReferences(newReferences); }); assert.isTrue(!program_3.structureIsReused); - checkResolvedTypeDirectivesCache(program_1, "/a.ts", { "typedefs": { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }); + checkResolvedTypeDirectivesCache(program_1, "/a.ts", createMap({ "typedefs": { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } })); }); }); diff --git a/src/harness/unittests/session.ts b/src/harness/unittests/session.ts index e8e06da7bad..cf7486acb40 100644 --- a/src/harness/unittests/session.ts +++ b/src/harness/unittests/session.ts @@ -362,13 +362,13 @@ namespace ts.server { class InProcClient { private server: InProcSession; private seq = 0; - private callbacks: ts.MapLike<(resp: protocol.Response) => void> = {}; - private eventHandlers: ts.MapLike<(args: any) => void> = {}; + private callbacks = createMap<(resp: protocol.Response) => void>(); + private eventHandlers = createMap<(args: any) => void>(); handle(msg: protocol.Message): void { if (msg.type === "response") { const response = msg; - if (this.callbacks[response.request_seq]) { + if (response.request_seq in this.callbacks) { this.callbacks[response.request_seq](response); delete this.callbacks[response.request_seq]; } @@ -380,7 +380,7 @@ namespace ts.server { } emit(name: string, args: any): void { - if (this.eventHandlers[name]) { + if (name in this.eventHandlers) { this.eventHandlers[name](args); } } diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 1e76a636c9a..9887573436e 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -68,10 +68,10 @@ namespace ts { return entry; } - function checkMapKeys(caption: string, map: MapLike, expectedKeys: string[]) { - assert.equal(countOwnProperties(map), expectedKeys.length, `${caption}: incorrect size of map`); + function checkMapKeys(caption: string, map: Map, expectedKeys: string[]) { + assert.equal(countProperties(map), expectedKeys.length, `${caption}: incorrect size of map`); for (const name of expectedKeys) { - assert.isTrue(hasProperty(map, name), `${caption} is expected to contain ${name}, actual keys: ${getOwnKeys(map)}`); + assert.isTrue(name in map, `${caption} is expected to contain ${name}, actual keys: ${Object.keys(map)}`); } } @@ -116,8 +116,8 @@ namespace ts { private getCanonicalFileName: (s: string) => string; private toPath: (f: string) => Path; private callbackQueue: TimeOutCallback[] = []; - readonly watchedDirectories: MapLike<{ cb: DirectoryWatcherCallback, recursive: boolean }[]> = {}; - readonly watchedFiles: MapLike = {}; + readonly watchedDirectories = createMap<{ cb: DirectoryWatcherCallback, recursive: boolean }[]>(); + readonly watchedFiles = createMap(); constructor(public useCaseSensitiveFileNames: boolean, private executingFilePath: string, private currentDirectory: string, fileOrFolderList: FileOrFolder[]) { this.getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); @@ -198,7 +198,7 @@ namespace ts { watchDirectory(directoryName: string, callback: DirectoryWatcherCallback, recursive: boolean): DirectoryWatcher { const path = this.toPath(directoryName); - const callbacks = getProperty(this.watchedDirectories, path) || (this.watchedDirectories[path] = []); + const callbacks = this.watchedDirectories[path] || (this.watchedDirectories[path] = []); callbacks.push({ cb: callback, recursive }); return { referenceCount: 0, @@ -219,7 +219,7 @@ namespace ts { triggerDirectoryWatcherCallback(directoryName: string, fileName: string): void { const path = this.toPath(directoryName); - const callbacks = getProperty(this.watchedDirectories, path); + const callbacks = this.watchedDirectories[path]; if (callbacks) { for (const callback of callbacks) { callback.cb(fileName); @@ -229,7 +229,7 @@ namespace ts { triggerFileWatcherCallback(fileName: string, removed?: boolean): void { const path = this.toPath(fileName); - const callbacks = getProperty(this.watchedFiles, path); + const callbacks = this.watchedFiles[path]; if (callbacks) { for (const callback of callbacks) { callback(path, removed); @@ -239,7 +239,7 @@ namespace ts { watchFile(fileName: string, callback: FileWatcherCallback) { const path = this.toPath(fileName); - const callbacks = getProperty(this.watchedFiles, path) || (this.watchedFiles[path] = []); + const callbacks = this.watchedFiles[path] || (this.watchedFiles[path] = []); callbacks.push(callback); return { close: () => { diff --git a/src/server/session.ts b/src/server/session.ts index 13b0e6d6ce4..9383a54bd48 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1061,7 +1061,7 @@ namespace ts.server { return { response, responseRequired: true }; } - private handlers: MapLike<(request: protocol.Request) => { response?: any, responseRequired?: boolean }> = { + private handlers = createMap<(request: protocol.Request) => { response?: any, responseRequired?: boolean }>({ [CommandNames.Exit]: () => { this.exit(); return { responseRequired: false }; @@ -1198,9 +1198,10 @@ namespace ts.server { this.reloadProjects(); return { responseRequired: false }; } - }; + }); + public addProtocolHandler(command: string, handler: (request: protocol.Request) => { response?: any, responseRequired: boolean }) { - if (this.handlers[command]) { + if (command in this.handlers) { throw new Error(`Protocol handler already exists for command "${command}"`); } this.handlers[command] = handler; diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts index bac33a6bdfb..4f0e06244d2 100644 --- a/src/services/jsTyping.ts +++ b/src/services/jsTyping.ts @@ -58,12 +58,7 @@ namespace ts.JsTyping { if (!safeList) { const result = readConfigFile(safeListPath, (path: string) => host.readFile(path)); - if (result.config) { - safeList = result.config; - } - else { - safeList = createMap(); - }; + safeList = createMap(result.config); } const filesToWatch: string[] = []; @@ -93,7 +88,7 @@ namespace ts.JsTyping { // Add the cached typing locations for inferred typings that are already installed for (const name in packageNameToTypingLocation) { - if (hasProperty(inferredTypings, name) && !inferredTypings[name]) { + if (name in inferredTypings && !inferredTypings[name]) { inferredTypings[name] = packageNameToTypingLocation[name]; } } @@ -124,7 +119,7 @@ namespace ts.JsTyping { } for (const typing of typingNames) { - if (!hasProperty(inferredTypings, typing)) { + if (!(typing in inferredTypings)) { inferredTypings[typing] = undefined; } } @@ -167,7 +162,7 @@ namespace ts.JsTyping { mergeTypings(cleanedTypingNames); } else { - mergeTypings(filter(cleanedTypingNames, f => hasProperty(safeList, f))); + mergeTypings(filter(cleanedTypingNames, f => f in safeList)); } const hasJsxFile = forEach(fileNames, f => scriptKindIs(f, /*LanguageServiceHost*/ undefined, ScriptKind.JSX)); diff --git a/src/services/patternMatcher.ts b/src/services/patternMatcher.ts index cff3f591f8a..b4f67ca056d 100644 --- a/src/services/patternMatcher.ts +++ b/src/services/patternMatcher.ts @@ -188,7 +188,7 @@ namespace ts { } function getWordSpans(word: string): TextSpan[] { - if (!hasProperty(stringToWordSpans, word)) { + if (!(word in stringToWordSpans)) { stringToWordSpans[word] = breakIntoWordSpans(word); } diff --git a/src/services/services.ts b/src/services/services.ts index 0c1f5929fbf..87093645e2a 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2042,7 +2042,7 @@ namespace ts { function fixupCompilerOptions(options: CompilerOptions, diagnostics: Diagnostic[]): CompilerOptions { // Lazily create this value to fix module loading errors. commandLineOptionsStringToEnum = commandLineOptionsStringToEnum || filter(optionDeclarations, o => - typeof o.type === "object" && !forEachOwnProperty(>o.type, v => typeof v !== "number")); + typeof o.type === "object" && !forEachProperty(o.type, v => typeof v !== "number")); options = clone(options); @@ -2117,7 +2117,9 @@ namespace ts { sourceFile.moduleName = transpileOptions.moduleName; } - sourceFile.renamedDependencies = transpileOptions.renamedDependencies; + if (transpileOptions.renamedDependencies) { + sourceFile.renamedDependencies = createMap(transpileOptions.renamedDependencies); + } const newLine = getNewLineCharacter(options); @@ -6745,7 +6747,7 @@ namespace ts { // the function will add any found symbol of the property-name, then its sub-routine will call // getPropertySymbolsFromBaseTypes again to walk up any base types to prevent revisiting already // visited symbol, interface "C", the sub-routine will pass the current symbol as previousIterationSymbol. - if (hasProperty(previousIterationSymbolsCache, symbol.name)) { + if (symbol.name in previousIterationSymbolsCache) { return; } From f7f50073d3e8166b04647617e94f438e69db5595 Mon Sep 17 00:00:00 2001 From: Yui Date: Tue, 16 Aug 2016 08:47:21 -0700 Subject: [PATCH 62/72] Fix 10625: JSX Not validating when index signature is present (#10352) * Check for type of property declaration before using index signature * Add tests and baselines * fix linting error --- src/compiler/checker.ts | 7 ++- .../tsxAttributeResolution14.errors.txt | 38 +++++++++++++++ .../reference/tsxAttributeResolution14.js | 47 +++++++++++++++++++ .../jsx/tsxAttributeResolution14.tsx | 32 +++++++++++++ 4 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 tests/baselines/reference/tsxAttributeResolution14.errors.txt create mode 100644 tests/baselines/reference/tsxAttributeResolution14.js create mode 100644 tests/cases/conformance/jsx/tsxAttributeResolution14.tsx diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 984cedbf5ad..25a35c307f4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10140,10 +10140,9 @@ namespace ts { const correspondingPropSymbol = getPropertyOfType(elementAttributesType, node.name.text); correspondingPropType = correspondingPropSymbol && getTypeOfSymbol(correspondingPropSymbol); if (isUnhyphenatedJsxName(node.name.text)) { - // Maybe there's a string indexer? - const indexerType = getIndexTypeOfType(elementAttributesType, IndexKind.String); - if (indexerType) { - correspondingPropType = indexerType; + const attributeType = getTypeOfPropertyOfType(elementAttributesType, getTextOfPropertyName(node.name)) || getIndexTypeOfType(elementAttributesType, IndexKind.String); + if (attributeType) { + correspondingPropType = attributeType; } else { // If there's no corresponding property with this name, error diff --git a/tests/baselines/reference/tsxAttributeResolution14.errors.txt b/tests/baselines/reference/tsxAttributeResolution14.errors.txt new file mode 100644 index 00000000000..81d42e49059 --- /dev/null +++ b/tests/baselines/reference/tsxAttributeResolution14.errors.txt @@ -0,0 +1,38 @@ +tests/cases/conformance/jsx/file.tsx(14,28): error TS2322: Type 'number' is not assignable to type 'string'. +tests/cases/conformance/jsx/file.tsx(16,28): error TS2322: Type 'boolean' is not assignable to type 'string | number'. + + +==== tests/cases/conformance/jsx/react.d.ts (0 errors) ==== + + declare module JSX { + interface Element { } + interface IntrinsicElements { + div: any; + } + interface ElementAttributesProperty { prop: any } + } + +==== tests/cases/conformance/jsx/file.tsx (2 errors) ==== + + interface IProps { + primaryText: string, + [propName: string]: string | number + } + + function VerticalNavMenuItem(prop: IProps) { + return
props.primaryText
+ } + + function VerticalNav() { + return ( +
+ // error + ~~~~~~~~~~~~~~~ +!!! error TS2322: Type 'number' is not assignable to type 'string'. + // ok + // error + ~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2322: Type 'boolean' is not assignable to type 'string | number'. +
+ ) + } \ No newline at end of file diff --git a/tests/baselines/reference/tsxAttributeResolution14.js b/tests/baselines/reference/tsxAttributeResolution14.js new file mode 100644 index 00000000000..d920179458c --- /dev/null +++ b/tests/baselines/reference/tsxAttributeResolution14.js @@ -0,0 +1,47 @@ +//// [tests/cases/conformance/jsx/tsxAttributeResolution14.tsx] //// + +//// [react.d.ts] + +declare module JSX { + interface Element { } + interface IntrinsicElements { + div: any; + } + interface ElementAttributesProperty { prop: any } +} + +//// [file.tsx] + +interface IProps { + primaryText: string, + [propName: string]: string | number +} + +function VerticalNavMenuItem(prop: IProps) { + return
props.primaryText
+} + +function VerticalNav() { + return ( +
+ // error + // ok + // error +
+ ) +} + +//// [file.jsx] +function VerticalNavMenuItem(prop) { + return
props.primaryText
; +} +function VerticalNav() { + return (
+ // error + // error + // ok + // ok + // error + // error +
); +} diff --git a/tests/cases/conformance/jsx/tsxAttributeResolution14.tsx b/tests/cases/conformance/jsx/tsxAttributeResolution14.tsx new file mode 100644 index 00000000000..1e4418e7fba --- /dev/null +++ b/tests/cases/conformance/jsx/tsxAttributeResolution14.tsx @@ -0,0 +1,32 @@ +//@jsx: preserve +//@module: amd + +//@filename: react.d.ts +declare module JSX { + interface Element { } + interface IntrinsicElements { + div: any; + } + interface ElementAttributesProperty { prop: any } +} + +//@filename: file.tsx + +interface IProps { + primaryText: string, + [propName: string]: string | number +} + +function VerticalNavMenuItem(prop: IProps) { + return
props.primaryText
+} + +function VerticalNav() { + return ( +
+ // error + // ok + // error +
+ ) +} \ No newline at end of file From 57701575044e8acbd55845cddb5bd7a7f08331b6 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 16 Aug 2016 09:41:33 -0700 Subject: [PATCH 63/72] Adding more comments --- src/compiler/checker.ts | 15 ++++++++++++++- src/compiler/types.ts | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8a32e23e1ff..685b32f796f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8809,21 +8809,34 @@ namespace ts { const type = getTypeOfSymbol(localOrExportSymbol); const declaration = localOrExportSymbol.valueDeclaration; + // We only narrow variables and parameters occurring in a non-assignment position. For all other + // entities we simply return the declared type. if (!(localOrExportSymbol.flags & SymbolFlags.Variable) || isAssignmentTarget(node) || !declaration) { return type; } - + // The declaration container is the innermost function that encloses the declaration of the variable + // or parameter. The flow container is the innermost function starting with which we analyze the control + // flow graph to determine the control flow based type. const isParameter = getRootDeclaration(declaration).kind === SyntaxKind.Parameter; const declarationContainer = getControlFlowContainer(declaration); let flowContainer = getControlFlowContainer(node); + // When the control flow originates in a function expression or arrow function and we are referencing + // a const variable or parameter from an outer function, we extend the origin of the control flow + // analysis to include the immediately enclosing function. while (flowContainer !== declarationContainer && (flowContainer.kind === SyntaxKind.FunctionExpression || flowContainer.kind === SyntaxKind.ArrowFunction) && (isReadonlySymbol(localOrExportSymbol) || isParameter && !isParameterAssigned(localOrExportSymbol))) { flowContainer = getControlFlowContainer(flowContainer); } + // We only look for uninitialized variables in strict null checking mode, and only when we can analyze + // the entire control flow graph from the variable's declaration (i.e. when the flow container and + // declaration container are the same). const assumeInitialized = !strictNullChecks || (type.flags & TypeFlags.Any) !== 0 || isParameter || flowContainer !== declarationContainer || isInAmbientContext(declaration); const flowType = getFlowTypeOfReference(node, type, assumeInitialized, flowContainer); + // A variable is considered uninitialized when it is possible to analyze the entire control flow graph + // from declaration to use, and when the variable's declared type doesn't include undefined but the + // control flow based type does include undefined. if (!assumeInitialized && !(getFalsyFlags(type) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) { error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol)); // Return the declared type to reduce follow-on errors diff --git a/src/compiler/types.ts b/src/compiler/types.ts index cca94c0adc1..8e9953ee64d 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2157,7 +2157,7 @@ namespace ts { /* @internal */ exportSymbol?: Symbol; // Exported symbol associated with this symbol /* @internal */ constEnumOnlyModule?: boolean; // True if module contains only const enums or other modules with only const enums /* @internal */ isReferenced?: boolean; // True if the symbol is referenced elsewhere - /* @internal */ isAssigned?: boolean; // True if the symbol has assignments + /* @internal */ isAssigned?: boolean; // True if the symbol is a parameter with assignments } /* @internal */ From 889e5ac7ae00d9aefd664d11ec942c13acdd79a4 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 16 Aug 2016 11:15:15 -0700 Subject: [PATCH 64/72] Clean up/move some Map helper functions. --- src/compiler/commandLineParser.ts | 4 +- src/compiler/core.ts | 151 ++---------------- src/harness/fourslash.ts | 2 +- src/harness/harnessLanguageService.ts | 2 +- src/harness/unittests/moduleResolution.ts | 2 +- .../unittests/reuseProgramStructure.ts | 2 +- .../unittests/tsserverProjectSystem.ts | 2 +- src/server/editorServices.ts | 2 +- src/services/services.ts | 2 +- 9 files changed, 18 insertions(+), 151 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 35140855d81..616bf5e70c0 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1016,8 +1016,8 @@ namespace ts { } } - const literalFiles = reduceOwnProperties(literalFileMap, addFileToOutput, []); - const wildcardFiles = reduceOwnProperties(wildcardFileMap, addFileToOutput, []); + const literalFiles = reduceProperties(literalFileMap, addFileToOutput, []); + const wildcardFiles = reduceProperties(wildcardFileMap, addFileToOutput, []); wildcardFiles.sort(host.useCaseSensitiveFileNames ? compareStrings : compareStringsCaseInsensitive); return { fileNames: literalFiles.concat(wildcardFiles), diff --git a/src/compiler/core.ts b/src/compiler/core.ts index d10d76b3492..40341929c02 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -30,8 +30,10 @@ namespace ts { map["__"] = undefined; delete map["__"]; - if (template) { - copyOwnProperties(template, map); + // Copies keys/values from template. Note that for..in will not throw if + // template is undefined, and instead will just exit the loop. + for (const key in template) if (hasOwnProperty.call(template, key)) { + map[key] = template[key]; } return map; @@ -412,9 +414,6 @@ namespace ts { /** * Enumerates the properties of a Map, invoking a callback and returning the first truthy result. * - * NOTE: This is intended for use with Map objects. For MapLike objects, use - * forEachOwnProperties instead as it offers better runtime safety. - * * @param map A map for which properties should be enumerated. * @param callback A callback to invoke for each property. */ @@ -426,53 +425,6 @@ namespace ts { return result; } - /** - * Enumerates the owned properties of a MapLike, invoking a callback and returning the first truthy result. - * - * NOTE: This is intended for use with MapLike objects. For Map objects, use - * forEachProperty instead as it offers better performance. - * - * @param map A map for which properties should be enumerated. - * @param callback A callback to invoke for each property. - */ - export function forEachOwnProperty(map: MapLike, callback: (value: T, key: string) => U): U { - let result: U; - for (const key in map) if (hasOwnProperty.call(map, key)) { - if (result = callback(map[key], key)) break; - } - return result; - } - - /** - * Maps key-value pairs of a map into a new map. - * - * NOTE: The key-value pair passed to the callback is *not* safe to cache between invocations - * of the callback. - * - * @param map A map. - * @param callback A callback that maps a key-value pair into a new key-value pair. - */ - export function mapPairs(map: Map, callback: (entry: [string, T]) => [string, U]): Map { - let result: Map; - if (map) { - result = createMap(); - let inPair: [string, T]; - for (const key in map) { - if (inPair) { - inPair[0] = key; - inPair[1] = map[key]; - } - else { - inPair = [key, map[key]]; - } - - const outPair = callback(inPair); - result[outPair[0]] = outPair[1]; - } - } - return result; - } - /** * Returns true if a Map has some matching property. * @@ -489,9 +441,6 @@ namespace ts { /** * Performs a shallow copy of the properties from a source Map to a target MapLike * - * NOTE: This is intended for use with Map objects. For MapLike objects, use - * copyOwnProperties instead as it offers better runtime safety. - * * @param source A map from which properties should be copied. * @param target A map to which properties should be copied. */ @@ -501,21 +450,6 @@ namespace ts { } } - /** - * Performs a shallow copy of the owned properties from a source map to a target map-like. - * - * NOTE: This is intended for use with MapLike objects. For Map objects, use - * copyProperties instead as it offers better performance. - * - * @param source A map-like from which properties should be copied. - * @param target A map-like to which properties should be copied. - */ - export function copyOwnProperties(source: MapLike, target: MapLike): void { - for (const key in source) if (hasOwnProperty.call(source, key)) { - target[key] = source[key]; - } - } - /** * Reduce the properties of a map. * @@ -552,72 +486,9 @@ namespace ts { return result; } - /** - * Counts the properties of a map. - * - * NOTE: This is intended for use with Map objects. For MapLike objects, use - * countOwnProperties instead as it offers better runtime safety. - * - * @param map A map whose properties should be counted. - * @param predicate An optional callback used to limit which properties should be counted. - */ - export function countProperties(map: Map, predicate?: (value: T, key: string) => boolean) { - let count = 0; - for (const key in map) { - if (!predicate || predicate(map[key], key)) { - count++; - } - } - return count; - } - - /** - * Counts the owned properties of a map-like. - * - * NOTE: This is intended for use with MapLike objects. For Map objects, use - * countProperties instead as it offers better performance. - * - * @param map A map-like whose properties should be counted. - * @param predicate An optional callback used to limit which properties should be counted. - */ - export function countOwnProperties(map: MapLike, predicate?: (value: T, key: string) => boolean) { - let count = 0; - for (const key in map) if (hasOwnProperty.call(map, key)) { - if (!predicate || predicate(map[key], key)) { - count++; - } - } - return count; - } - - /** - * Performs a shallow equality comparison of the contents of two maps. - * - * NOTE: This is intended for use with Map objects. For MapLike objects, use - * equalOwnProperties instead as it offers better runtime safety. - * - * @param left A map whose properties should be compared. - * @param right A map whose properties should be compared. - */ - export function equalProperties(left: Map, right: Map, equalityComparer?: (left: T, right: T) => boolean) { - if (left === right) return true; - if (!left || !right) return false; - for (const key in left) { - if (!(key in right)) return false; - if (equalityComparer ? !equalityComparer(left[key], right[key]) : left[key] !== right[key]) return false; - } - for (const key in right) { - if (!(key in left)) return false; - } - return true; - } - /** * Performs a shallow equality comparison of the contents of two map-likes. * - * NOTE: This is intended for use with MapLike objects. For Map objects, use - * equalProperties instead as it offers better performance. - * * @param left A map-like whose properties should be compared. * @param right A map-like whose properties should be compared. */ @@ -670,17 +541,13 @@ namespace ts { return result; } - export function extend, T2 extends MapLike<{}>>(first: T1 , second: T2): T1 & T2 { + export function extend(first: T1 , second: T2): T1 & T2 { const result: T1 & T2 = {}; - for (const id in first) { - if (hasOwnProperty.call(first, id)) { - (result as any)[id] = first[id]; - } + for (const id in second) if (hasOwnProperty.call(second, id)) { + (result as any)[id] = (second as any)[id]; } - for (const id in second) { - if (hasOwnProperty.call(second, id) && !hasOwnProperty.call(result, id)) { - (result as any)[id] = second[id]; - } + for (const id in first) if (hasOwnProperty.call(first, id)) { + (result as any)[id] = (first as any)[id]; } return result; } diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index ba1994c8141..f9fdd5a1159 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -773,7 +773,7 @@ namespace FourSlash { } public verifyRangesWithSameTextReferenceEachOther() { - ts.forEachOwnProperty(this.rangesByText(), ranges => this.verifyRangesReferenceEachOther(ranges)); + ts.forEachProperty(this.rangesByText(), ranges => this.verifyRangesReferenceEachOther(ranges)); } private verifyReferencesWorker(references: ts.ReferenceEntry[], fileName: string, start: number, end: number, isWriteAccess?: boolean, isDefinition?: boolean) { diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index b37a6a90899..94124432780 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -135,7 +135,7 @@ namespace Harness.LanguageService { public getFilenames(): string[] { const fileNames: string[] = []; - ts.forEachOwnProperty(this.fileNameToScript, (scriptInfo) => { + ts.forEachProperty(this.fileNameToScript, (scriptInfo) => { if (scriptInfo.isRootFile) { // only include root files here // usually it means that we won't include lib.d.ts in the list of root files so it won't mess the computation of compilation root dir. diff --git a/src/harness/unittests/moduleResolution.ts b/src/harness/unittests/moduleResolution.ts index ff0151c40a5..09febd2300a 100644 --- a/src/harness/unittests/moduleResolution.ts +++ b/src/harness/unittests/moduleResolution.ts @@ -358,7 +358,7 @@ export = C; function test(files: Map, options: CompilerOptions, currentDirectory: string, useCaseSensitiveFileNames: boolean, rootFiles: string[], diagnosticCodes: number[]): void { const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); if (!useCaseSensitiveFileNames) { - files = mapPairs(files, ([fileName, file]) => [getCanonicalFileName(fileName), file]); + files = reduceProperties(files, (files, file, fileName) => (files[getCanonicalFileName(fileName)] = file, files), createMap()); } const host: CompilerHost = { diff --git a/src/harness/unittests/reuseProgramStructure.ts b/src/harness/unittests/reuseProgramStructure.ts index 9f2b0c343c2..60687d34c55 100644 --- a/src/harness/unittests/reuseProgramStructure.ts +++ b/src/harness/unittests/reuseProgramStructure.ts @@ -183,7 +183,7 @@ namespace ts { } else { assert.isTrue(cache !== undefined, `expected ${caption} to be set`); - assert.isTrue(equalProperties(expectedContent, cache, entryChecker), `contents of ${caption} did not match the expected contents.`); + assert.isTrue(equalOwnProperties(expectedContent, cache, entryChecker), `contents of ${caption} did not match the expected contents.`); } } diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 9887573436e..49f033b9def 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -69,7 +69,7 @@ namespace ts { } function checkMapKeys(caption: string, map: Map, expectedKeys: string[]) { - assert.equal(countProperties(map), expectedKeys.length, `${caption}: incorrect size of map`); + assert.equal(reduceProperties(map, count => count + 1, 0), expectedKeys.length, `${caption}: incorrect size of map`); for (const name of expectedKeys) { assert.isTrue(name in map, `${caption} is expected to contain ${name}, actual keys: ${Object.keys(map)}`); } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 9ba243d9704..db9bdd5043b 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1419,7 +1419,7 @@ namespace ts.server { /*recursive*/ true ); - project.directoriesWatchedForWildcards = reduceOwnProperties(projectOptions.wildcardDirectories, (watchers, flag, directory) => { + project.directoriesWatchedForWildcards = reduceProperties(createMap(projectOptions.wildcardDirectories), (watchers, flag, directory) => { if (comparePaths(configDirectoryPath, directory, ".", !this.host.useCaseSensitiveFileNames) !== Comparison.EqualTo) { const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0; this.log(`Add ${ recursive ? "recursive " : ""}watcher for: ${directory}`); diff --git a/src/services/services.ts b/src/services/services.ts index 87093645e2a..1153e2e9e3d 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2058,7 +2058,7 @@ namespace ts { options[opt.name] = parseCustomTypeOption(opt, value, diagnostics); } else { - if (!forEachOwnProperty(opt.type, v => v === value)) { + if (!forEachProperty(opt.type, v => v === value)) { // Supplied value isn't a valid enum value. diagnostics.push(createCompilerDiagnosticForInvalidCustomType(opt)); } From c0146556e863f39eaa1922d66b1dda6b173f7b8a Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 16 Aug 2016 11:18:24 -0700 Subject: [PATCH 65/72] Revert some formatting changes. --- src/compiler/checker.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c309b9e36c0..73ae7fc750d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8467,11 +8467,11 @@ namespace ts { return type; } const doubleEquals = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken; - const facts = doubleEquals - ? assumeTrue ? TypeFacts.EQUndefinedOrNull : TypeFacts.NEUndefinedOrNull - : value.kind === SyntaxKind.NullKeyword - ? assumeTrue ? TypeFacts.EQNull : TypeFacts.NENull - : assumeTrue ? TypeFacts.EQUndefined : TypeFacts.NEUndefined; + const facts = doubleEquals ? + assumeTrue ? TypeFacts.EQUndefinedOrNull : TypeFacts.NEUndefinedOrNull : + value.kind === SyntaxKind.NullKeyword ? + assumeTrue ? TypeFacts.EQNull : TypeFacts.NENull : + assumeTrue ? TypeFacts.EQUndefined : TypeFacts.NEUndefined; return getTypeWithFacts(type, facts); } if (type.flags & TypeFlags.NotUnionOrUnit) { @@ -8507,9 +8507,9 @@ namespace ts { return targetType; } } - const facts = assumeTrue - ? typeofEQFacts[literal.text] || TypeFacts.TypeofEQHostObject - : typeofNEFacts[literal.text] || TypeFacts.TypeofNEHostObject; + const facts = assumeTrue ? + typeofEQFacts[literal.text] || TypeFacts.TypeofEQHostObject : + typeofNEFacts[literal.text] || TypeFacts.TypeofNEHostObject; return getTypeWithFacts(type, facts); } From ce5e2078eed31c10c785150c22b4e31442f641e9 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Tue, 16 Aug 2016 11:29:09 -0700 Subject: [PATCH 66/72] Improve ReadonlyArray.concat to match Array The Array-based signature was incorrect and also out-of-date. --- src/lib/es5.d.ts | 7 ++- ...typeIsAssignableToReadonlyArray.errors.txt | 46 ++++++++++++++++ ...rayOfSubtypeIsAssignableToReadonlyArray.js | 54 +++++++++++++++++++ ...rayOfSubtypeIsAssignableToReadonlyArray.ts | 18 +++++++ 4 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/arrayOfSubtypeIsAssignableToReadonlyArray.errors.txt create mode 100644 tests/baselines/reference/arrayOfSubtypeIsAssignableToReadonlyArray.js create mode 100644 tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts diff --git a/src/lib/es5.d.ts b/src/lib/es5.d.ts index 496df578c19..e4cbe323c68 100644 --- a/src/lib/es5.d.ts +++ b/src/lib/es5.d.ts @@ -1006,7 +1006,12 @@ interface ReadonlyArray { * Combines two or more arrays. * @param items Additional items to add to the end of array1. */ - concat(...items: T[]): T[]; + concat(...items: T[][]): T[]; + /** + * Combines two or more arrays. + * @param items Additional items to add to the end of array1. + */ + concat(...items: (T | T[])[]): T[]; /** * Adds all the elements of an array separated by the specified separator string. * @param separator A string used to separate one element of an array from the next in the resulting String. If omitted, the array elements are separated with a comma. diff --git a/tests/baselines/reference/arrayOfSubtypeIsAssignableToReadonlyArray.errors.txt b/tests/baselines/reference/arrayOfSubtypeIsAssignableToReadonlyArray.errors.txt new file mode 100644 index 00000000000..6f4a0ef9d6d --- /dev/null +++ b/tests/baselines/reference/arrayOfSubtypeIsAssignableToReadonlyArray.errors.txt @@ -0,0 +1,46 @@ +tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts(13,1): error TS2322: Type 'A[]' is not assignable to type 'ReadonlyArray'. + Types of property 'concat' are incompatible. + Type '{ (...items: A[][]): A[]; (...items: (A | A[])[]): A[]; }' is not assignable to type '{ >(...items: U[]): B[]; (...items: B[][]): B[]; (...items: (B | B[])[]): B[]; }'. + Type 'A[]' is not assignable to type 'B[]'. + Type 'A' is not assignable to type 'B'. + Property 'b' is missing in type 'A'. +tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts(18,1): error TS2322: Type 'C' is not assignable to type 'ReadonlyArray'. + Types of property 'concat' are incompatible. + Type '{ (...items: A[][]): A[]; (...items: (A | A[])[]): A[]; }' is not assignable to type '{ >(...items: U[]): B[]; (...items: B[][]): B[]; (...items: (B | B[])[]): B[]; }'. + Type 'A[]' is not assignable to type 'B[]'. + Type 'A' is not assignable to type 'B'. + + +==== tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts (2 errors) ==== + class A { a } + class B extends A { b } + class C extends Array { c } + declare var ara: A[]; + declare var arb: B[]; + declare var cra: C; + declare var crb: C; + declare var rra: ReadonlyArray; + declare var rrb: ReadonlyArray; + rra = ara; + rrb = arb; // OK, Array is assignable to ReadonlyArray + rra = arb; + rrb = ara; // error: 'A' is not assignable to 'B' + ~~~ +!!! error TS2322: Type 'A[]' is not assignable to type 'ReadonlyArray'. +!!! error TS2322: Types of property 'concat' are incompatible. +!!! error TS2322: Type '{ (...items: A[][]): A[]; (...items: (A | A[])[]): A[]; }' is not assignable to type '{ >(...items: U[]): B[]; (...items: B[][]): B[]; (...items: (B | B[])[]): B[]; }'. +!!! error TS2322: Type 'A[]' is not assignable to type 'B[]'. +!!! error TS2322: Type 'A' is not assignable to type 'B'. +!!! error TS2322: Property 'b' is missing in type 'A'. + + rra = cra; + rra = crb; // OK, C is assignable to ReadonlyArray + rrb = crb; + rrb = cra; // error: 'A' is not assignable to 'B' + ~~~ +!!! error TS2322: Type 'C' is not assignable to type 'ReadonlyArray'. +!!! error TS2322: Types of property 'concat' are incompatible. +!!! error TS2322: Type '{ (...items: A[][]): A[]; (...items: (A | A[])[]): A[]; }' is not assignable to type '{ >(...items: U[]): B[]; (...items: B[][]): B[]; (...items: (B | B[])[]): B[]; }'. +!!! error TS2322: Type 'A[]' is not assignable to type 'B[]'. +!!! error TS2322: Type 'A' is not assignable to type 'B'. + \ No newline at end of file diff --git a/tests/baselines/reference/arrayOfSubtypeIsAssignableToReadonlyArray.js b/tests/baselines/reference/arrayOfSubtypeIsAssignableToReadonlyArray.js new file mode 100644 index 00000000000..255b52e16c9 --- /dev/null +++ b/tests/baselines/reference/arrayOfSubtypeIsAssignableToReadonlyArray.js @@ -0,0 +1,54 @@ +//// [arrayOfSubtypeIsAssignableToReadonlyArray.ts] +class A { a } +class B extends A { b } +class C extends Array { c } +declare var ara: A[]; +declare var arb: B[]; +declare var cra: C; +declare var crb: C; +declare var rra: ReadonlyArray; +declare var rrb: ReadonlyArray; +rra = ara; +rrb = arb; // OK, Array is assignable to ReadonlyArray +rra = arb; +rrb = ara; // error: 'A' is not assignable to 'B' + +rra = cra; +rra = crb; // OK, C is assignable to ReadonlyArray +rrb = crb; +rrb = cra; // error: 'A' is not assignable to 'B' + + +//// [arrayOfSubtypeIsAssignableToReadonlyArray.js] +var __extends = (this && this.__extends) || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +}; +var A = (function () { + function A() { + } + return A; +}()); +var B = (function (_super) { + __extends(B, _super); + function B() { + _super.apply(this, arguments); + } + return B; +}(A)); +var C = (function (_super) { + __extends(C, _super); + function C() { + _super.apply(this, arguments); + } + return C; +}(Array)); +rra = ara; +rrb = arb; // OK, Array is assignable to ReadonlyArray +rra = arb; +rrb = ara; // error: 'A' is not assignable to 'B' +rra = cra; +rra = crb; // OK, C is assignable to ReadonlyArray +rrb = crb; +rrb = cra; // error: 'A' is not assignable to 'B' diff --git a/tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts b/tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts new file mode 100644 index 00000000000..d621cf460bc --- /dev/null +++ b/tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts @@ -0,0 +1,18 @@ +class A { a } +class B extends A { b } +class C extends Array { c } +declare var ara: A[]; +declare var arb: B[]; +declare var cra: C; +declare var crb: C; +declare var rra: ReadonlyArray; +declare var rrb: ReadonlyArray; +rra = ara; +rrb = arb; // OK, Array is assignable to ReadonlyArray +rra = arb; +rrb = ara; // error: 'A' is not assignable to 'B' + +rra = cra; +rra = crb; // OK, C is assignable to ReadonlyArray +rrb = crb; +rrb = cra; // error: 'A' is not assignable to 'B' From dcf3946b6876d6a9408bebef297f434b05168b33 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Tue, 16 Aug 2016 11:31:32 -0700 Subject: [PATCH 67/72] Fix link to blog --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fca2890bc77..d16bc363b26 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Join the chat at https://gitter.im/Microsoft/TypeScript](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Microsoft/TypeScript?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[TypeScript](http://www.typescriptlang.org/) is a language for application-scale JavaScript. TypeScript adds optional types, classes, and modules to JavaScript. TypeScript supports tools for large-scale JavaScript applications for any browser, for any host, on any OS. TypeScript compiles to readable, standards-based JavaScript. Try it out at the [playground](http://www.typescriptlang.org/Playground), and stay up to date via [our blog](http://blogs.msdn.com/typescript) and [Twitter account](https://twitter.com/typescriptlang). +[TypeScript](http://www.typescriptlang.org/) is a language for application-scale JavaScript. TypeScript adds optional types, classes, and modules to JavaScript. TypeScript supports tools for large-scale JavaScript applications for any browser, for any host, on any OS. TypeScript compiles to readable, standards-based JavaScript. Try it out at the [playground](http://www.typescriptlang.org/Playground), and stay up to date via [our blog](https://blogs.msdn.microsoft.com/typescript) and [Twitter account](https://twitter.com/typescriptlang). ## Installing From a6974474a1fb850529378570da9e914f8a44040a Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Tue, 16 Aug 2016 13:51:17 -0700 Subject: [PATCH 68/72] Remove old assertion about when we're allowed to use fileExists --- src/services/services.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/services/services.ts b/src/services/services.ts index 850e29030d8..5876da74f15 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3114,7 +3114,6 @@ namespace ts { getCurrentDirectory: () => currentDirectory, fileExists: (fileName): boolean => { // stub missing host functionality - Debug.assert(!host.resolveModuleNames || !host.resolveTypeReferenceDirectives); return hostCache.getOrCreateEntry(fileName) !== undefined; }, readFile: (fileName): string => { From a36e15558e3e0608fc03986b422877a9a59d1b86 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Tue, 16 Aug 2016 14:04:02 -0700 Subject: [PATCH 69/72] Update error message for conflicting type definitions Fixes #10370 --- src/compiler/diagnosticMessages.json | 2 +- src/compiler/program.ts | 2 +- tests/baselines/reference/library-reference-5.errors.txt | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index c1eead94f14..c6a3698ea16 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2231,7 +2231,7 @@ "category": "Error", "code": 4082 }, - "Conflicting library definitions for '{0}' found at '{1}' and '{2}'. Copy the correct file to the 'typings' folder to resolve this conflict.": { + "Conflicting definitions for '{0}' found at '{1}' and '{2}'. Consider installing a specific version of this library to resolve the conflict.": { "category": "Message", "code": 4090 }, diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 767fcb488bb..3f698e82758 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -2048,7 +2048,7 @@ namespace ts { const otherFileText = host.readFile(resolvedTypeReferenceDirective.resolvedFileName); if (otherFileText !== getSourceFile(previousResolution.resolvedFileName).text) { fileProcessingDiagnostics.add(createDiagnostic(refFile, refPos, refEnd, - Diagnostics.Conflicting_library_definitions_for_0_found_at_1_and_2_Copy_the_correct_file_to_the_typings_folder_to_resolve_this_conflict, + Diagnostics.Conflicting_definitions_for_0_found_at_1_and_2_Consider_installing_a_specific_version_of_this_library_to_resolve_the_conflict, typeReferenceDirective, resolvedTypeReferenceDirective.resolvedFileName, previousResolution.resolvedFileName diff --git a/tests/baselines/reference/library-reference-5.errors.txt b/tests/baselines/reference/library-reference-5.errors.txt index a3729bc3a99..c23b49c61de 100644 --- a/tests/baselines/reference/library-reference-5.errors.txt +++ b/tests/baselines/reference/library-reference-5.errors.txt @@ -1,4 +1,4 @@ -/node_modules/bar/index.d.ts(1,1): message TS4090: Conflicting library definitions for 'alpha' found at '/node_modules/bar/node_modules/alpha/index.d.ts' and '/node_modules/foo/node_modules/alpha/index.d.ts'. Copy the correct file to the 'typings' folder to resolve this conflict. +/node_modules/bar/index.d.ts(1,1): message TS4090: Conflicting definitions for 'alpha' found at '/node_modules/bar/node_modules/alpha/index.d.ts' and '/node_modules/foo/node_modules/alpha/index.d.ts'. Consider installing a specific version of this library to resolve the conflict. ==== /src/root.ts (0 errors) ==== @@ -18,7 +18,7 @@ ==== /node_modules/bar/index.d.ts (1 errors) ==== /// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! message TS4090: Conflicting library definitions for 'alpha' found at '/node_modules/bar/node_modules/alpha/index.d.ts' and '/node_modules/foo/node_modules/alpha/index.d.ts'. Copy the correct file to the 'typings' folder to resolve this conflict. +!!! message TS4090: Conflicting definitions for 'alpha' found at '/node_modules/bar/node_modules/alpha/index.d.ts' and '/node_modules/foo/node_modules/alpha/index.d.ts'. Consider installing a specific version of this library to resolve the conflict. declare var bar: any; ==== /node_modules/bar/node_modules/alpha/index.d.ts (0 errors) ==== From c42f1cb0d0fd298039bf6fe374e1d2a6acfe6214 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Wed, 17 Aug 2016 06:21:49 -0700 Subject: [PATCH 70/72] Explain why we lower-case type reference directives --- src/compiler/program.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 004f4102667..1c491aeeaac 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -2010,6 +2010,7 @@ namespace ts { } function processTypeReferenceDirectives(file: SourceFile) { + // We lower-case all type references because npm automatically lowercases all packages. See GH#9824. const typeDirectives = map(file.typeReferenceDirectives, ref => ref.fileName.toLocaleLowerCase()); const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeDirectives, file.fileName); From 07a8f31a2d294fb7d9aca3fe937cab0bb5345e21 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Wed, 17 Aug 2016 10:43:02 -0700 Subject: [PATCH 71/72] Correctly merge bindThisPropertyAssignment Also simply it considerably after noticing that it's *only* called for Javascript files, so there was a lot of dead code for TS cases that never happened. --- src/compiler/binder.ts | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index ee82a88d7a5..5aaf6f50183 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1977,33 +1977,25 @@ namespace ts { } function bindThisPropertyAssignment(node: BinaryExpression) { + Debug.assert(isInJavaScriptFile(node)); // Declare a 'member' if the container is an ES5 class or ES6 constructor - let assignee: Node; if (container.kind === SyntaxKind.FunctionDeclaration || container.kind === SyntaxKind.FunctionExpression) { - assignee = container; + container.symbol.members = container.symbol.members || createMap(); + // It's acceptable for multiple 'this' assignments of the same identifier to occur + declareSymbol(container.symbol.members, container.symbol, node, SymbolFlags.Property, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property); } else if (container.kind === SyntaxKind.Constructor) { - if (isInJavaScriptFile(node)) { - // this.foo assignment in a JavaScript class - // Bind this property to the containing class - const saveContainer = container; - container = container.parent; - bindPropertyOrMethodOrAccessor(node, SymbolFlags.Property, SymbolFlags.None); - container = saveContainer; - return; - } - else { - assignee = container.parent; + // this.foo assignment in a JavaScript class + // Bind this property to the containing class + const saveContainer = container; + container = container.parent; + // AND it can be overwritten by subsequent method declarations + const symbol = bindPropertyOrMethodOrAccessor(node, SymbolFlags.Property, SymbolFlags.None); + if (symbol) { + (symbol as Symbol).isReplaceableByMethod = true; } + container = saveContainer; } - else { - return; - } - assignee.symbol.members = assignee.symbol.members || createMap(); - // It's acceptable for multiple 'this' assignments of the same identifier to occur - // AND it can be overwritten by subsequent method declarations - const symbol = declareSymbol(assignee.symbol.members, assignee.symbol, node, SymbolFlags.Property, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property); - symbol.isReplaceableByMethod = true; } function bindPrototypePropertyAssignment(node: BinaryExpression) { From c73efe2fb629bcb3a283eaf7979d7070705546cc Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Wed, 17 Aug 2016 10:45:35 -0700 Subject: [PATCH 72/72] Fix comment --- src/compiler/binder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 5aaf6f50183..d8017d601ad 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1989,9 +1989,9 @@ namespace ts { // Bind this property to the containing class const saveContainer = container; container = container.parent; - // AND it can be overwritten by subsequent method declarations const symbol = bindPropertyOrMethodOrAccessor(node, SymbolFlags.Property, SymbolFlags.None); if (symbol) { + // constructor-declared symbols can be overwritten by subsequent method declarations (symbol as Symbol).isReplaceableByMethod = true; } container = saveContainer;